前言
費了很多腦細胞,把Lifecycle單拆出來整了一篇文章。那麼接下來自然而然的就到了ViewModel,為了讓系列像系列的樣子,所以這裡仍然是單獨把ViewModel拿出來。
你別說單獨抽出來,還真有點乾乾巴巴,麻麻賴賴,一點都不圓潤。那還說啥呢?盤它…
正文
一、ViewModel
新官上任三把火,強敵面前秀三波。對於ViewModel來說,它算是JetPack框架中堪當中樞的角色,說實話它實在不好單獨去聊,更多的是和LiveData共進退。這裡必須安利一下,ViewModel+LiveData的確很好用,甚至可能加上Room簡直…飄了,拽了,感覺自己個頭都不矮了;瘋了,狂了,敢在宇宙之間稱王了….
礙於篇幅的原因,這裡單獨聊ViewModel,後邊會綜合介紹展現其強大的戰鬥力…
關於ViewModel來說,其實還是蠻簡單的。從ViewModel官方的描述來看ViewModel的存在,解決了倆大問題:
1.1、解決問題1
我們都知道,當我們的Activity/Fragment因為某些因素被銷燬重建時,我們的成員變數便失去了意義。因此我們常常需要通過 onSaveInstanceState()和onCreate()/onSaveInstanceState(Bundle)完成對資料的恢復(通常還要保證其正確的序列化)。並且對於大型資料來書,便有些乏力,比如:List、Bitmap…
而ViewModel就是解決此問題。
1.2、解決問題2
另一個問題是Activity/Fragment經常需要進行一些非同步操作。一旦涉及到非同步,我們都明白這裡存在記憶體洩漏的可能性。因此我們保證Activity/Fragment在銷燬後及時清理非同步操作,以避免潛在的記憶體洩漏。
ViewModel並沒有自動幫我們解決這個問題,而是通過onCleared()
交給我們業務自己重寫去處理。
1.3、使用方法
關於ViewModel的使用,實在沒啥好說的。實在是太簡單了,一個簡單的demo:
class MyViewModel : ViewModel() {
var name: String = "MDove"
}
// Activity中呼叫
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
// TODO model.name
}
}
複製程式碼
我們只需要將想要被儲存、被管理的變數,宣告在ViewModel的實現類中即可。然後通過ViewModelProviders.of()/get()
拿到這個例項。就可能像往常一樣自由的使用,而不需要擔心Activity/Fragment重建所帶來的一系列問題。
注意警告!
文件在此處,有一個大大的警告:Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
為啥?從上述解決的問題來看,ViewModel很明顯生命週期會比Activity要長,因此如果持有Activity相關例項,必然會帶來記憶體洩漏。(那如果的確有業務需要咋整?使用AndroidViewModel(application)
即可。)
1.4、Fragment共享
值得注意的一點:of方法需要傳遞一個Activity/Fragment。因為ViewModel需要與其生命週期繫結。既然可以傳遞一個Activity,那麼我們就能夠猜到:是不是對於此Activity下的Fragment這個ViewModel也是可見的?
沒錯,正是如此。官方也作出瞭解讀:Activity中的兩個或多個Framgent需要相互通訊是很常見的,這個常見的痛點可以通過使用ViewModel物件來解決,這些Fragment可以共享ViewModel來處理通訊問題。
所以我們在同Activity下,不同的Fragment例項,可以直接通過傳入activity,拿到同樣的ViewModel例項,進而實現資料通訊。
真的很方便…
二、原始碼
如果我們開啟ViewModel的原始碼,我們會發現…
public abstract class ViewModel {
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
}
複製程式碼
就是一個抽象類,沒錯,整個ViewModel的設計就是很簡潔,我們往ViewModelProviders中繼續看:
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
複製程式碼
我們可以看到在例項化ViewModelProvider中,需要傳一個ViewModelStore,而這個ViewModelStore直接通過傳入的FragmentActivity中拿,讓我們走進去看一看:
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can`t request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
複製程式碼
我們可以看到,這個ViewModelStore是在FragmentActivity中是一個mViewModelStore的變數。這個ViewModelStore是什麼?從名字可以看出它是一個ViewModel的Store。
ViewModelStore的很簡單,就是一個Map在後文中會展開。
最開始我看到這時,很懵。ViewModel是保證我們重建後例項的唯一,可是這竟然是一個成員變數,很明顯重建後變數就沒了?!…(PS:當然有這種疑問,是因為我自己蠢…)
怎麼肥死,小老弟??…其實這裡是沒問題的,我們仔細看一看,這個mViewModelStore賦值是通過這一行程式碼:
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
複製程式碼
沒錯,就是這行程式碼,保證了我們重建後恢復原來的mViewModelStore,進而保證了我們的ViewModel的唯一性。
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
複製程式碼
mViewModelStore原始碼 -> ViewModelStore原始碼,很常見的Map儲存操作
//
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
複製程式碼
三、小總結
從ViewModel的使用上來說,似乎並沒有什麼“船新的版本”…更多的是幫我們搞定了一些現存的坑。的確是如此,但其實ViewModel更多是帶來了一種思想:資料驅動,也就是MVVM。
ViewModel作為中樞,擔任了從資料來源拿資料,交由LiveData通知UI層更新UI。用一張圖來解釋這種變革:
Google Sample為Repository的編寫,提供了一個很巧妙的設計:NetworkBoundResource
。全類一共有120+的程式碼,卻基於LiveData+ViewModel幫我們約束了:從伺服器取、從資料庫取、網路獲取失敗,從資料庫取…等等一系列網路請求、本地請求約數。
關於這個類的設計與用法,會在後續的實戰篇一點點展開。沒錯,當你用上它們,你會愛上這款“遊戲”。
尾聲
今天的文章想聊的內容就到此結束了,更多的是ViewModel的一個引子。畢竟對於我們來說,我tm不需要知道這些,只需要告訴我怎麼寫就行。老夫寫程式碼就是ctrl+c/v!
不著急,一點點來。後邊我會把業務中正在執行的程式碼拿出來,做實戰操作分析。飯要一口口的吃,文章要一篇篇的寫…