DataBinding——使用Kotlin 委托优化

藏宝库编辑 2024-9-7 14:51:22 35 0 来自 中国
简介

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()。
1.png 也就是说假如我这样写:
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)
作者:ak
链接:https://juejin.cn/post/7194024942650785852
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-11-23 20:50, Processed in 0.223301 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表