簡(jiǎn)介
DataBinding 是 Google 在 Jetpack 中推出的一款數(shù)據(jù)綁定的支持庫(kù)变骡,利用該庫(kù)可以實(shí)現(xiàn)在頁(yè)面組件中直接綁定應(yīng)用程序的數(shù)據(jù)源比原。使其維護(hù)起來(lái)更加方便讨惩,架構(gòu)更明確簡(jiǎn)潔睁壁。
啟用DataBinding
DataBinding庫(kù)與 Android Gradle 插件捆綁在一起为狸。無(wú)需聲明對(duì)此庫(kù)的依賴項(xiàng)歼郭,但必須啟用它。
android {
...
buildFeatures {
dataBinding true
}
}
基本使用 DataBinding—官方文檔
常規(guī)用法
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中使用病曾,我們直接通過(guò)inflate(@NonNull LayoutInflater inflater)
創(chuàng)建binding對(duì)象,然后通過(guò)setContentView(View view)
把根部局(binding.root)設(shè)置進(jìn)去
或者我們可以通過(guò)懶加載的方式
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"
}
}
我們通過(guò)by lazy{}
,在首次訪問(wèn)的時(shí)候會(huì)調(diào)用lazy中的代碼塊進(jìn)行初始化漾根;這里我們會(huì)發(fā)現(xiàn)泰涂,在onCreate()
中,我們并沒(méi)有調(diào)用setContentView()
設(shè)置布局辐怕;這是因?yàn)槲覀冊(cè)谑状卧L問(wèn)binding的時(shí)候逼蒙,會(huì)執(zhí)行l(wèi)azy中的DataBindingUtil.setContentView()
,其中就調(diào)用了activity.setContentView()并創(chuàng)建binding對(duì)象返回寄疏;由于我們首次訪問(wèn)是在onCreate()
中是牢,自然就會(huì)在此處設(shè)置布局了。
2陕截、在Fragment中使用
注意內(nèi)存泄漏:
在Activity中使無(wú)需考慮此問(wèn)題
在Fragment中使用時(shí)需要注意在onDestroyView()
的時(shí)候把binding對(duì)象置空驳棱,因?yàn)?strong>Fragment的生命周期和Fragment中View的生命周期是不同步的;而binding綁定的是視圖农曲,當(dāng)視圖被銷毀時(shí)社搅,binding就不應(yīng)該再被訪問(wèn)且能夠被回收,因此乳规,我們需要在onDestroyView()
中將binding對(duì)象置空罚渐; 否則,當(dāng)視圖被銷毀時(shí)驯妄,F(xiàn)ragment繼續(xù)持有binding的引用荷并,就會(huì)導(dǎo)致binding無(wú)法被回收,造成內(nèi)存泄漏青扔。
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版中使用了兩個(gè)binding對(duì)象源织?
因?yàn)樵贙otlin語(yǔ)言的特性中
- 當(dāng)某個(gè)變量的值可以為 null 的時(shí)候翩伪,必須在聲明處的類型后添加
?
來(lái)標(biāo)識(shí)該引用可為空。 - 可重新賦值的變量使用
var
關(guān)鍵字
因此我們需要將Binding對(duì)象聲明為可變的且可為空的谈息;又因?yàn)樵贙otlin中有null
檢測(cè)缘屹,會(huì)導(dǎo)致我們每次使用時(shí)都需要判空或使用安全調(diào)用操作符?.
這樣又會(huì)造成代碼可讀性較差、不必要的判空侠仇、不夠優(yōu)雅轻姿,用起來(lái)也麻煩。
然后這里就引出了我們的第二個(gè)對(duì)象逻炊,使用Kotlin的非空斷言運(yùn)算符將它轉(zhuǎn)為非空類型來(lái)使用互亮。
非空斷言運(yùn)算符(
!!
)將任何值轉(zhuǎn)換為非空類型,若該值為空則拋出異常
即解決了判空問(wèn)題余素,又可以將binding對(duì)象用val
聲明為不可變的豹休。
使用Kotlin屬性委托來(lái)優(yōu)化
像上文中創(chuàng)建和銷毀binding對(duì)象,如果每次使用都要寫一遍這樣的模板代碼桨吊,就會(huì)變得很繁瑣威根,我們通知將之封裝到Activity / Fragment的基類(Base)中,在對(duì)應(yīng)的生命周期中創(chuàng)建或銷毀视乐;但是會(huì)依賴于基類洛搀,往往項(xiàng)目中基類做的事情太多了;如果我們只是需要這個(gè)binding佑淀,就會(huì)繼承到一些不需要的功能姥卢。
像這樣的情況我們希望將它進(jìn)一步優(yōu)化,將之解耦出來(lái)作為一個(gè)頁(yè)面的組件存在渣聚,可以理解為做成一個(gè)支持熱插拔的組件独榴,這里就需要用到委托來(lái)實(shí)現(xiàn)。
關(guān)于Kotlin委托機(jī)制請(qǐng)看:委托屬性 - Kotlin 語(yǔ)言中文站 (kotlincn.net)
1奕枝、Activity中的委托
ContentViewBindingDelegate.kt
/**
* 懶加載DataBinding的委托棺榔,
* 調(diào)用 [Activity.setContentView],設(shè)置[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拓展函數(shù)來(lái)使用
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通過(guò)by
關(guān)鍵字委托給了其中定義的Activity的拓展函數(shù)contentView()
,此函數(shù)返回我們的委托類ContentViewBindingDelegate
,每次訪問(wèn)binding時(shí)谭梗,會(huì)執(zhí)行委托類中的getValue()
忘晤;當(dāng)我們?cè)?code>onCreate()中首次訪問(wèn)時(shí),委托中的binding為空激捏,會(huì)去創(chuàng)建binding對(duì)象设塔,并調(diào)用了Activity.setContentView()
;此后每次訪問(wèn),binding不再為空远舅,直接返回了binding闰蛔。
2痕钢、Fragment中的委托
避坑:Fragment的viewLifecycleOwner 會(huì)在 Fragment的
onDestroyView()
之前執(zhí)行onDestroy()
。
也就是說(shuō)如果我這樣寫:
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 {
//會(huì)在Fragment的`onDestroyView()` 之前執(zhí)行
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
return binding!!
}
}
那么binding會(huì)在Fragment的onDestroyView()
之前置空任连,當(dāng)我們onDestroyView()
訪問(wèn)了binding,會(huì)再給binding賦值例诀。
因此我們需要實(shí)現(xiàn)在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随抠,設(shè)置生命周期所有者并返回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
}
}
這種方式通過(guò)注冊(cè)FragmentManager.FragmentLifecycleCallbacks
來(lái)監(jiān)聽(tīng)Fragment的生命周期變化繁涂,其中的onFragmentViewDestroyed()
會(huì)在Fragment從 FragmentManager 對(duì)Fragment.onDestroyView()
的調(diào)用返回之后調(diào)用拱她。
方式二
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,設(shè)置生命周期所有者并返回binding爆土。
*/
fun <F : Fragment, T : ViewDataBinding> Fragment.binding(): FragmentViewBindingDelegate<F, T> =
FragmentViewBindingDelegate()
這種方式通過(guò)在viewLifecycleOwner
的onDestroy()
時(shí)使用主線程Handler.post
將binding置空的任務(wù)添加到消息隊(duì)列中,而viewLifecycleOwner
的onDestroy()
和Fragment的onDestroyView()
方法是在同一個(gè)消息中被處理的:
在performDestroyView()
中:
因此诸蚕,我們
post
的Runnable自然會(huì)在onDestroyView()
之后
相比方式二步势,方式一的生命周期回調(diào)會(huì)得更穩(wěn)定。