关于android:View-Binding-与Kotlin委托属性的巧妙结合告别垃圾代码

50次阅读

共计 3660 个字符,预计需要花费 10 分钟才能阅读完成。

文章首发于公众号「技术最 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…

正文完
 0