简介

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的生命周期和FragmentView的生命周期是不同步的;而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言语的特性中

  • 当某个变量的值能够为 null 的时分,有必要在声明处的类型后增加 ? 来标识该引用可为空。
  • 可从头赋值的变量运用 var 关键

因而咱们需求将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()

DataBinding——使用Kotlin 委托优化

也就是说假如我这样写:

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()

这种办法经过在viewLifecycleOwneronDestroy()时运用主线程Handler.post将binding置空的使命增加到音讯队列中,而viewLifecycleOwneronDestroy()和Fragment的onDestroyView()办法是在同一个音讯中被处理的:

DataBinding——使用Kotlin 委托优化

performDestroyView()中:

DataBinding——使用Kotlin 委托优化

因而,咱们post的Runnable天然会在onDestroyView()之后

比较办法二,办法一的生命周期回调会得更稳定。

拓宽

  • DataBinding—官方文档
  • 托付特点 – Kotlin 言语中文站 (kotlincn.net)