DataBinding——使用Kotlin 委托优化
Posted bug樱樱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DataBinding——使用Kotlin 委托优化相关的知识,希望对你有一定的参考价值。
简介
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语言的特性中
- 当某个变量的值可以为 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()
。
也就是说如果我这样写:
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()
之后
相比方式二,方式一的生命周期回调会得更稳定。
拓展
作者:ak
链接:https://juejin.cn/post/7194024942650785852
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓
以上是关于DataBinding——使用Kotlin 委托优化的主要内容,如果未能解决你的问题,请参考以下文章
DataBinding:如何使用 Kotlin 中的泛型创建具有多个 ViewHolder(多个布局)的 Recyclerview
DataBinding 和 LiveData :两种实现(Kotlin 和 Java),不能使 Java impl 工作
使用 Kotlin Multiplatform Mobile (KMM) 的多平台应用程序中的 DataBinding 错误
项目引用Kotlin与databinding,ARouter,Architecture Components冲突解决办法