如何優雅、高效地恢復Activity數據

google 原文可以點這里

保存 UI 狀態

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

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

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

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

用戶期望和系統行為


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

用戶主動讓 UI 狀態消失

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

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

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

系統讓 UI 狀態消失

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

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

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

保存UI狀態幾種方式的介紹


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


截圖.png

使用ViewModel來保存因Configuration changes所丟失的數據


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

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

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

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

使用onSaveInstanceState()備份數據,在系統殺死、重建應用之后恢復


在系統殺死一個應用(Activity)后,如果稍后這個應用(Activity)又被系統自動啟動,那么該Activity的onSaveInstanceState()會被調用,我們可以在這個方法中恢復Activity中的UI狀態。

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

所以不要用onSaveInstanceState()去保存大量數據,比如圖片或者需要消耗大量序列化、反序列化時間的復雜結構數據。相反的我們應該只保存一些基礎、簡單的數據比如String。同樣的我們應該保存最關鍵的少量必要數據,比如一個ID,當其它恢復策略時效后,我們可以通過這個ID去恢復之前的UI狀態。

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

除此之外,當你使用Intent打開一個Activity的時候,如果需要傳遞數據,我們使用Intent.putExtras方法來傳遞數據,當被啟動的Activity因Configuration changes導致銷毀重建的時候,我們仍然可以從Intent處獲取extras數據,這可以代替onSaveInstanceState()方法。

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

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

使用硬盤存儲來復雜、大量的數據


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

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

使用分治法存儲UI狀態


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

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

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

當用戶添加一首音樂的時候,ViewModel會通知本地存儲組件去存儲這首音樂。如果這首新添加的音樂需要呈現在UI中,我們還應該更新ViewModel對象中的數據,反應它關聯的UI狀態。請記住,我們應該在子線程對數據庫進行操作。

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

當應用進入后臺,系統會調用onSaveInstanceState()。我們應該保存搜索關鍵字在onSaveInstanceState()的bundle參數中,這個小數據容易保存,這個數據也是我們恢復UI狀態所需的所有信息,輕量又全面。

使用組裝法恢復復雜數據


當用戶返回Activity時,有兩種可能的場景會重新創建Activity:

  • 該Activity在被系統停止后被重新創建。我們可以獲取之前保存在onSaveInstanceState()中的查詢關鍵字,我們應該將這個關鍵字傳遞給ViewModel對象,ViewModel發現內存中并沒有存儲對應的歌曲,所以它通知本地數據層去加載對應的歌曲,最后將結果反饋給UI層。

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容