如何優(yōu)雅、高效地恢復(fù)Activity數(shù)據(jù)

google 原文可以點這里

保存 UI 狀態(tài)

在因內(nèi)存不足被系統(tǒng)殺死的應(yīng)用或者因為 Configuration changes 導(dǎo)致的Activity重建中,以及時的方式保存和恢復(fù)Activity的UI狀態(tài)是用戶體驗的關(guān)鍵部分。在這種情況下用戶是希望新啟動的Activity 和最后銷毀的 Activity之間的狀態(tài)要保持一致,但是如果我們不加以處理,系統(tǒng)并不會自動恢復(fù)這些數(shù)據(jù)。

為了實現(xiàn)用戶的期望,我們可以單獨或者組合使用 ViewModel、onSaceInstanceState()方法或者是一個本地文件存儲的方式來保存 UI 狀態(tài),至于是組合還是單獨用這三種手段,要取決于 UI 數(shù)據(jù)的復(fù)雜度、運行設(shè)備的情況、恢復(fù)數(shù)據(jù)的速度和消耗內(nèi)存的大小來綜合考慮。

不管我們使用哪種方式,我們應(yīng)該讓用戶看到他們最后看到的 UI 狀態(tài),用一種自然平滑的方式來加載數(shù)據(jù),避免占用太多時間加載需要重新呈現(xiàn)的 UI 狀態(tài),特別是在頻繁的發(fā)生 Configuration changes 的情況,比如旋轉(zhuǎn)屏幕。大多數(shù)情況下我們應(yīng)該使用 ViewModel 和 onSaveInstanceState()方法。

本章討論我們用來滿足期望所使用的幾種方式之間的差別,并列舉不同的使用場景,在不同的場景中我們將討論這幾種方式的優(yōu)缺點,并權(quán)衡地選出一種較為合理的手段。

用戶期望和系統(tǒng)行為


用戶可能希望清空 UI 狀態(tài),也可能期望保存 UI 狀態(tài),這取決于用戶的操作。在某些情況中系統(tǒng)的行為結(jié)果和用戶的期望是一致的,在另外一些情況則不滿足用戶的期望。

用戶主動讓 UI 狀態(tài)消失

用戶期望當(dāng)啟動一個 Activity 之后,一些不重要的 UI 狀態(tài)也可以一直存在,除非他們自己手動關(guān)閉一個 Activity,用戶可以通過以下幾種方式關(guān)閉一個 Activity:

  • 按返回按鈕
  • 后臺滑掉應(yīng)用
  • 返回導(dǎo)航棧中上一個 Activity 的按鈕
  • 在設(shè)置中心中強(qiáng)制停止一個應(yīng)用
  • 操作了某些執(zhí)行結(jié)束操作的動作,比如某些動作所調(diào)用的Activity.finish()方法

用戶覺得通過以上的方式后,應(yīng)該是會銷毀所有臨時的 UI 狀態(tài)的,如果重新打開 Activity 以后,它的界面應(yīng)該是一個初始的、干凈的界面。在這些場景中,系統(tǒng)的行為結(jié)果就滿足用戶的期望---Activity 實例會被銷毀并從內(nèi)存中移除,并且任何的 UI 狀態(tài)都不會被保存,在重新打開也就不會恢復(fù)這些 UI 狀態(tài)了。

對于某些場景來說這樣的操作后用戶并不希望徹底不保存 UI 狀態(tài),比如對于一個瀏覽器而言,用戶期望的可能是點擊返回按鈕之后不銷毀這個界面而是返回上一頁瀏覽的網(wǎng)頁。

系統(tǒng)讓 UI 狀態(tài)消失

用戶希望在Configuration changes,比如旋轉(zhuǎn)屏幕、拖入多窗口模式之后,Activity 的 UI 狀態(tài)應(yīng)該和之前的保持一致。但是系統(tǒng)的默認(rèn)行為結(jié)果和此相反,任何的 UI 狀態(tài)都不會得到保存和恢復(fù),雖然我們可以通過更改 Configuration changes 的默認(rèn)行為,但是我們并不推薦這種方式。

用戶希望在他們短暫地離開了 Activity 之后再回來還能看到的 Activity 還是最后看到的樣子,比如用戶在我們的搜索 Activity 中輸入搜索內(nèi)容之后按了 home 鍵或者被一個來電打斷了---當(dāng)他們重新回到這個界面的時候,用戶希望能看到他們最后輸入的搜索詞。

在這種場景中,我們的 app 應(yīng)該在后臺做一些操作讓我們的應(yīng)用能在內(nèi)存中駐留。但是系統(tǒng)可能因為內(nèi)存不足而殺死我們的不可視應(yīng)用,在這種情況下,Activity 對象會被銷毀,所以用戶重新加載 app 之后看到的不是他們最后看到的狀態(tài),這種情況是用戶不希望看到的。

保存UI狀態(tài)幾種方式的介紹


當(dāng)用戶期望UI狀態(tài)得以保存而系統(tǒng)默認(rèn)的行為是不保存的時候,我們必須手動存儲和恢復(fù)丟失的數(shù)據(jù),確保用戶感知不到Activity被銷毀重建了。
保存UI狀態(tài)大致分為三類,它們之間保存數(shù)據(jù)的方式和時效各不相同,如下圖所示:


截圖.png

使用ViewModel來保存因Configuration changes所丟失的數(shù)據(jù)


ViewModel中保存的數(shù)據(jù)不會因為Configuration changes發(fā)生而丟失,并且由于其中的數(shù)據(jù)是保存在內(nèi)存中的,所以恢復(fù)數(shù)據(jù)的速度是幾種方式中最快的。

ViewModel是與Activity(或者其他LifecycleOwner對象)關(guān)聯(lián)的,在configuration changes之后,ViewModel對象依然在內(nèi)存中存活,并且自動和新重建的Activity(或者其他LifecycleOwner對象)建立關(guān)聯(lián)。

當(dāng)我們調(diào)用finish()方法的時候,這意味著用戶希望清除Activity的狀態(tài),這時ViewModel對象會被系統(tǒng)自動銷毀。

不像saved instance state在應(yīng)用、Activity被系統(tǒng)殺死、自動重建還能存活,在這個過程中ViewModel對象會被銷毀。所以這也是我們應(yīng)該使用ViewModel配合onSaveInstanceState()或者其它硬盤存儲手段來保存UI狀態(tài)的原因,我們可以在onSaveInstanceState()中保存數(shù)據(jù)的關(guān)鍵標(biāo)志,在系統(tǒng)殺死、重建應(yīng)用之后去恢復(fù)數(shù)據(jù)。

使用onSaveInstanceState()備份數(shù)據(jù),在系統(tǒng)殺死、重建應(yīng)用之后恢復(fù)


在系統(tǒng)殺死一個應(yīng)用(Activity)后,如果稍后這個應(yīng)用(Activity)又被系統(tǒng)自動啟動,那么該Activity的onSaveInstanceState()會被調(diào)用,我們可以在這個方法中恢復(fù)Activity中的UI狀態(tài)。

雖然通過saved instance state方式保存的Bundle類型數(shù)據(jù),可以在Configuration changes和系統(tǒng)殺死、自動重建的情況下存活,但是這種方式會受到硬盤大小和讀寫速度所限制,除此之外,需要保存的數(shù)據(jù)需要先序列化,序列化數(shù)據(jù)的開銷也受到數(shù)據(jù)大小的限制,因為序列化過程是發(fā)生在主線程的,所以如果我們需要序列化大量數(shù)據(jù),這時候可能會導(dǎo)致程序丟幀,對用戶體驗造成一定的影響。

所以不要用onSaveInstanceState()去保存大量數(shù)據(jù),比如圖片或者需要消耗大量序列化、反序列化時間的復(fù)雜結(jié)構(gòu)數(shù)據(jù)。相反的我們應(yīng)該只保存一些基礎(chǔ)、簡單的數(shù)據(jù)比如String。同樣的我們應(yīng)該保存最關(guān)鍵的少量必要數(shù)據(jù),比如一個ID,當(dāng)其它恢復(fù)策略時效后,我們可以通過這個ID去恢復(fù)之前的UI狀態(tài)。

onSaveInstanceState()不是必須實現(xiàn)的。舉個例子,在瀏覽器中,當(dāng)用戶點擊返回按鈕的時候,我們可能讓瀏覽器顯示用戶上一個訪問的網(wǎng)頁,這個時候我們就沒必要使用onSaveInstanceState()方法,而是通過其它手段將上一個網(wǎng)頁的所有數(shù)據(jù)保存在本地。

除此之外,當(dāng)你使用Intent打開一個Activity的時候,如果需要傳遞數(shù)據(jù),我們使用Intent.putExtras方法來傳遞數(shù)據(jù),當(dāng)被啟動的Activity因Configuration changes導(dǎo)致銷毀重建的時候,我們?nèi)匀豢梢詮腎ntent處獲取extras數(shù)據(jù),這可以代替onSaveInstanceState()方法。

但是ViewModel是無可替代的,在任何情況下我們都需要使用ViewModel保存數(shù)據(jù),避免在Configuration changes情況下浪費資源去從數(shù)據(jù)庫加載數(shù)據(jù)。

如果要保存的UI數(shù)據(jù)簡單且輕量級,我們可以單獨使用onSaveInstanceState()來保存狀態(tài)數(shù)據(jù)。

使用硬盤存儲來復(fù)雜、大量的數(shù)據(jù)


通過將數(shù)據(jù)保存在本地,比如數(shù)據(jù)庫或者偏好設(shè)置文件,只要用戶不卸載或者清理app的數(shù)據(jù),我們的數(shù)據(jù)都不會丟失。所以應(yīng)用因為什么原因重新打開,我們都可以從本地文件中恢復(fù)之前的數(shù)據(jù)。但是這種方式的開銷比前面兩種大,因為我們需要將硬盤中的數(shù)據(jù)先讀取、載入內(nèi)存。通常我們設(shè)置一個APP的時候,數(shù)據(jù)庫存儲已經(jīng)成為架構(gòu)中的一個重要環(huán)節(jié),它用來保存用戶所有想要保存的數(shù)據(jù),并且在下次打開應(yīng)用的時候還可以恢復(fù)。

在這種情況下,ViewModel和saved instance state這兩種方式保存的時間都不夠長,所以這種情況下本地化存儲方案是無法替代的,比如使用數(shù)據(jù)庫存儲。相反的,對于臨時數(shù)據(jù),我們不應(yīng)該使用本地化存儲方案,而應(yīng)該使用ViewModel或者saved instance state方案。

使用分治法存儲UI狀態(tài)


將'保存和恢復(fù)'任務(wù)分配給這幾種方式,讓它們協(xié)和完成這個過程是一種高效的手段。在大多數(shù)情況下,鑒于需要保存數(shù)據(jù)的復(fù)雜性、幾種方式的生命周期和獲取數(shù)據(jù)的速度,我們應(yīng)該讓它們存儲不同類型的數(shù)據(jù)。

  • 本地文件存儲:當(dāng)我們重復(fù)開閉一個Activity,我們不希望某些重要數(shù)據(jù)丟失,這時候我們就需要用到這種存儲方案。
    比如存儲音樂文件。
  • ViewModel:存儲臨時的、和Activity關(guān)聯(lián)的數(shù)據(jù),數(shù)據(jù)可以是復(fù)雜的,因為數(shù)據(jù)存儲在內(nèi)存中。
    比如存儲最近播放的音樂和最近搜索的關(guān)鍵字
  • onSaveInstanceState(): 存儲少量、簡單、必須的數(shù)據(jù),當(dāng)系統(tǒng)殺死、自動重建Activity以后用來恢復(fù)數(shù)據(jù)。如果是復(fù)雜的數(shù)據(jù),那么應(yīng)該講數(shù)據(jù)存儲在本地文件比如數(shù)據(jù)庫,然后在這個方法中存儲數(shù)據(jù)的標(biāo)志符,在Activity重建后通過這個標(biāo)志符去數(shù)據(jù)庫中加載、恢復(fù)UI狀態(tài)。
    比如存儲最近搜索的關(guān)鍵字

舉個例子,當(dāng)我們的Activity允許我們在音樂庫搜索音樂的時候,以下是處理不同事件的方法:

當(dāng)用戶添加一首音樂的時候,ViewModel會通知本地存儲組件去存儲這首音樂。如果這首新添加的音樂需要呈現(xiàn)在UI中,我們還應(yīng)該更新ViewModel對象中的數(shù)據(jù),反應(yīng)它關(guān)聯(lián)的UI狀態(tài)。請記住,我們應(yīng)該在子線程對數(shù)據(jù)庫進(jìn)行操作。

當(dāng)用戶搜索一首歌的時候,從數(shù)據(jù)庫讀取出來的歌曲不管多么復(fù)雜, 我們都應(yīng)該馬上存儲在ViewModel中,同時將這個搜索關(guān)鍵字也一并保存在ViewModel對象中。

當(dāng)應(yīng)用進(jìn)入后臺,系統(tǒng)會調(diào)用onSaveInstanceState()。我們應(yīng)該保存搜索關(guān)鍵字在onSaveInstanceState()的bundle參數(shù)中,這個小數(shù)據(jù)容易保存,這個數(shù)據(jù)也是我們恢復(fù)UI狀態(tài)所需的所有信息,輕量又全面。

使用組裝法恢復(fù)復(fù)雜數(shù)據(jù)


當(dāng)用戶返回Activity時,有兩種可能的場景會重新創(chuàng)建Activity:

  • 該Activity在被系統(tǒng)停止后被重新創(chuàng)建。我們可以獲取之前保存在onSaveInstanceState()中的查詢關(guān)鍵字,我們應(yīng)該將這個關(guān)鍵字傳遞給ViewModel對象,ViewModel發(fā)現(xiàn)內(nèi)存中并沒有存儲對應(yīng)的歌曲,所以它通知本地數(shù)據(jù)層去加載對應(yīng)的歌曲,最后將結(jié)果反饋給UI層。

  • 當(dāng)Configuration changes之后,Activity被重新加載,我們可以獲取之前保存在onSaveInstanceState()中的查詢關(guān)鍵字,我們應(yīng)該將這個關(guān)鍵字傳遞給ViewModel對象,因為ViewModel可以在Configuration changes后存活,所以它可以在內(nèi)存中找到對應(yīng)的歌曲信息,并直接將結(jié)果反饋給UI層。這意味著它不需要從數(shù)據(jù)庫中重新查詢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容