一點點入坑JetPack:ViewModel篇

前言

費了不少腦細胞,把Lifecycle單拆出來整了一篇文章。那麼接下來天然而然的就到了ViewModel,爲了讓系列像系列的樣子,因此這裏仍然是單獨把ViewModel拿出來。java

你別說單獨抽出來,還真有點乾乾巴巴,麻麻賴賴,一點都不圓潤。那還說啥呢?盤它...android

一點點入坑JetPack:ViewModel篇數據庫

一點點入坑JetPack:Lifecycle篇服務器

一點點入坑JetPack:LiveData篇網絡

一點點入坑JetPack:實戰前戲NetworkBoundResource篇app

一點點入坑JetPack(終章):實戰MVVM框架

正文

1、ViewModel

新官上任三把火,強敵面前秀三波。對於ViewModel來講,它算是JetPack框架中堪當中樞的角色,說實話它實在很差單獨去聊,更多的是和LiveData共進退。這裏必須安利一下,ViewModel+LiveData的確很好用,甚至可能加上Room簡直...飄了,拽了,感受本身個頭都不矮了;瘋了,狂了,敢在宇宙之間稱王了....異步

礙於篇幅的緣由,這裏單獨聊ViewModel,後邊會綜合介紹展示其強大的戰鬥力...ide

關於ViewModel來講,其實仍是蠻簡單的。從ViewModel官方的描述來看ViewModel的存在,解決了倆大問題:post

1.一、解決問題1

咱們都知道,當咱們的Activity/Fragment由於某些因素被銷燬重建時,咱們的成員變量便失去了意義。所以咱們經常須要經過 onSaveInstanceState()和onCreate()/onSaveInstanceState(Bundle)完成對數據的恢復(一般還要保證其正確的序列化)。而且對於大型數據來書,便有些乏力,好比:List、Bitmap...

而ViewModel就是解決此問題。

1.二、解決問題2

另外一個問題是Activity/Fragment常常須要進行一些異步操做。一旦涉及到異步,咱們都明白這裏存在內存泄漏的可能性。所以咱們保證Activity/Fragment在銷燬後及時清理異步操做,以免潛在的內存泄漏。

ViewModel並無自動幫咱們解決這個問題,而是經過onCleared()交給咱們業務本身重寫去處理。

1.三、使用方法

關於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.四、Fragment共享

值得注意的一點:of方法須要傳遞一個Activity/Fragment。由於ViewModel須要與其生命週期綁定。既然能夠傳遞一個Activity,那麼咱們就可以猜到:是否是對於此Activity下的Fragment這個ViewModel也是可見的?

沒錯,正是如此。官方也做出瞭解讀:Activity中的兩個或多個Framgent須要相互通訊是很常見的,這個常見的痛點能夠經過使用ViewModel對象來解決,這些Fragment能夠共享ViewModel來處理通訊問題。

因此咱們在同Activity下,不一樣的Fragment實例,能夠直接經過傳入activity,拿到一樣的ViewModel實例,進而實現數據通信。

真的很方便...

2、源碼

若是咱們打開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();
    }
}
複製代碼

3、小總結

從ViewModel的使用上來講,彷佛並無什麼「船新的版本」...更多的是幫咱們搞定了一些現存的坑。的確是如此,但其實ViewModel更可能是帶來了一種思想:數據驅動,也就是MVVM。

ViewModel做爲中樞,擔任了從數據源拿數據,交由LiveData通知UI層更新UI。用一張圖來解釋這種變革:

Google Sample爲Repository的編寫,提供了一個很巧妙的設計:NetworkBoundResource。全類一共有120+的代碼,卻基於LiveData+ViewModel幫咱們約束了:從服務器取從數據庫取網絡獲取失敗,從數據庫取...等等一系列網絡請求、本地請求約數。

關於這個類的設計與用法,會在後續的實戰篇一點點展開。沒錯,當你用上它們,你會愛上這款「遊戲」。

尾聲

今天的文章想聊的內容就到此結束了,更多的是ViewModel的一個引子。畢竟對於咱們來講,我tm不須要知道這些,只須要告訴我怎麼寫就行。老夫寫代碼就是ctrl+c/v!

不着急,一點點來。後邊我會把業務中正在運行的代碼拿出來,作實戰操做分析。飯要一口口的吃,文章要一篇篇的寫...

我是一個應屆生,最近和朋友們維護了一個公衆號,內容是咱們在從應屆生過渡到開發這一路所踩過的坑,以及咱們一步步學習的記錄,若是感興趣的朋友能夠關注一下,一同加油~

我的公衆號:鹹魚正翻身
相關文章
相關標籤/搜索