티스토리 뷰

Kotlin에서 지원하는 기능 중에서 Kotlin 코드에서 XML 인스턴스로 바로 접근이 가능하도록 도와주는 Android Extension이라는 기능이 있다.

XML 인스턴스를 Kotlin으로 가져와서 사용할 수 있도록 만든 프로퍼티를 합성 프로퍼티(Synthetic Property)라고 한다.

이 프로퍼티는 클래스 내에 뷰 ID 이름으로 생성된다.

예를 들어 xml 코드에서 아래와 같은 Button을 하나 정의했다고 가정하자.

<Button
    android:id="@+id/btn_save"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

 

이 Button은 Android Extension에 의해 Kotlin의 합성 프로퍼티가 만들어진다. 이 프로퍼티는 아래 예제 코드처럼 Button의 id인 ‘btn_save’를 통해 접근할 수 있다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    btn_save.setOnClickListener { 
        // 버튼 클릭 동작 정의
    }
}

 

이 합성 프로퍼티는 내부적으로 findViewById()를 호출한다. 그렇기 때문에 xml 인스턴스에 접근하기 위해 findViewById() 메서드를 직접 호출해서 인스턴스를 저장할 변수를 따로 만들지 않아도 되고, 결국 코드가 간결해지면서 생산성이 높아진다.

여기서 findViewById()는 호출 비용이 큰 메서드이라서 자주 호출하면 그만큼 성능에 지장이 생긴다. 그렇기 때문에 findViewById() 메서드 호출을 최소한으로 하는 것이 좋다. 이를 반영해 Kotlin은 어떤 합성 프로퍼티를 사용할 때, 최초의 한 번만 findViewById()를 호출한 뒤 클래스 내부에 캐시를 추가해서 그 다음 사용부터는 내부에 캐싱된 인스턴스를 반환한다.

 

 

 

이러한 Android Extension 기능을 이용해 RecyclerView의 ViewHolder를 사용할 때 유의할 점이 있다.

VIewHolder 내부에서 직접 합성 프로퍼티를 사용하는 것은 좋지 않다는 것이다.

예를 들어 아래의 코드를 보자.

override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
    with(holder.view.iv_image_item) {
        setImageBitmap(imagebitmapImages[position]))
        setOnClickListener {
            // 이미지 클릭 동작 정의
        }
    }
}

 

 

위 코드는 iv_image_item을 id로 갖는 <ImageView> 합성 프로퍼티를 ViewHolder에서 직접 호출하고 있다.

액티비티 내에서 합성 프로퍼티를 사용할 때는 캐싱을 통해 findViewById() 메서드 호출을 최소화 하지만, 이처럼 RecyclerView의 ViewHolder 안에서 직접 합성 프로퍼티를 사용하게 되면 Binding 작업 시 캐시 객체를 저장할 공간이 없어 매번 findViewById() 메서드를 호출하게 되어 성능이 떨어진다.

 

 

 

이 문제를 해결하기 위해서는 아래 코드처럼 ViewHolder 내부에 각 뷰를 위한 프로퍼티를 추가하는 방법이 있다.

class ImageViewHolder(val view: View): RecyclerView.ViewHolder(view) {
    val imageItem = view.iv_image_item
}
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
    with(holder.imageItem) {
        setImageBitmap(imagebitmapImages[position]))
        setOnClickListener {
            // 이미지 클릭 동작 정의
        }
    }
}

 

이렇게 하면 ViewHolder 내부에 뷰 인스턴스를 저장할 수 있는 필드가 생기면서 Binding 작업 시 findViewById() 메서드를 매번 호출하는 것이 아닌 ViewHolder에 추가한 프로퍼티를 가져와서 사용하는 구조로 바뀐다.

 

 

 

이 방법은 성능은 개선되지만 각 뷰의 인스턴스를 담는 프로퍼티를 직접 추가해야 하는 불편함이 있다.

그래서 나온 것이 LayoutContainer 인터페이스이다. 이 기능은 Kotlin 1.1.4 이상 버전부터 지원하고 있고, 아직 실험실 기능의 일부이기 때문에 LayoutContainer를 사용하기 위해서는 빌드 스크립트에 아래 코드를 추가해야 한다.

apply plugin: 'kotlin-android-extensions'

android {
    ...
}

dependencies {
    ...
}

androidExtensions {
    // 실험실 기능 활성화
    experimental = true
}

 

이제 LayoutContainer 인터페이스를 이용해 AndroidExtensionsViewHolder를 아래 코드처럼 정의하고, ImageViewHolder는 AndroidExtensionsViewHolder만 상속받도록 정의하면 된다.

abstract class AndroidExtensionsViewHolder(override val containerView: View)
    : RecyclerView.ViewHolder(containerView), LayoutContainer
    
class ImageViewHolder(val view: View): AndroidExtensionsViewHolder(view)

 

위처럼 정의하면 자동으로 ImageViewHolder 내부에 사용하는 뷰 인스턴스 프로퍼티가 추가된다.

그렇기 때문에 아래 코드처럼 Binder에서 직접 합성 프로퍼티에 접근하는 것 처럼 작성해도 성능 문제가 발생하지 않는다.

 

 

 

 

 

 

 

[참고 사이트]

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함