文章首发于公众号「 技术最TOP 」

注释

ViewBinding 是Android Studio 3.6中增加的一个新性能,更精确的说,它是DataBinding 的一个更轻量变体,为什么要应用View Binding 呢?答案是性能。许多开发者应用Data Binding库来援用Layout XML中的视图,而疏忽它的其余弱小性能。相比来说,主动生成代码ViewBinding其实比DataBinding 性能更好。然而传统的形式应用View Binding 却不是很好,因为会有很多样板代码(垃圾代码)。

View Binding 的传统应用形式

让咱们看看Fragment 中“ViewBinding”的用法。咱们有一个布局资源profile.xml。View Binding 为布局文件生成的类叫ProfileBinding,传统应用形式如下:

class ProfileFragment : Fragment(R.layout.profile) {        private var viewBinding: ProfileBinding? = null        override fun onViewCreated(view: View, savedState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        viewBinding = ProfileBinding.bind(view)        // Use viewBinding      }        override fun onDestroyView() {         super.onDestroyView()         viewBinding = null      }}

有几点我不太喜爱:

  • 创立和销毁viewBinding的样板代码
  • 如果有很多Fragment,每一个都要拷贝一份雷同的代码
  • viewBinding 属性是可空的,并且可变的,这可不太妙

怎么办呢?用弱小Kotlin来重构它。

Kotlin 委托属性联合ViewBinding

应用Kotlin委托的属性,咱们能够重用局部代码并简化工作(不明确委托属性的,能够看我(译者)以前的文章:一文彻底搞懂Kotlin中的委托),我用它来简化·ViewBinding的用法。用一个委托包装了ViewBinding`的创立和销毁。

class FragmentViewBindingProperty<T : ViewBinding>(    private val viewBinder: ViewBinder<T>) : ReadOnlyProperty<Fragment, T> {    private var viewBinding: T? = null    private val lifecycleObserver = BindingLifecycleObserver()    @MainThread    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {        checkIsMainThread()        this.viewBinding?.let { return it }        val view = thisRef.requireView()        thisRef.viewLifecycleOwner.lifecycle.addObserver(lifecycleObserver)        return viewBinder.bind(view).also { vb -> this.viewBinding = vb }    }    private inner class BindingLifecycleObserver : DefaultLifecycleObserver {        private val mainHandler = Handler(Looper.getMainLooper())        @MainThread        override fun onDestroy(owner: LifecycleOwner) {            owner.lifecycle.removeObserver(this)            viewBinding = null        }    }}/** * Create new [ViewBinding] associated with the [Fragment][this] */@Suppress("unused")inline fun <reified T : ViewBinding> Fragment.viewBinding(): ReadOnlyProperty<Fragment, T> {    return FragmentViewBindingProperty(DefaultViewBinder(T::class.java))}

而后,应用咱们定义的委托来重构ProfileFragment:

class ProfileFragment : Fragment(R.layout.profile) {    private val viewBinding: ProfileBinding by viewBinding()    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        // Use viewBinding    }}

很好,咱们去掉了创立和销毁ViewBinding的样板代码,当初只须要申明一个委托属性就能够了,是不是简略了?然而当初还有点问题。

问题来了

在重构之后,onDestroyView 须要清理掉viewBinding中的View。

class ProfileFragment() : Fragment(R.layout.profile) {    private val viewBinding: ProfileBinding by viewBinding()    override fun onDestroyView() {        super.onDestroyView()        // Clear data in views from viewBinding        // ViewBinding inside viewBinding is null    }}

然而,后果是,我失去的在委托属性内对ViewBinding的援用为null。起因是Fragment的ViewLifecycleOwner告诉更新lifecycle的ON_DESTROY事件机会,该事件产生在Fragment.onDestroyView()之前。这就是为什么我仅在主线程上的所有操作实现后才须要革除viewBinding。能够应用Handler.post实现。批改如下:

class FragmentViewBindingProperty<T : ViewBinding>(    private val viewBinder: ViewBinder<T>) : ReadOnlyProperty<Fragment, T> {    private var viewBinding: T? = null    private val lifecycleObserver = BindingLifecycleObserver()    @MainThread    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {        checkIsMainThread()        this.viewBinding?.let { return it }        val view = thisRef.requireView()        thisRef.viewLifecycleOwner.lifecycle.addObserver(lifecycleObserver)        return viewBinder.bind(view).also { vb -> this.viewBinding = vb }    }    private inner class BindingLifecycleObserver : DefaultLifecycleObserver {        private val mainHandler = Handler(Looper.getMainLooper())        @MainThread        override fun onDestroy(owner: LifecycleOwner) {            owner.lifecycle.removeObserver(this)            // Fragment.viewLifecycleOwner call LifecycleObserver.onDestroy() before Fragment.onDestroyView().            // That's why we need to postpone reset of the viewBinding            mainHandler.post {                viewBinding = null            }        }    }}

这样,就很完满了。

Android的新库ViewBinding是一个去掉我的项目中findViewByid()很好的解决方案,同时它也代替了驰名的Butter Knife。ViewBinding 与Kotlin委托属性的奇妙联合,能够让你的代码更加简洁易读。残缺的代码能够查看github:https://github.com/kirich1409...