简介
DataBinding 是 Google 在 Jetpack 中推出的一款数据绑定的支撑库,利用该库能够实现在页面组件中直接绑定应用程序的数据源。使其保护起来愈加便利,架构更明确简洁。
启用DataBinding
DataBinding库与 Android Gradle 插件绑缚在一起。无需声明对此库的依赖项,但有必要启用它。
android {
...
buildFeatures {
dataBinding true
}
}
基本运用 DataBinding—官方文档
惯例用法
1、在Activity中运用
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvName.text = "ak"
}
}
在Activity中运用,咱们直接经过inflate(@NonNull LayoutInflater inflater)
创立binding目标,然后经过setContentView(View view)
把根部局(binding.root)设置进去
或者咱们能够经过懒加载的办法
class MainActivity : AppCompatActivity() {
private val binding: ActivityMainBinding by lazy { DataBindingUtil.setContentView(this,R.layout.activity_main) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.tvName.text = "ak"
}
}
咱们经过by lazy{}
,在初次拜访的时分会调用lazy中的代码块进行初始化;这儿咱们会发现,在onCreate()
中,咱们并没有调用setContentView()
设置布局;这是由于咱们在初次拜访binding的时分,会履行lazy中的DataBindingUtil.setContentView()
,其中就调用了activity.setContentView()并创立binding目标回来;由于咱们初次拜访是在onCreate()
中,天然就会在此处设置布局了。
2、在Fragment中运用
留意内存走漏:
在Activity中使无需考虑此问题
在Fragment中运用时需求留意在onDestroyView()
的时分把binding目标置空,由于Fragment的生命周期和Fragment中View的生命周期是不同步的;而binding绑定的是视图,当视图被毁掉时,binding就不应该再被拜访且能够被收回,因而,咱们需求在onDestroyView()
中将binding目标置空;
不然,当视图被毁掉时,Fragment继续持有binding的引用,就会导致binding无法被收回,形成内存走漏。
Java版
public class BlankFragmentOfJava extends Fragment {
private FragmentBlankBinding binding;
public BlankFragmentOfJava() {
super(R.layout.fragment_blank);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
binding = FragmentBlankBinding.bind(view);
binding.tvName.setText("ak");
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
Kotlin版
class BlankFragment : Fragment(R.layout.fragment_blank) {
private var _binding: FragmentBlankBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.tvName
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
为什么Kotlin版中运用了两个binding目标?
由于在Kotlin言语的特性中
因而咱们需求将Binding目标声明为可变的且可为空的;又由于在Kotlin中有null
检测,会导致咱们每次运用时都需求判空或运用安全调用操作符?.
这样又会形成代码可读性较差、不必要的判空、不行优雅,用起来也费事。
然后这儿就引出了咱们的第二个目标,运用Kotlin的非空断语运算符将它转为非空类型来运用。
非空断语运算符(
!!
)将任何值转换为非空类型,若该值为空则抛出反常
即处理了判空问题,又能够将binding目标用val
声明为不可变的。
运用Kotlin特点托付来优化
像上文中创立和毁掉binding目标,假如每次运用都要写一遍这样的模板代码,就会变得很繁琐,咱们通知将之封装到Activity / Fragment的基类(Base)中,在对应的生命周期中创立或毁掉;可是会依赖于基类,往往项目中基类做的工作太多了;假如咱们只是需求这个binding,就会继承到一些不需求的功用。
像这样的状况咱们期望将它进一步优化,将之解耦出来作为一个页面的组件存在,能够理解为做成一个支撑热插拔的组件,这儿就需求用到托付来实现。
关于Kotlin托付机制请看:托付特点 – Kotlin 言语中文站 (kotlincn.net)
1、Activity中的托付
ContentViewBindingDelegate.kt
/**
* 懒加载DataBinding的托付,
* 调用 [Activity.setContentView],设置[androidx.lifecycle.LifecycleOwner]并回来绑定。
*/
class ContentViewBindingDelegate<in A : AppCompatActivity, out T : ViewDataBinding>(
@LayoutRes private val layoutRes: Int
) {
private var binding: T? = null
operator fun getValue(activity: A, property: KProperty<*>): T {
binding?.let { return it } //不为空,直接回来
binding = DataBindingUtil.setContentView<T>(activity, layoutRes).apply {
lifecycleOwner = activity
}
return binding!!
}
}
//作为Activity拓宽函数来运用
fun <A : AppCompatActivity, T : ViewDataBinding> AppCompatActivity.contentView(
@LayoutRes layoutRes: Int
): ContentViewBindingDelegate<A, T> = ContentViewBindingDelegate(layoutRes)
运用示例
class MainActivity : AppCompatActivity() {
private val binding: ActivityMainBinding by contentView(R.layout.activity_main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.tvName.text = "ak"
}
}
首要咱们Activity中的binding经过by
关键字托付给了其中界说的Activity的拓宽函数contentView()
,此函数回来咱们的托付类ContentViewBindingDelegate
,每次拜访binding时,会履行托付类中的getValue()
;当咱们在onCreate()
中初次拜访时,托付中的binding为空,会去创立binding目标,并调用了Activity.setContentView()
;尔后每次拜访,binding不再为空,直接回来了binding。
2、Fragment中的托付
避坑:Fragment的viewLifecycleOwner 会在 Fragment的
onDestroyView()
之前履行onDestroy()
。
也就是说假如我这样写:
class FragmentViewBindingDelegate<in R : Fragment, out T : ViewDataBinding> {
private var binding: T? = null
operator fun getValue(fragment: R, property: KProperty<*>): T {
binding?.let { return it } //不为空,直接回来
binding = DataBindingUtil.bind<T>(fragment.requireView())?.also {
it.lifecycleOwner = fragment.viewLifecycleOwner
}
fragment.viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
//会在Fragment的`onDestroyView()` 之前履行
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
return binding!!
}
}
那么binding会在Fragment的onDestroyView()
之前置空,当咱们onDestroyView()
拜访了binding,会再给binding赋值。
因而咱们需求实现在onDestroyView()
之后再将binding置空
办法一(引荐)
class FragmentViewBindingDelegate<in F : Fragment, out T : ViewDataBinding> {
private var binding: T? = null
operator fun getValue(fragment: F, property: KProperty<*>): T {
binding?.let { return it }
fragment.view ?: throw IllegalArgumentException("The fragment view is empty or has been destroyed")
binding = DataBindingUtil.bind<T>(fragment.requireView())?.also {
it.lifecycleOwner = fragment.viewLifecycleOwner
}
fragment.parentFragmentManager.registerFragmentLifecycleCallbacks(Clear(fragment), false)
return binding!!
}
inner class Clear(private val thisRef: F) : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewDestroyed(fm: FragmentManager, f: Fragment) {
if (thisRef === f) {
binding = null
fm.unregisterFragmentLifecycleCallbacks(this)
}
}
}
}
/**
* 绑定fragment布局View,设置生命周期所有者并回来binding。
*/
fun <F : Fragment, T : ViewDataBinding> Fragment.binding(): FragmentViewBindingDelegate<F, T> =
FragmentViewBindingDelegate()
运用示例
class BlankFragment : Fragment(R.layout.fragment_blank) {
private val binding: FragmentBlankBinding by binding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.tvName
}
}
这种办法经过注册FragmentManager.FragmentLifecycleCallbacks
来监听Fragment的生命周期变化,其中的onFragmentViewDestroyed()
会在Fragment从 FragmentManager 对Fragment.onDestroyView()
的调用回来之后调用。
办法二
class FragmentViewBindingDelegate<in F : Fragment, out T : ViewDataBinding>() {
private var binding: T? = null
operator fun getValue(fragment: F, property: KProperty<*>): T {
binding?.let { return it }
fragment.view ?: throw IllegalArgumentException("The fragment view is empty or has been destroyed")
binding = DataBindingUtil.bind<T>(fragment.requireView())?.apply {
lifecycleOwner = fragment.viewLifecycleOwner
}
fragment.viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
private val mainHandler = Handler(Looper.getMainLooper())
override fun onDestroy(owner: LifecycleOwner) {
mainHandler.post { binding = null }
}
})
return binding!!
}
}
/**
* 绑定fragment布局View,设置生命周期所有者并回来binding。
*/
fun <F : Fragment, T : ViewDataBinding> Fragment.binding(): FragmentViewBindingDelegate<F, T> =
FragmentViewBindingDelegate()
这种办法经过在viewLifecycleOwner
的onDestroy()
时运用主线程Handler.post
将binding置空的使命增加到音讯队列中,而viewLifecycleOwner
的onDestroy()
和Fragment的onDestroyView()
办法是在同一个音讯中被处理的:
在performDestroyView()
中:
因而,咱们
post
的Runnable天然会在onDestroyView()
之后
比较办法二,办法一的生命周期回调会得更稳定。
拓宽
- DataBinding—官方文档
- 托付特点 – Kotlin 言语中文站 (kotlincn.net)