本文來(lái)自微信公眾號(hào):ByteCode (ID:ByteCode1024),作者:程序員 DHL
習(xí)慣性的每天都會(huì)打開(kāi) medium 看一下技術(shù)相關(guān)的內(nèi)容,偶然看到一位大佬分享和 Android Lifecycle 相關(guān)的面試題,覺(jué)得非常的有價(jià)值。
在 Android 開(kāi)發(fā)中 Android Lifecycle 是非常重要的知識(shí)點(diǎn)。但是不幸的是,我發(fā)現(xiàn)很多新的 Android 開(kāi)發(fā)對(duì) Android Lifecycle 不是很了解,導(dǎo)致在開(kāi)發(fā)中遇到很多奇怪的問(wèn)題。
分享這些面試題,不僅僅是為了通過(guò)面試,更是為了讓 Android 開(kāi)發(fā)者基礎(chǔ)更加的扎實(shí),防止在開(kāi)發(fā)中遇到很多奇怪的問(wèn)題。
面試題一:Launch Fragment by Default
問(wèn)題:
花幾秒鐘思考一下,下面的代碼有什么問(wèn)題。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) supportFragmentManager .beginTransaction() .replace(R.id.container, MainFragment()) .commit() } }
錯(cuò)誤的回答
沒(méi)有使用 KTX 添加 Fragment
應(yīng)該使用 .add 而不是 .replace
正確的回答
如果 Activity 因意外被殺死并被恢復(fù),會(huì)再次執(zhí)行 onCreate () 方法,創(chuàng)建新的 Fragment,因此在棧中會(huì)存在 2 個(gè) Fragment。在 Fragment 上的任何操作都可能被執(zhí)行兩次,這將會(huì)導(dǎo)致出現(xiàn)奇怪的問(wèn)題。
為了防止 Activity 因意外被殺死而恢復(fù),導(dǎo)致添加新的 Fragment,所以我們可以使用 stateInstanceState == null 作為判斷條件,防止添加新的 Fragment,因此我們可以將上面的代碼簡(jiǎn)單修改一下。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (savedInstanceState == null) { supportFragmentManager .beginTransaction() .replace(R.id.container, MainFragment()) .commit() } } }
面試題二:Create Fragment with Constructor
問(wèn)題:
如果往 Fragment 構(gòu)造函數(shù)中添加參數(shù),花幾秒鐘思考一下,下面的代碼會(huì)有什么問(wèn)題?
supportFragmentManager .beginTransaction() .replace(R.id.container, MainFragment()) .commit() class MainFragment(private val repository: Repository): Fragment() { }
錯(cuò)誤的回答
我們可以使用 .replace (R.id.container, MainFragment (repository)) 方法來(lái)代替
它不會(huì)編譯和運(yùn)行
正確的回答
我們不應(yīng)該直接用帶參數(shù)的構(gòu)造函數(shù)實(shí)例化任何 Fragment (),如果想使用帶參數(shù)的構(gòu)造函數(shù)實(shí)例化 Fragment (),可以使用 FragmentFactory 解決這個(gè)問(wèn)題,這是在 AndriodX Fragment 1.2.0 中新增加的 API,詳情可以查看另外一篇文章 Google 建議使用這些 Fragment 的新特性。
如果不使用 AndriodX Fragment 庫(kù),默認(rèn)情況下系統(tǒng)是不支持的,雖然上面的代碼可以正常編譯運(yùn)行,但是在運(yùn)行過(guò)程當(dāng)中,因?yàn)榕渲酶模瑢?dǎo)致在銷毀恢復(fù)的過(guò)程中會(huì)崩潰,錯(cuò)誤信息如下所示。
Caused by: java.lang.NoSuchMethodException: MainFragment.<init>
這是因?yàn)橄到y(tǒng)需要在某些情況下重新初始化它,比如配置更改,例如設(shè)備被旋轉(zhuǎn)時(shí),導(dǎo)致 Fragment 被銷毀,如果沒(méi)有默認(rèn)空的構(gòu)造函數(shù),系統(tǒng)不知道如何重新初始化 Fragment 實(shí)例。
因此,我們總是需要確保實(shí)例化 Fragment 的時(shí)候有一個(gè)空的構(gòu)造函數(shù)。
面試題三:Instantiate ViewModel Directly
ViewModel 是 Jetpack 架構(gòu)組件成員之一,花幾秒鐘思考一下,下面的代碼會(huì)有什么問(wèn)題?
class MainActivity: AppCompatActivity() { private val viewModel = MainViewModel() } class MainViewModel(): ViewModel { }
錯(cuò)誤回答
沒(méi)有什么問(wèn)題,可以正常編譯運(yùn)行
我們還需要向它注入一些依賴項(xiàng),例如 MainViewModel (repository)
正確回答
我們不應(yīng)該直接實(shí)例化 ViewModel。ViewModel 是 Jetpack 架構(gòu)組件成員之一,意味著它可以在配置更改時(shí)存活,例如設(shè)備旋轉(zhuǎn)時(shí),它比 Activity 有更長(zhǎng)的生命周期。
如果我們?cè)诖a中直接實(shí)例化 ViewModel,盡管它可以工作,但它將會(huì)變成一個(gè)普通的 ViewModel,失去原本擁有的特性。
因此,要實(shí)例化 ViewModel,建議使用 ViewModel KTX,代碼如下所示。
class MainActivity: AppCompatActivity() { private val viewMode:MainViewModel by viewModels() }
by viewModels () 會(huì)在重新啟動(dòng)或從已殺死的進(jìn)程中恢復(fù)時(shí),實(shí)例化一個(gè)新的 ViewModel。如果有配置更改,例如設(shè)備被旋轉(zhuǎn)時(shí),它將檢查 ViewModel 是否已經(jīng)創(chuàng)建,而不重新創(chuàng)建它
ViewModels () 會(huì)根據(jù)需要自動(dòng)注入 SavedInstancestate (例如 Activity 中的 SavedInstanceState 和 Intent),如果我們有其他依賴是通過(guò) Dagger Hilt 注入,它將與 ViewModel 一起使用下面的參數(shù)
@HiltViewModel class MyViewModel @Inject constructor( private val repository: Repository, savedStateHandle: SavedStateHandle ) : ViewModel { }
面試題四:ViewModel as StateRestoration Solution
問(wèn)題:
Jetpack 架構(gòu)組件提供的 ViewModel 的作用是什么?
class MainActivity: AppCompatActivity() { private val viewMode:MainViewModel by viewModels() // Some other Activity Code }
錯(cuò)誤回答
ViewModel 是用于狀態(tài)恢復(fù),例如當(dāng) Activity 被殺死并重新啟動(dòng)時(shí),ViewModel 是用來(lái)幫助恢復(fù)到原始狀態(tài)。
正確回答
ViewModel 實(shí)際上是 google 提供的 Jetpack 架構(gòu)組件之一,它鼓勵(lì) Android 開(kāi)發(fā)者使用 MVVM 設(shè)計(jì)模式。
它還有其它重要的功能,例如設(shè)備旋轉(zhuǎn)時(shí),即使 Activity 和 Fragment 被銷毀,它們各自的 ViewModel 仍會(huì)保留,Google 在 ViewModel 中提供了一個(gè)名為 savedStateHandle 參數(shù),該參數(shù)用于保存和恢復(fù)數(shù)據(jù)。
面試題五:LiveData as State Restoration Solution
問(wèn)題:
Jetpack 架構(gòu)組件提供的 LiveData 的作用是什么。
// Declaring it val liveDataA = MutableLiveData<String>() // Trigger the value change liveDataA.value = someValue
錯(cuò)誤回答:
它的存在是為了確保數(shù)據(jù)在 Activity 的生命周期中存活。當(dāng) Activity 在進(jìn)程銷毀返回時(shí),數(shù)據(jù)將會(huì)自動(dòng)恢復(fù)。
正確回答
LiveData 本身不能在進(jìn)程銷毀中存活。它是一種特殊類型的數(shù)據(jù),根據(jù)觀察者(Activity 或 Fragment)的生命周期來(lái)控制其發(fā)出的值。
ViewModel 在配置變更后仍然存在,所以 ViewModel 內(nèi)部的 LiveData 也一樣。這確保 LiveData 發(fā)射值按照下圖控制。
然而 LiveData 本身不能在進(jìn)程銷毀中存活,當(dāng)內(nèi)存不足時(shí),Activity 被系統(tǒng)殺死,ViewModel 本身也會(huì)被銷毀。
為了解決這個(gè)問(wèn)題,Google 在 ViewModel 中提供了一個(gè)名為 savedStateHandle 參數(shù),該參數(shù)用于保存和恢復(fù)數(shù)據(jù),以便數(shù)據(jù)在進(jìn)程銷毀后繼續(xù)存在。
@HiltViewModel class MyViewModel @Inject constructor( private val repository: Repository, savedStateHandle: SavedStateHandle ) : ViewModel { // Some other ViewModel Code }
它是一種增強(qiáng)的機(jī)制,可以處理 Intent 和 SavedInstanceState,在以前的時(shí)候,這些都是由 Activity 單獨(dú)處理的。
為了確保 Livedata 保存下來(lái),我們可以在 SavedStateHandle 中檢查 Livedata 是否已經(jīng)創(chuàng)建。
val liveData = savedStateHandle.getLiveData<String>(KEY)
類似地,這也適用于 stateFlow,它可以在進(jìn)程銷毀中存活下來(lái)。
val stateFlow = savedStateHandle.getStateFlow<String>(KEY, 0)
因此 LiveData 本身并不是用來(lái)恢復(fù)數(shù)據(jù)的。
面試題六:When is The View Destroyed But Not the Instance
問(wèn)題:
在 Activity 或 Fragment 通常會(huì)有一個(gè)視圖。你能給我提供一個(gè)場(chǎng)景,實(shí)例的視圖被破壞了,但實(shí)例(例如 Activity 或 Fragment)還存在。
錯(cuò)誤回答
當(dāng)配置發(fā)生變化時(shí)(例如設(shè)備旋轉(zhuǎn))
當(dāng)內(nèi)存不足時(shí),App 在后臺(tái)運(yùn)行,進(jìn)程會(huì)殺死 App
正確回答
實(shí)際上 Activity 總是與其視圖一起被銷毀。因此,在 Activity 中沒(méi)有 onDestroyView () 生命周期方法。
只有在 Fragment 中有 onDestroyView () 生命周期方法。在大多數(shù)情況下 Fragment 和它的視圖一起被銷毀。
但是通過(guò) Fragment transaction 用一個(gè) Fragment 替換另一個(gè) Fragment 時(shí),棧下面的 Fragment 仍然存在,但是它的視圖被破壞了。
當(dāng)一個(gè) Fragment 被另一個(gè) Fragment 替換時(shí),會(huì)調(diào)用 onDestroyView () 方法,但不會(huì)調(diào)用 onDestroy () 或 onDetect () 方法。
正因?yàn)檫@種復(fù)雜性,在使用 Fragment 時(shí),會(huì)遇到許多奇怪的問(wèn)題。和 Fragment 相關(guān)的問(wèn)題,將會(huì)在后面的文章中分享。
問(wèn)題七:Lifecycle Aware Coroutine
在 App 中使用協(xié)程,如何確保它們的生命周期可感知。
錯(cuò)誤回答
對(duì)于普通視圖,只需在 Activity 或 Fragment 中使用 lifecycleScope,在 ViewModel 中使用 viewModelScope
對(duì)于組合視圖,需要使用 stateFlow 中的 collectAsState () 方法,因?yàn)楫?dāng)可組合函數(shù)不活動(dòng)時(shí),它不會(huì)收集
正確回答
對(duì)于普通視圖,即使 lifecycleScope 是可用的,它在 Activity 或 Fragment 的整個(gè)生命周期中都處于活動(dòng)狀態(tài)。因?yàn)橛袝r(shí)我們希望某些場(chǎng)景只在 onStart () 或 onResume () 之后處于活動(dòng)狀態(tài)。
為此,我們需要在 lifecycleScope 中使用像 repeatOnLifecycle 這樣的 API 提供額外的作用域。
lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.stateFlowValue.collect { // Do something } } }
它們的變體如下圖所示。
對(duì)于組合視圖 collectAsState () 不會(huì)確保在組合函數(shù)處于活動(dòng)狀態(tài)時(shí)安全使用數(shù)據(jù),它也不會(huì)停止繼續(xù)發(fā)送 StateFlow,這會(huì)導(dǎo)致資源浪費(fèi)。
為了確保只在 Activity 或 Fragment 處于正確的生命周期時(shí),例如在 onStart () 之后發(fā)出,我們需要使用 collectAsStateWithLifecycle () 和 stateFlow 中的 WhileSubscribed (...)。
當(dāng)我們?cè)谘芯?collectAsStateWithLifecycle () 源碼時(shí),發(fā)現(xiàn)它也在使用 repeatOnLifecycle (…)。
全文到這里就結(jié)束了,感謝你的閱讀
廣告聲明:文內(nèi)含有的對(duì)外跳轉(zhuǎn)鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時(shí)間,結(jié)果僅供參考,IT之家所有文章均包含本聲明。