Android開(kāi)發(fā)Viewbinding委托實(shí)例詳解
背景
前一陣子我們?cè)谑褂胿iewbinding的委托的時(shí)候碰到了點(diǎn)crash問(wèn)題,然后發(fā)現(xiàn)了一個(gè)比較有意思的解決方案,就和大家展開(kāi)聊聊。
另外一點(diǎn)就是我后面打算將kotlin extensions這個(gè)插件統(tǒng)一移除掉。
估計(jì)大家應(yīng)該對(duì)Viewbinding的委托應(yīng)該都有一定的了解,好幾個(gè)大佬分享過(guò)類(lèi)似的文章,但是大佬們的代碼貌似也有一陣子都沒(méi)有維護(hù)了,所以我找到了一個(gè)外國(guó)大佬寫(xiě)的倉(cāng)庫(kù),其實(shí)應(yīng)該算是一個(gè)相對(duì)來(lái)說(shuō)比較穩(wěn)定的庫(kù)了,而且也一直處于一個(gè)持續(xù)更新迭代的狀態(tài)。
倉(cāng)庫(kù)地址 ViewBindingPropertyDelegate
從Crash到有意思的源碼
委托模式是軟件設(shè)計(jì)模式中的一項(xiàng)基本技巧。在委托模式中,有兩個(gè)對(duì)象參與處理同一個(gè)請(qǐng)求,接受請(qǐng)求的對(duì)象將請(qǐng)求委托給另一個(gè)對(duì)象來(lái)處理。
Kotlin 直接支持委托模式,更加優(yōu)雅,簡(jiǎn)潔。Kotlin 通過(guò)關(guān)鍵字 by 實(shí)現(xiàn)委托。
上述是kotlin對(duì)于委托的釋義,Viewbinding委托就是把生成Viewbinding實(shí)例的過(guò)程交給委托類(lèi)去完成,然后讓使用方可以忽略掉其中的細(xì)節(jié),是一種非常好玩的模式了。
但是由于Viewbinding的特殊性,它其實(shí)就會(huì)和當(dāng)前的lifecycle綁定在一起。因?yàn)槲覀円阡N(xiāo)毀的情況下把實(shí)例重置為空。否則當(dāng)我們頁(yè)面重新生成的情況下,就會(huì)出現(xiàn)view并不是當(dāng)前的頁(yè)面的困擾。
作者在定義的時(shí)候就將Viewbinding委托獲取的實(shí)例定義為了非空,這里我和我的同事其實(shí)是存在一些分歧的,我認(rèn)為非空其實(shí)挺合理的,但是對(duì)方并不認(rèn)為。
恰巧這種空非空的問(wèn)題,在實(shí)際的使用中就出現(xiàn)了很多不可預(yù)期的crash問(wèn)題。比如說(shuō)在一個(gè)異步操作中獲取viewbinding實(shí)例然后進(jìn)行賦值操作,就會(huì)出現(xiàn)空指針異常。另外由于使用的是lifecycle的頁(yè)面銷(xiāo)毀方法,如果我們復(fù)寫(xiě)了銷(xiāo)毀方法之后在設(shè)置這個(gè)值,也會(huì)出現(xiàn)崩潰問(wèn)題。
上述問(wèn)題我在幾個(gè)我之前參考的庫(kù)中其實(shí)都發(fā)現(xiàn)了對(duì)應(yīng)的問(wèn)題。我參考了Binding,還有之前彭旭說(shuō)的那個(gè)也有類(lèi)似的情況。
另外在fragment中,其實(shí)問(wèn)題尤其的明顯。因?yàn)槲覀兒芏鄷r(shí)候使用的fragment相關(guān)的LifecycleOwner是fragment本身,但是Android官方其實(shí)推薦我們使用的是fragment內(nèi)部的view相關(guān)的LifecycleOwner。因?yàn)閒ragment相比較于activity,存在的問(wèn)題就是多了幾個(gè)生命周期,比如createView,和onDestroyView。其中出現(xiàn)最多問(wèn)題的也就是onDestroyView和onDestroy。
有趣的代碼
接下來(lái)我們看下這個(gè)作者是如何解決這些奇奇怪怪的問(wèn)題的哦。
private class FragmentViewBindingProperty<in F : Fragment, out T : ViewBinding>(
private val viewNeedInitialization: Boolean,
viewBinder: (F) -> T,
onViewDestroyed: (T) -> Unit,
) : LifecycleViewBindingProperty<F, T>(viewBinder, onViewDestroyed) {
private var fragmentLifecycleCallbacks: FragmentManager.FragmentLifecycleCallbacks? = null
private var fragmentManager: Reference<FragmentManager>? = null
// 賦值操作
override fun getValue(thisRef: F, property: KProperty<*>): T {
val viewBinding = super.getValue(thisRef, property)
registerFragmentLifecycleCallbacksIfNeeded(thisRef)
return viewBinding
}
private fun registerFragmentLifecycleCallbacksIfNeeded(fragment: Fragment) {
if (fragmentLifecycleCallbacks != null) return
val fragmentManager = fragment.parentFragmentManager.also { fm ->
this.fragmentManager = WeakReference(fm)
}
fragmentLifecycleCallbacks = ClearOnDestroy(fragment).also { callbacks ->
fragmentManager.registerFragmentLifecycleCallbacks(callbacks, false)
}
}
override fun isViewInitialized(thisRef: F): Boolean {
if (!viewNeedInitialization) return true
if (thisRef !is DialogFragment) {
return thisRef.view != null
} else {
return super.isViewInitialized(thisRef)
}
}
override fun clear() {
super.clear()
fragmentManager?.get()?.let { fragmentManager ->
fragmentLifecycleCallbacks?.let(fragmentManager::unregisterFragmentLifecycleCallbacks)
}
fragmentManager = null
fragmentLifecycleCallbacks = null
}
override fun getLifecycleOwner(thisRef: F): LifecycleOwner {
try {
return thisRef.viewLifecycleOwner
} catch (ignored: IllegalStateException) {
error("Fragment doesn't have view associated with it or the view has been destroyed")
}
}
// 有意思的代碼
private inner class ClearOnDestroy(
fragment: Fragment
) : FragmentManager.FragmentLifecycleCallbacks() {
private var fragment: Reference<Fragment> = WeakReference(fragment)
override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) {
// Fix for destroying view for case with issue of navigation
if (fragment.get() === f) {
postClear()
}
}
}
}
從上述代碼上我們可以看出來(lái),其中獲取的LifecycleOwner就是我上文說(shuō)的viewLifecycleOwner。這個(gè)就其實(shí)已經(jīng)非常精彩了。
另外我們可以看下他在內(nèi)部定義了ClearOnDestroy這個(gè)類(lèi),然后當(dāng)onFragmentDestroyed觸發(fā)的時(shí)候調(diào)用postClear方法。而這個(gè)方法就是解決當(dāng)我們?cè)贒estroyed中還執(zhí)行了ViewBinding內(nèi)的對(duì)象的操作的空指針問(wèn)題。
經(jīng)典面試題的真實(shí)使用場(chǎng)景,Handler.post執(zhí)行。很多人覺(jué)得Handler相關(guān)的面試題都是八股文,這次我們就通過(guò)這個(gè)真是場(chǎng)景來(lái)給大家說(shuō)說(shuō)這個(gè)有意思的問(wèn)題。
首先從onFragmentDestroyed方法會(huì)執(zhí)行在Fragment本身的onDestroyView之前,原來(lái)我們會(huì)在這個(gè)方法下執(zhí)行引用清空的操作。然后當(dāng)onDestroyView執(zhí)行的時(shí)候就會(huì)出現(xiàn)空指針異常了。那么Lifecycle有沒(méi)有提供一個(gè)在onDestroyView之后的方法呢?我們是不是可以考慮自己造一個(gè)呢?面試中,我們知道所有生命周期方法都是有主線(xiàn)程Handler來(lái)負(fù)責(zé)調(diào)度的,這也就是說(shuō)活我么可以把生命周期方法認(rèn)為就是一個(gè)Message,當(dāng)onFragmentDestroyed執(zhí)行的時(shí)候,onDestroyView也已經(jīng)被添加到主線(xiàn)程的MessageQueue中,這個(gè)時(shí)候我們?cè)趐ost一個(gè)runnable,那么他的排序規(guī)則上來(lái)說(shuō),就必然在onDestroyView之后了。
另外一些有意思的地方
這個(gè)庫(kù)另外一個(gè)優(yōu)點(diǎn)就是他同時(shí)支持反射和非反射的寫(xiě)法。同時(shí)也支持了Activity,F(xiàn)ragment,View,F(xiàn)ragmentDialog,ViewHolder等等。反射寫(xiě)法是基于非反射寫(xiě)法的,所以也保證了底層庫(kù)的一致性。
//非反射寫(xiě)法
private val viewBinding by viewBinding(ViewProfileBinding::bind)
//反射寫(xiě)法
private val viewBinding: ItemProfileBinding by viewBinding()
同時(shí)他的反射相關(guān)的混淆配置文件也非常有意思。
allowoptimization 指定對(duì)象可能會(huì)被優(yōu)化,即使他們被keep選項(xiàng)保留。所指定對(duì)象可能會(huì)被改變(優(yōu)化步驟),但可能不會(huì)被混淆或者刪除。該修飾符只對(duì)實(shí)現(xiàn)異常要求有用。
-keep,allowoptimization class * implements androidx.viewbinding.ViewBinding {
public static *** bind(android.view.View);
public static *** inflate(...);
}
它只會(huì)keep實(shí)現(xiàn)了ViewBinding的類(lèi)的bind和inflate方法。因?yàn)閂iewBinding會(huì)將所有的xml轉(zhuǎn)化成一個(gè)類(lèi)實(shí)例,如果不刪除掉沒(méi)有實(shí)際被調(diào)用的類(lèi)的情況下就會(huì)導(dǎo)致dex包變大,大家對(duì)于包體積優(yōu)化都是有追求的嗎。然后用了-keep,allowoptimization,這樣在混淆的代碼優(yōu)化過(guò)程中就可以刪除掉沒(méi)有被調(diào)用的ViewBinding類(lèi)了。
結(jié)尾
本次內(nèi)卷到此結(jié)束。但是又是一個(gè)老生常談的話(huà)題,一個(gè)開(kāi)源庫(kù)還是要持續(xù)的進(jìn)行迭代和解決問(wèn)題才能持續(xù)變好,而不是一次性的工作。擁抱變化的代碼世界,解決一些奇奇怪怪的問(wèn)題,都是挺好玩的,更多關(guān)于Android開(kāi)發(fā)Viewbinding委托的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android編程實(shí)現(xiàn)ListView內(nèi)容無(wú)限循環(huán)顯示的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)ListView內(nèi)容無(wú)限循環(huán)顯示的方法,通過(guò)繼承Adapter類(lèi)實(shí)現(xiàn)ListView中的數(shù)據(jù)無(wú)限循環(huán)顯示功能,需要的朋友可以參考下2017-06-06
Android 使用mediaplayer播放res/raw文件夾中的音樂(lè)的實(shí)例
這篇文章主要介紹了Android 使用mediaplayer播放res/raw文件夾中的音樂(lè)的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04
Android Button點(diǎn)擊事件的四種實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Android Button點(diǎn)擊事件的四種實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android中l(wèi)istview嵌套scrollveiw沖突的解決方法
這篇文章主要為大家詳細(xì)介紹了Android中l(wèi)istview嵌套scrollveiw沖突的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
Android5.0新特性詳解之全新的動(dòng)畫(huà)
這篇文章主要介紹了Android5.0新特性詳解之全新的動(dòng)畫(huà)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
基于Android實(shí)現(xiàn)一個(gè)常用的布局吸頂效果
這篇文章給大家介紹一個(gè)布局吸頂效果,一般出現(xiàn)在內(nèi)容較長(zhǎng)頁(yè)面還嵌套著分類(lèi)頁(yè)面的情況,比如電商的詳情頁(yè)嵌套分類(lèi),在頁(yè)面滑動(dòng)到tab的時(shí)候我們希望tab還能保留在頁(yè)面頂部而不被頂上去,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-09-09

