Kotlin 升级到1.4.20之后,正式废弃了 kotlin-android-extensions
插件,将其Parcelable相关功能迁移到新插件kotlin-parcelize
,并推荐使用**ViewBinding
**进行视图绑定。
JakeWharton大神的ButterKnife也早已停止维护,也推荐迁移到
ViewBinding
kotlin-android-extensions
插件主要提供了两个很方便的功能:
这个插件优点就是用起来特别舒畅,直接用控件id就可以搞定了。主要缺点也是比较明显的,否则也不会被放弃了:
为了解决这些痛点,Android官方推出了ViewBinding
解决方案,当不同layout文件,id不一致时,会在属性上声明为@Nullable
,从而提供了可空信息
官方的viewBinding虽然解决了上述的问题,缺也存在了明显的缺陷:使用过于繁琐!!,而且在fragment中使用时,还得额外在onDestroyView
中释放binding
,防止造成内存泄漏,会产生大量无意义的重复代码!!
这里只展示简单的fragment使用,介绍问题。更多使用细节可以查阅官方指南或Exploring Android View Binding in Depth(这里有篇很详细的译文)
ViewBinding
从Android Studio 3.6开始,就是内置在Gradle插件中了,开发者不需要额外添加库来开启,在模块级的build.gradle文件
// Android Studio 3.6
android {
viewBinding {
enabled = true
}
}
// Android Studio 4.0
android {
buildFeatures {
viewBinding = true
}
}
启用视图绑定功能后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。每个绑定类均包含对根视图以及具有 ID 的所有视图的引用。绑定类的名称生成方式为:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”一词。如
fragment_binding
则为FragmentBindingBinding
make project
之后就会生成绑定类文件,可在模块级别的build/generated/data_binding_base_class_source_out/
目录下找到
在fragment
中使用ViewBinding
,常见问题就是内存的泄漏,因为fragment
的生命周期长于其视图的生命周期。所以得在onDestroyView
中释放视图,如使用指南中的代码所示:
...
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
...
每次都得手动进行释放。如果只有简单几个页面,倒也不是什么问题,但当项目变大时,就得不断重复这些繁琐的代码,而且activity的使用方式也是不同的,这就造成项目中会生成大量重复代码
这里讲两种可行性的思路,一种是改动基类进行封装,另一种则是借用kotlin的委托属性功能结合反射进行封装(但只适合kotlin,使用便捷)
这里就只提供了一个fragment的思路,其他的可以类似实现,相对也是比较简单的。
abstract class BaseFragment(layoutId: Int) : Fragment(layoutId) {
private var _binding: T? = null
val binding
get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = initBinding(view)
}
abstract fun initBinding(view: View): T
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}
class MainFragment :BaseFragment(R.layout.fragment_main) {
override fun initBinding(view: View): FragmentMainBinding = FragmentMainBinding.bind(view)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvContent = "嗨起来"
}
}
对kotlin的委托属性不了解的可以查看一文彻底搞懂Kotlin中的委托
这里也只提供了一个针对fragment的思路,其他的可以类似实现。
完成之后使用直接一个by bind()
即可:
class BindingFragment:Fragment(R.layout.fragment_binding) {
private val binding:FragmentBindingBinding by bind()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.e("binding","${binding.tvContent.text}")
binding.tvContent.text = "new value"
}
}
首先实现扩展方法,返回直接委托对象FragmentBindingDelegate
inline fun Fragment.bind(): FragmentBindingDelegate {
return FragmentBindingDelegate(T::class.java, this)
}
委托对象代码如下:
class FragmentBindingDelegate(classes: Class, fragment: Fragment) :
ReadOnlyProperty {
private var binding: T? = null
private val bindViewMethod by lazy { classes.getMethod("bind", View::class.java) }
init {
fragment.lifecycle.donOnDestroy { release() }
}
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
return binding ?: let {
Log.e("binding","generate value")
(bindViewMethod.invoke(null, thisRef.view) as T).also { binding = it }
}
}
private fun release() {
Log.e("binding","release binding")
binding = null
}
}
binding
属性,在不为null时,直接返回该对象:return binding
binding
属性还未赋值的时候,反射并调用ViewBinding
的bind(view)
方法:classes.getMethod("bind", View::class.java)
,并赋值给binding
属性ON_DESTROY
的时候释放持有的binding
对象: fragment.lifecycle.donOnDestroy { release() }
Lifecycle的扩展函数如下,在ON_DESTROY
事件时释放资源并移除自身监听
inline fun Lifecycle.donOnDestroy(crossinline destroyed: () -> Unit) {
addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
destroyed()
source.lifecycle.removeObserver(this)
}
}
})
}
这种实现方案思路也是比较清晰和简单的,但因为是使用了委托by
,所以必须至少有一个地方使用了binding
值,才会触发一系列的赋值操作!!所以如果你使用了这种方案去实现activity
,并将setContentView()
也封装进委托方法里,但没有任何地方触发binding
操作,就不会将view
给设置进去
如果有其他更好的方案,强烈欢迎指出!!