授權原文:https://mp.weixin.qq.com/s/tBojbetY1pmqHoNFYJcK8w
前言在引入了 Jetpack 之後,我們通常使用 ViewModel 元件來管理資料,當頁面因配置變更(尤其是在發生像旋轉這樣頻繁的配置更改之後)而重建時,可以使用 ViewModel 和 onSaveInstanceState(),以確保應用滿足使用者對其介面狀態的預期。
參考:Jetpack-VM再不懂你就out了
但如果是記憶體不足或者電量不足等系統原因導致的頁面被回收時 ViewModel 是不會被複用的。
一、使用者預期和系統行為參考官網 :https://developer.android.google.cn/topic/libraries/architecture/saving-states
1、使用者發起的介面狀態解除使用者希望當他們啟動 Activity 時,該 Activity 的暫時性介面狀態會保持不變,直到使用者完全關閉 Activity 為止。使用者可以透過以下方式完全關閉 Activity:
按返回按鈕從“概覽”(“最近使用的應用”)螢幕中滑動關閉 Activity從 Activity 向上導航從“設定”螢幕中終止應用完成某種“完成”Activity(由 Activity.finish() 提供支援)以上情況 Activity 例項將連同其中儲存的任何狀態以及與該 Activity 關聯的任何已儲存例項狀態記錄一起被銷燬並從記憶體中移除。
2、系統發起的介面狀態解除使用者期望 Activity 的介面狀態在整個配置變更(例如旋轉或切換到多視窗模式)期間保持不變。
但是,預設情況下,系統會在發生此類配置更改時銷燬 Activity;
例如應用切換至後臺後重新回到App時記憶體不足導致App被重啟;
我們知道Activity可以透過 onSaveInstanceState() onRestoreInstanceState()方式 狀態儲存機制,頁面重建後 ,可以恢復之前的狀態,為使用者提供更好的體驗。
onSaveInstanceState() 回撥會儲存一些資料,如果系統銷燬後又重新建立介面控制器(如 Activity 或 Fragment),則需要使用這些資料重新載入該控制器的狀態。
因為 onSavedInstanceState() 會將資料序列化到磁碟。如果序列化的物件很複雜,序列化會佔用大量的記憶體。
所以onSavedInstanceState() 不能用於儲存大量的資料(如點陣圖),也不能用於儲存冗長複雜資料結構
而是隻能用於儲存基本型別和簡單的小物件,例如字串
二、記憶體不足或者電量不足等系統原因導致的頁面被回收時 ViewModel 不會被複用的情況模擬購買商品時增加/減少數量的場景:
1、建立ViewModel,新增add()&del()控制num變化public class MyNormalViewModel extends ViewModel { private MutableLiveData<Integer> num = new MutableLiveData<Integer>(1); public LiveData<Integer> getNumber(){ return num; } public void add(){ num.setValue((int)num.getValue() + 1); } public void del(){ if(num.getValue() > 1){ num.setValue((int)num.getValue() - 1); } }}
2、建立Activity,透過DataBinding控制UI資料public class MyActivity extends AppCompatActivity { MyNormalViewModel myViewModel; MainActivityMyBinding binding; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.main_activity_my); myViewModel = new ViewModelProvider(this).get(MyNormalViewModel.class); binding.setVm(myViewModel); binding.setLifecycleOwner(this); }}
3、建立佈局檢視,透過DataBinding控制檢視屬性<?xml version="1.0" encoding="utf-8"?><layout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="vm" type="com.pxwx.main.viewmodel.MyNormalViewModel" /> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <LinearLayout android:layout_width="wrap_content" android:layout_height="@dimen/dp_30" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatButton android:id="@+id/btnDecrease" android:layout_width="30dp" android:layout_height="30dp" android:gravity="center" android:text="-" android:textStyle="bold" android:background="#999999" android:onClick="@{()->vm.del()}"/> <EditText android:id="@+id/etAmount" android:layout_width="wrap_content" android:layout_height="match_parent" android:minWidth="50dp" android:background="@null" android:inputType="number" android:gravity="center" android:enabled="false" android:textColor="@color/color_222222" android:text="@{vm.number.toString()}"/> <androidx.appcompat.widget.AppCompatButton android:id="@+id/btnIncrease" android:layout_width="30dp" android:layout_height="30dp" android:gravity="center" android:text="+" android:textStyle="bold" android:background="#999999" android:onClick="@{()->vm.add()}"/> </LinearLayout> </RelativeLayout></layout>
模擬購買商品時增加/減少數量的場景步驟:
1、增加要兌換的商品數量2、開發者選項開啟【不保留活動】3、按home鍵到桌面後再從程序中回到app4、再進行加減操作時數量是重置狀態了設定不保留活動
App進入後臺銷燬重新進入程序流程
透過ViewModel該如何解決呢?
應用切換至後臺後重新回到App時記憶體不足導致App被重啟;為了它能夠幫助開發者在 ViewModel 中處理 Activity 和 fragment 狀態儲存和恢復,googole提供了viewmodel-savedstate元件
三、ViewModel-SavedState儲存資料1、首先先新增依賴api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
2、ViewModel 搭配 SavedState 實現資料複用
ViewModel 可以有一個接收 SavedStateHandle 的建構函式:
public class MySavedStateViewModel extends ViewModel { private SavedStateHandle handle; public static final String KEY = "KEY_NUMBER"; public MySavedStateViewModel(SavedStateHandle handle){ if (!handle.contains(KEY)) { handle.set(KEY, 0); } this.handle = handle; } public LiveData<Integer> getNumber(){ return handle.getLiveData(KEY); } public void add(){ handle.set(KEY,(int)handle.get(KEY) + 1); } public void del(){ handle.set(KEY,(int)handle.get(KEY) - 1); }}
3、透過傳入savedstateviewmodel建立viewmodelpublic class MyActivity extends AppCompatActivity { MySavedStateViewModel myViewModel; MainActivityMyBinding binding; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //用databinding來繫結佈局 binding = DataBindingUtil.setContentView(this, R.layout.main_activity_my); //透過傳入savedstateviewmodel建立viewmodel myViewModel = new ViewModelProvider(this,new SavedStateViewModelFactory(getApplication(),this)).get(MySavedStateViewModel.class); binding.setVm(myViewModel); binding.setLifecycleOwner(this); }}
4、佈局檔案處理,同上面的列子一樣,不變<?xml version="1.0" encoding="utf-8"?><layout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="vm" type="com.pxwx.main.viewmodel.MySavedStateViewModel" /> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <LinearLayout android:layout_width="wrap_content" android:layout_height="@dimen/dp_30" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatButton android:id="@+id/btnDecrease" android:layout_width="30dp" android:layout_height="30dp" android:gravity="center" android:text="-" android:textStyle="bold" android:background="#999999" android:onClick="@{()->vm.del()}"/> <EditText android:id="@+id/etAmount" android:layout_width="wrap_content" android:layout_height="match_parent" android:minWidth="50dp" android:background="@null" android:inputType="number" android:gravity="center" android:enabled="false" android:textColor="@color/color_222222" android:text="@{vm.number.toString()}"/> <androidx.appcompat.widget.AppCompatButton android:id="@+id/btnIncrease" android:layout_width="30dp" android:layout_height="30dp" android:gravity="center" android:text="+" android:textStyle="bold" android:background="#999999" android:onClick="@{()->vm.add()}"/> </LinearLayout> </RelativeLayout></layout>
再次模擬購買商品時增加/減少數量的場景步驟:
1、增加要兌換的商品數量2、開發者選項開啟【不保留活動】3、按home鍵到桌面後再從程序中回到app4、再進行加減操作時數量是在原來儲存的資料基礎上增加(正常)SavedStateHandle 類包含鍵值對對映應有的方法:get(String key)contains(String key)remove(String key)set(String key, T value)keys()如上使用:此外,還有一種特殊的方法:getLiveData(String key),用於返回封裝在 LiveData 可觀察物件中的值。
當使用Savedstate儲存資料之後,後臺程序關閉,資料也會得到保留
SavedStateHandle生命週期