關於ViewModel 介紹,文章不過多闡述。官方文件
本文將從三個方面做一定闡述
為什麼需要ViewModel
-
你需要處理配置變化
- 筆者認為,使用者可以隨時改變配置(i.e 旋轉螢幕,切換語言,切換系統文字大小 ...),這可能會導致當前Activity重建,這些都不受開發者的控制,但是你又不得不處理它
- 可能很多APP在配置清單檔案中申明瞭每一個Activity
orientation = portrait
,但是你無法禁止使用者去改變語言、文字大小。這樣就可能會導致Activity被移除或者重新建立
-
為什麼
onSaveInstanceState
依舊不夠傳統的做法都是在配置發生變化即
onSaveInstanceState
方法去save data, 在onCreate去restore data但是這裡有兩個限制
onSaveInstanceState
方法不能夠快取較大的資料,筆者之前嘗試快取上百兆資料發現丟擲了TransactionTooLargeException
- 儲存的資料一定需要實現
serializable
或者Parceable
, 但是有時候這些資料來自第三方庫,我們不能修改它,對於某些場景,很難在onSaveonSaveInstanceState
中儲存資料
基於上述兩點,ViewModel應運而生
- 配置改變前後資料儲存與恢復
一些例子
基礎功能
public class ZeroViewModel extends ViewModel {
public User user;
}
複製程式碼
public class ZeroDemo extends AppCompatActivity {
private TextView tv;
private ZeroViewModel vm;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tv_btn);
tv = findViewById(R.id.tv_simple);
vm = ViewModelProviders.of(this).get(ZeroViewModel.class);
System.out.println("szw vm.user = " + vm.user);
}
// android:onClick="onClickSimpleButton"
public void onClickSimpleButton(View v) {
vm.user = new User(23, "jorden");
}
}
複製程式碼
旋轉螢幕,vm.user 依舊 != null
同一個Activity不同例項
- 同時存在兩個例項
public class SameClass01 extends AppCompatActivity {
private TextView tv;
private ZeroViewModel vm;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tv_btn);
tv = findViewById(R.id.tv_simple);
vm = ViewModelProviders.of(this).get(ZeroViewModel.class);
System.out.println("szw SameClass01 : " + vm.user);
}
// launch the second instance
// android:onClick="onClickSimpleButton"
public void onClickSimpleButton(View v) {
vm.user = new User(100, "SuperMario");
startActivity(new Intent(this, SameClass01.class));
}
}
複製程式碼
即使有兩個同類的Activity例項,第一個vm.user 持有的依舊是Mario,第二個vm.user 持有null . 這符合筆者的預期
- finish再重新建立
public class SameClass02 extends AppCompatActivity {
private TextView tv;
private ZeroViewModel vm;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tv_btn);
tv = findViewById(R.id.tv_simple);
vm = ViewModelProviders.of(this).get(ZeroViewModel.class);
System.out.println("szw SameClass02 onCreate() : " + vm.user);
}
// android:onClick="onClickSimpleButton"
public void onClickSimpleButton(View v) {
vm.user = new User(22, "test");
}
// android:onClick="onClickSimpleButton2"
public void onClickSimpleButton2(View v) {
System.out.println("szw SameClass02 : saved = "+vm.user);
}
}
複製程式碼
先啟動SameClass02 ,執行onClickSimpleButton,finish重新開啟,日誌輸出null
上述兩個例子表現正常
和Static申明的變數比較
- 基礎比較
public class SameVm {
public static User user;
}
複製程式碼
public class ZeroDemo extends AppCompatActivity {
private TextView tv;
private ZeroViewModel vm;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tv_btn);
tv = findViewById(R.id.tv_simple);
String value = savedInstanceState == null ? "emptyBundle" : savedInstanceState.getString("key");
System.out.println("szw onCreate() " + value);
vm = ViewModelProviders.of(this).get(ZeroViewModel.class);
System.out.println("szw vm.user = " + vm.user);
System.out.println("szw static = "+SameVm.user);
}
// android:onClick="onClickSimpleButton"
public void onClickSimpleButton(View v) {
vm.user = new User(23, "jorden");
SameVm.user = new User(21, "king");
}
}
複製程式碼
旋轉螢幕後,日誌輸出
szw vm.user = User{id=23, name='jorden'}
szw static = User{id=21, name='king’}
複製程式碼
- 終止應用
Terminate Application
和1同樣的操作
szw vm.user = null
szw static = null
複製程式碼
從上面兩個例子,感覺沒什麼不同。他們都能快取資料,終止應用程式都會被銷燬
它們的不同之處:
- ViewModel 主要是為了解耦,有點類似於MVP中的P,ViewModel 是MvvM中VM。你能在ViewModel中做非同步操作(i.e訪問網路),你可以改變data並且讓View接受到通知LiveData
- static value 能被任何類修改,但是ViewModel 是Activity的私有變數,有點類似ThreadLocal
- ViewModel 可以判斷Activity是正常銷燬或者配置改變,進而做出不同的響應,finish->removedata ,configurationchange->savedata,靜態變數卻不能
相關注意事項
- ViewModel不要應用Activity等相關例項,容易造成記憶體洩漏
- 如果你需要在ViewMolde中獲取Resource LocationManager等系統服務,可以繼承AndroidViewModel
- ViewModel本身不支援事件模型(EventBus),你可以使用LiveData,當然為了解決旋轉螢幕後,再次註冊Observer,重複提示,可以使用SingleLiveEvent
- 當系統回收我們應用時,ViewModel 並不能儲存資料,我們依舊需要複寫
onSaveInstanceState
方法
原始碼如下
public class DupliViewModel extends ViewModel {
private SingleLiveEvent<String> message = new SingleLiveEvent<>();
public void fetchMessage(){
message.setValue("A New Value");
}
public LiveData<String> getMessage() {
return message;
}
}
複製程式碼
public class DupliObserverDemo extends AppCompatActivity {
private TextView tv;
private DupliObserverDemo self;
private DupliViewModel vm;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tv_btn);
self = this;
tv = findViewById(R.id.tv_simple);
vm = ViewModelProviders.of(this).get(DupliViewModel.class);
vm.getMessage().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
System.out.println("szw updated ~");
Toast.makeText(self, "updated "+s, Toast.LENGTH_SHORT).show();
}
});
}
// android:onClick="onClickSimpleButton"
public void onClickSimpleButton(View v) {
vm.fetchMessage();
}
}
複製程式碼