Android開(kāi)發(fā)SavedState?Jetpack狀態(tài)保存利器
背景
在我們android開(kāi)發(fā)中,如果需要actiivty/fragment等有狀態(tài)的控件保存當(dāng)前狀態(tài),由系統(tǒng)進(jìn)行數(shù)據(jù)保存的恢復(fù)的時(shí)候
比如正常的暗黑模式切換/后臺(tái)時(shí)低內(nèi)存系統(tǒng)回收等等,都需要我們對(duì)當(dāng)前的用戶(hù)數(shù)據(jù)進(jìn)行保存,不然下次重新恢復(fù)的時(shí)候,就會(huì)出現(xiàn)丟失數(shù)據(jù)的情況,給用戶(hù)造成不太好的體驗(yàn)
一般都會(huì)重寫(xiě)onSaveInstanceState方法進(jìn)行,在里面的Bundle對(duì)象進(jìn)行數(shù)據(jù)的寫(xiě)入,然后會(huì)在onCreate階段或者onRestoreInstanceState階段進(jìn)行數(shù)據(jù)的恢復(fù)!這套生命周期框架一直是深深嵌入在我們的開(kāi)發(fā)習(xí)慣中!
但是隨著項(xiàng)目的迭代,也隨著業(yè)務(wù)的發(fā)展,我們可以發(fā)現(xiàn),在頁(yè)面復(fù)雜度提高的同時(shí),在onSaveInstanceState里面需要保存的數(shù)據(jù)也是越來(lái)越多這就帶來(lái)了幾個(gè)問(wèn)題,僅舉例
- onSaveInstanceState存儲(chǔ)數(shù)據(jù)復(fù)雜,可能多人迭代就存在重復(fù)存取的現(xiàn)象,造成代碼結(jié)構(gòu)問(wèn)題與隱藏bug的風(fēng)險(xiǎn)
- 不可復(fù)用,比如activity1需要存儲(chǔ)的數(shù)據(jù),剛好activity2也需要存儲(chǔ),這個(gè)時(shí)候就只能寫(xiě)兩份代碼,以此類(lèi)推
- 沒(méi)有統(tǒng)一的管理層,即數(shù)據(jù)的維護(hù)可能需要團(tuán)隊(duì)的代碼規(guī)范
SavedState的登場(chǎng)
為了解決這些歷史android的設(shè)計(jì)問(wèn)題,也為了更方便廣大開(kāi)發(fā)者進(jìn)行更好的代碼結(jié)構(gòu)解耦設(shè)計(jì),所以google大哥在jetpack庫(kù)中,推出了SavedState,它的定位是在Android開(kāi)發(fā)中,編寫(xiě)可插入組件,以在進(jìn)程終止時(shí)保存界面狀態(tài),并在進(jìn)程重啟時(shí)恢復(fù)界面狀態(tài)。
雖然Savedstate推出來(lái)一段時(shí)間了,但是卻一直處在“默默無(wú)聞”的狀態(tài),可能的原因有很多,比如日常開(kāi)發(fā)很少接觸狀態(tài)保存,大部分app中真正需要保存狀態(tài)的其實(shí)是很少一部分,很多app甚至是不寫(xiě)狀態(tài)保存邏輯的,被系統(tǒng)回收就回收掉了,重建就是了(即使丟失了)。
還有就是學(xué)習(xí)資料比較少,現(xiàn)在基本找不到SavedState的相關(guān)資料(區(qū)別于我們viewmodel常用的SavesStateHandle)。
官方的demo例子也沒(méi)有,所以Savedstate很長(zhǎng)一段時(shí)間都是處于雪藏的狀態(tài)。但是!為了讓我們的app擁有更加好的體驗(yàn),同時(shí)也提高我們的技術(shù)視野,學(xué)習(xí)Savedstate還是很有必要的,不然官方也不會(huì)白白將其加入jetpack系列。
理解SavedState
用法
深入理解之前呢,我們必須要會(huì)用不是嘛!我們下面來(lái)看一下例子
在Activity onCreate方法中執(zhí)行以下代碼
注意:savedStateRegistry.isRestored在onCreate之后就會(huì)變成true,
也就是說(shuō),我們必須在ComponentActivity的onCreate走完之后才能用,
原理看下邊的解析
if(savedStateRegistry.isRestored){
val consumeRestoredStateForKey = savedStateRegistry.consumeRestoredStateForKey("test")
val test = consumeRestoredStateForKey?.getInt("test")
Log.i("hello","tag test is $test")
}
savedStateRegistry.registerSavedStateProvider("test",DataProvider())
class DataProvider: SavedStateRegistry.SavedStateProvider {
override fun saveState(): Bundle {
return Bundle().apply {
this.putInt("test",1)
}
}
}
如果對(duì)上訴程序不理解,不要緊,我們會(huì)接下來(lái)講解!運(yùn)行上訴程序,在app一開(kāi)始啟動(dòng)的時(shí)候,log輸出就是null,這個(gè)時(shí)候我們退到后臺(tái)(可以設(shè)置不保留活動(dòng)),再次打開(kāi)的時(shí)候,可以看到activity被回收重建,這個(gè)時(shí)候log輸出的卻是1,這里就驗(yàn)證了SavedState具有保存數(shù)據(jù)的能力。
在demo中savedStateRegistry其實(shí)是ComponentActivity的一個(gè)對(duì)象,我們接下里分析一下,SavedState的概念組成
SavedState組成概念
SavedState庫(kù)中,有以下幾個(gè)概念
| SavedStateRegistryOwner | SavedStateRegistryController | SavedStateRegistry | SavedStateProvider |
|---|---|---|---|
| 保存狀態(tài)擁有者,用于提供聲明周期的同步操作 | 控制器,相當(dāng)于一個(gè)連接角色,用于連接SavedStateRegistryOwner與SavedStateRegistry | 數(shù)據(jù)管理者,用于提供對(duì)存儲(chǔ)數(shù)據(jù)的相關(guān)操作 | 數(shù)據(jù)提供者,用于提供存儲(chǔ)的數(shù)據(jù) |
我們?cè)賮?lái)看一下依賴(lài)關(guān)系,可以看到這個(gè)是單向依賴(lài)

看到這里,我們就對(duì)SavedState有了個(gè)初步的認(rèn)識(shí),這個(gè)時(shí)候就可以再回到demo了,首先我們的Activity是繼承了ComponentActivity,而ComponentActivity其實(shí)是實(shí)現(xiàn)了SavedStateRegistryOwner接口的

所以我們的數(shù)據(jù)存儲(chǔ)過(guò)程發(fā)起者都是由該activity的生命周期決定,而ComponentActivity里面擁有一個(gè)SavedStateRegistryController對(duì)象

我們可以通過(guò)SavedStateRegistryController對(duì)象,獲取到SavedStateRegistry

構(gòu)成已經(jīng)很清楚了,我們?cè)賮?lái)解釋一下上面的用法,其實(shí)分為兩步:
- savedStateRegistry.registerSavedStateProvider,通過(guò)savedStateRegistry的registerSavedStateProvider方法注冊(cè)一個(gè)數(shù)據(jù)保存集合,第一個(gè)參數(shù)就是自定義的key,第二個(gè)參數(shù)為實(shí)現(xiàn)SavedStateProvider接口的類(lèi),即DataProvider
- savedStateRegistry.consumeRestoredStateForKey可以獲取當(dāng)前的存儲(chǔ)的數(shù)據(jù)集合,參數(shù)為我們自定義的key,如果當(dāng)前存在key對(duì)應(yīng)的數(shù)據(jù),就返回具體存儲(chǔ)的數(shù)據(jù)(發(fā)生在重組時(shí)),如果不存在就返回null(發(fā)生在首次進(jìn)入的時(shí)候)。值得注意的一個(gè)點(diǎn)是,如果我們沒(méi)有調(diào)用unregisterSavedStateProvider(刪除對(duì)應(yīng)key的存儲(chǔ)數(shù)據(jù))方法,那么除了首次進(jìn)入之外,每次系統(tǒng)回收都會(huì)幫我們恢復(fù)在registerSavedStateProvider創(chuàng)建時(shí)的數(shù)據(jù)集合。
到這里,用法其實(shí)就很明確了,通過(guò)以上的分層,我們就可以把原本耦合在onSaveInstanceState的數(shù)據(jù)存儲(chǔ)邏輯,變成了一個(gè)個(gè)SavedStateProvider,方便了后期數(shù)據(jù)的管理與復(fù)用,即像插件一樣我們隨時(shí)可以替換具體的邏輯而不影響業(yè)務(wù)本身。
原理探究
我們來(lái)看一下registerSavedStateProvider究竟做了些什么
@MainThread
public void registerSavedStateProvider(@NonNull String key,
@NonNull SavedStateProvider provider) {
SavedStateProvider previous = mComponents.putIfAbsent(key, provider);
if (previous != null) {
throw new IllegalArgumentException("SavedStateProvider with the given key is"
+ " already registered");
}
}
private SafeIterableMap<String, SavedStateProvider> mComponents =
new SafeIterableMap<>();
可以看到,其實(shí)就是在mComponents里面放了一個(gè)SavedStateProvider對(duì)象,當(dāng)前,如果我們之前存放過(guò)了,再次存放就會(huì)拋出異常,而mComponents,其實(shí)就是一個(gè)map對(duì)象。
那么我們SavedStateRegistry什么時(shí)候才觸發(fā)這個(gè)存儲(chǔ)邏輯呢?其實(shí)在performSave方法里面
@MainThread
void performSave(@NonNull Bundle outBundle) {
Bundle components = new Bundle();
if (mRestoredState != null) {
components.putAll(mRestoredState);
}
for (Iterator<Map.Entry<String, SavedStateProvider>> it =
mComponents.iteratorWithAdditions(); it.hasNext(); ) {
Map.Entry<String, SavedStateProvider> entry1 = it.next();
// 觸發(fā)了SavedStateProvider的saveState方法
components.putBundle(entry1.getKey(), entry1.getValue().saveState());
}
outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}
這里有個(gè)有趣的點(diǎn),它接受一個(gè)外來(lái)的Bundle,在外來(lái)的Bundle里面,再次存了一個(gè)子Bundle,而這個(gè)子Bundle,其實(shí)就是我們上文的mComponents的數(shù)據(jù),而對(duì)應(yīng)的key是一個(gè)常量
private static final String SAVED_COMPONENTS_KEY =
"androidx.lifecycle.BundlableSavedStateRegistry.key";
存放的子Bundle通過(guò)遍歷的方式,觸發(fā)了SavedStateProvider的saveState方法,獲取到我們實(shí)際上想要保存的數(shù)據(jù)。 那么是誰(shuí)調(diào)用了SavedStateRegistry的performSave方法呢?當(dāng)然就是 SavedStateRegistryController啦,我們說(shuō)過(guò)它其實(shí)是個(gè)中間角色,大部分操作需要經(jīng)過(guò)它才能調(diào)用到SavedStateRegistry
@MainThread
public void performSave(@NonNull Bundle outBundle) {
mRegistry.performSave(outBundle);
}
而SavedStateRegistryController的performSave方法,真的被調(diào)用起來(lái),就是在ComponenntActivity中
@CallSuper
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
Lifecycle lifecycle = getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
}
super.onSaveInstanceState(outState);
被調(diào)用
mSavedStateRegistryController.performSave(outState);
mActivityResultRegistry.onSaveInstanceState(outState);
}
到這里我們應(yīng)該豁然開(kāi)朗了,其實(shí)還是換湯不換藥,都是在onSaveInstanceState里面進(jìn)行的邏輯保存,只不過(guò)這一層被SavedState給封裝起來(lái)了,同時(shí)加入到了androidx中,也算是官方想要重新完善onSaveInstanceState架構(gòu)的體現(xiàn)。
看到這里了,我們也就能解釋存放在SavedStateProvider的數(shù)據(jù),其實(shí)就存放在了onSaveInstanceState的Bundle中,key為SAVED_COMPONENTS_KEY的子Bundle中。
我們?cè)賮?lái)看一下consumeRestoredStateForKey,取數(shù)據(jù)的邏輯
@MainThread
@Nullable
public Bundle consumeRestoredStateForKey(@NonNull String key) {
if (!mRestored) {
throw new IllegalStateException("You can consumeRestoredStateForKey "
+ "only after super.onCreate of corresponding component");
}
if (mRestoredState != null) {
Bundle result = mRestoredState.getBundle(key);
mRestoredState.remove(key);
if (mRestoredState.isEmpty()) {
mRestoredState = null;
}
return result;
}
return null;
}
可以看到,只有mRestored為true的時(shí)候,我們才能真正進(jìn)入到取數(shù)的邏輯,否則拋出異常,因?yàn)槲覀兡軌颢@取到數(shù)據(jù)的前提是,系統(tǒng)幫我們把數(shù)據(jù)進(jìn)行恢復(fù)之后!而mRestored被賦值的時(shí)候,其實(shí)就在performRestore中
@SuppressWarnings("WeakerAccess")
@MainThread
void performRestore(@NonNull Lifecycle lifecycle, @Nullable Bundle savedState) {
if (mRestored) {
throw new IllegalStateException("SavedStateRegistry was already restored.");
}
if (savedState != null) {
mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
}
....
mRestored = true;
}
有了上面存數(shù)據(jù)的邏輯,我們很容易知道,performRestore的調(diào)用者,最終肯定是由實(shí)現(xiàn)了 SavedStateRegistryOwner接口的ComponentActivity發(fā)起調(diào)用
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// Restore the Saved State first so that it is available to
// OnContextAvailableListener instances
//這里就是恢復(fù)數(shù)據(jù)來(lái)源
mSavedStateRegistryController.performRestore(savedInstanceState);
mContextAwareHelper.dispatchOnContextAvailable(this);
super.onCreate(savedInstanceState);
mActivityResultRegistry.onRestoreInstanceState(savedInstanceState);
ReportFragment.injectIfNeededIn(this);
if (mContentLayoutId != 0) {
setContentView(mContentLayoutId);
}
}
最后我們?cè)俳o出具體的流程圖,存數(shù)據(jù)和取數(shù)據(jù)的邏輯:

最后
通過(guò)SavedState,我們也能夠看到j(luò)etpack官方希望改變歷史android架構(gòu)的決心,也想要提供更加便捷優(yōu)雅的方式提供給開(kāi)發(fā)者。看到這里了,也希望我們?cè)谌粘i_(kāi)發(fā)中,可以運(yùn)用到SavedState進(jìn)行數(shù)據(jù)保存恢復(fù),別讓它繼續(xù)雪藏啦!畢竟連依賴(lài)都不需要導(dǎo)入呢 ! 更多關(guān)于SavedState Jetpack狀態(tài)保存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android HttpURLConnection.getResponseCode()錯(cuò)誤解決方法
在使用HttpURLConnection.getResponseCode()的時(shí)候直接報(bào)錯(cuò)是IOException錯(cuò)誤,一直想不明白,同一個(gè)程序我調(diào)用了兩次,結(jié)果有一個(gè)鏈接一直O(jiān)K,另一個(gè)卻一直報(bào)這個(gè)錯(cuò)誤2013-06-06
Android studio 出現(xiàn)錯(cuò)誤Run with --stacktrace option to get the s
這篇文章主要介紹了 Android studio 出現(xiàn)錯(cuò)誤Run with --stacktrace option to get the stack trace. Run with --info or --debu的相關(guān)資料,需要的朋友可以參考下2016-11-11
100 行代碼實(shí)現(xiàn)Flutter自定義TabBar的示例代碼
這篇文章主要介紹了100 行代碼實(shí)現(xiàn)Flutter自定義TabBar的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Android開(kāi)發(fā)之創(chuàng)建可點(diǎn)擊的Button實(shí)現(xiàn)方法
這篇文章主要介紹了Android創(chuàng)建可點(diǎn)擊的Button實(shí)現(xiàn)方法,實(shí)例分析了Android創(chuàng)建button按鈕過(guò)程中的界面配置,功能實(shí)現(xiàn)與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-03-03
淺談Android Studio 3.0 工具新特性的使用 Android Profiler 、Device File
這篇文章主要介紹了淺談Android Studio 3.0 工具新特性的使用 Android Profiler 、Device File Explorer的相關(guān)資料,需要的朋友可以參考下2017-11-11
Android開(kāi)發(fā)中的MVC設(shè)計(jì)模式淺析
這篇文章主要介紹了Android開(kāi)發(fā)中的MVC設(shè)計(jì)模式淺析,本文講解了對(duì)Android開(kāi)發(fā)中的MVC設(shè)計(jì)模式的理解,需要的朋友可以參考下2015-06-06
Android懸浮按鈕點(diǎn)擊返回頂部FloatingActionButton
這篇文章主要為大家詳細(xì)介紹了Android懸浮按鈕FloatingActionButton點(diǎn)擊回到頂部的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02
Android BroadcastReceiver實(shí)現(xiàn)網(wǎng)絡(luò)狀態(tài)實(shí)時(shí)監(jiān)聽(tīng)
這篇文章主要為大家詳細(xì)介紹了Android BroadcastReceiver實(shí)現(xiàn)網(wǎng)絡(luò)狀態(tài)實(shí)時(shí)監(jiān)聽(tīng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Android應(yīng)用借助LinearLayout實(shí)現(xiàn)垂直水平居中布局
這篇文章主要介紹了Android應(yīng)用借助LinearLayout實(shí)現(xiàn)垂直水平居中布局的方法,文中列舉了LinearLayout線性布局下居中相關(guān)的幾個(gè)重要參數(shù),需要的朋友可以參考下2016-04-04

