“包教包會(huì)“系列:Jetpack AAC完整解析(三)ViewModel 完全掌握!

上一篇介紹了Jetpack AAC 的數(shù)據(jù)處理組件 LiveData,它是使得 數(shù)據(jù)的更新 能以觀察者模式 被observer感知你虹,且此感知只發(fā)生在活躍生命周期狀態(tài)贯底。 這篇來介紹與LiveData搭配使用的視圖模型組件——ViewModel休建。

注意袍镀,如果你對(duì)MVVM架構(gòu)中的VM和本篇的ViewModel都沒有一定認(rèn)識(shí)的話,那么就不要將兩者進(jìn)行聯(lián)想了辆琅。目前漱办,你就理解為沒有任何關(guān)系。后面會(huì)有專門篇幅介紹MVVM婉烟。

一娩井、ViewModel介紹

ViewModel是Jetpack AAC的重要組件,同時(shí)也有一個(gè)同名抽象類似袁。

ViewModel洞辣,意為 視圖模型,即 為界面準(zhǔn)備數(shù)據(jù)的模型叔营。簡單理解就是屋彪,ViewModel為UI層提供數(shù)據(jù)。 官方文檔定義如下:

ViewModel 以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù)绒尊。(作用)

ViewModel 類讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)留存畜挥。(特點(diǎn))

到這里,你可能還是不清楚ViewModel到底是干啥的婴谱,別急蟹但,往下看。

1.1 出場背景

在詳細(xì)介紹ViewModel前谭羔,先來看下背景和問題點(diǎn)华糖。

  1. Activity可能會(huì)在某些場景(例如屏幕旋轉(zhuǎn))銷毀和重新創(chuàng)建界面,那么存儲(chǔ)在其中的界面相關(guān)數(shù)據(jù)都會(huì)丟失瘟裸。例如客叉,界面含用戶信息列表,因配置更改而重新創(chuàng)建 Activity 后,新 Activity 必須重新請求用戶列表兼搏,這會(huì)造成資源的浪費(fèi)卵慰。能否直接恢復(fù)之前的數(shù)據(jù)呢?對(duì)于簡單的數(shù)據(jù)佛呻,Activity 可以使用 onSaveInstanceState() 方法保存 然后從 onCreate() 中的Bundle恢復(fù)數(shù)據(jù)裳朋,但此方法僅適合可以序列化再反序列化的少量數(shù)據(jù)(IPC對(duì)Bundle有1M的限制),而不適合數(shù)量可能較大的數(shù)據(jù)吓著,如用戶信息列表或位圖鲤嫡。 那么如何做到 因配置更改而新建Activity后的數(shù)據(jù)恢復(fù)呢?

  2. UI層(如 Activity 和 Fragment)經(jīng)常需要通過邏輯層(如MVP中的Presenter)進(jìn)行異步請求绑莺,可能需要一些時(shí)間才能返回結(jié)果暖眼,如果邏輯層持有UI層應(yīng)用(如context),那么UI層需要管理這些請求紊撕,確保界面銷毀后清理這些調(diào)用以避免潛在的內(nèi)存泄露罢荡,但此項(xiàng)管理需要大量的維護(hù)工作赡突。 那么如何更好的避免因異步請求帶來的內(nèi)存泄漏呢对扶?

這時(shí)候ViewModel就閃亮出場了——ViewModel用于代替MVP中的Presenter,為UI層準(zhǔn)備數(shù)據(jù)惭缰,用于解決上面兩個(gè)問題浪南。

1.2 特點(diǎn)

具體地,相比于Presenter漱受,ViewModel有以下特點(diǎn):

1.2.1 生命周期長于Activity

ViewModel最重要的特點(diǎn)是 生命周期長于Activity络凿。來看下官網(wǎng)的一張圖:

ViewModel生命周期

看到在因屏幕旋轉(zhuǎn)而重新創(chuàng)建Activity后,ViewModel對(duì)象依然會(huì)保留昂羡。 只有Activity真正Finish的時(shí)ViewModel才會(huì)被清除絮记。

也就是說,因系統(tǒng)配置變更Activity銷毀重建虐先,ViewModel對(duì)象會(huì)保留并關(guān)聯(lián)到新的Activity怨愤。而Activity的正常銷毀(系統(tǒng)不會(huì)重建Activity)時(shí),ViewModel對(duì)象是會(huì)清除的蛹批。

那么很自然的撰洗,因系統(tǒng)配置變更Activity銷毀重建,ViewModel內(nèi)部存儲(chǔ)的數(shù)據(jù) 就可供重新創(chuàng)建的Activity實(shí)例使用了腐芍。這就解決了第一個(gè)問題差导。

1.2.2 不持有UI層引用

我們知道,在MVP的Presenter中需要持有IView接口來回調(diào)結(jié)果給界面猪勇。

而ViewModel是不需要持有UI層引用的设褐,那結(jié)果怎么給到UI層呢?答案就是使用上一篇中介紹的基于觀察者模式的LiveData。 并且助析,ViewModel也不能持有UI層引用裁替,因?yàn)閂iewModel的生命周期更長。

所以貌笨,ViewModel不需要也不能 持有UI層引用弱判,那么就避免了可能的內(nèi)存泄漏,同時(shí)實(shí)現(xiàn)了解耦锥惋。這就解決了第二個(gè)問題昌腰。

二、ViewModel使用

2.1 基本使用

了解了ViewModel作用解特點(diǎn)膀跌,下面來看看如何結(jié)合LivaData使用的遭商。(gradle依賴在第一篇中已經(jīng)介紹過了。)

步驟:

  1. 繼承ViewModel自定義MyViewModel
  2. 在MyViewModel中編寫獲取UI數(shù)據(jù)的邏輯
  3. 使用LiveData將獲取到的UI數(shù)據(jù)拋出
  4. 在Activity/Fragment中使用ViewModelProvider獲取MyViewModel實(shí)例
  5. 觀察MyViewModel中的LiveData數(shù)據(jù)捅伤,進(jìn)行對(duì)應(yīng)的UI更新劫流。

舉個(gè)例子,如果您需要在Activity中顯示用戶信息丛忆,那么需要將獲取用戶信息的操作分放到ViewModel中祠汇,代碼如下:

public class UserViewModel extends ViewModel {

    private MutableLiveData<String> userLiveData ;
    private MutableLiveData<Boolean> loadingLiveData;

    public UserViewModel() {
        userLiveData = new MutableLiveData<>();
        loadingLiveData = new MutableLiveData<>();
    }

    //獲取用戶信息,假裝網(wǎng)絡(luò)請求 2s后 返回用戶信息
    public void getUserInfo() {

        loadingLiveData.setValue(true);

        new AsyncTask<Void, Void, String>() {
            @Override
            protected void onPostExecute(String s) {
                loadingLiveData.setValue(false);
                userLiveData.setValue(s);//拋出用戶信息
            }
            @Override
            protected String doInBackground(Void... voids) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String userName = "我是胡飛洋,公眾號(hào)名字也是胡飛洋熄诡,歡迎關(guān)注~";
                return userName;
            }
        }.execute();
    }

    public LiveData<String> getUserLiveData() {
        return userLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }
}
復(fù)制代碼

UserViewModel繼承ViewModel可很,然后邏輯很簡單:假裝網(wǎng)絡(luò)請求 2s后 返回用戶信息,其中userLiveData用于拋出用戶信息凰浮,loadingLiveData用于控制進(jìn)度條顯示我抠。

再看UI層:

public class UserActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        Log.i(TAG, "onCreate: ");

        TextView tvUserName = findViewById(R.id.textView);
        ProgressBar pbLoading = findViewById(R.id.pb_loading);
    //獲取ViewModel實(shí)例
        ViewModelProvider viewModelProvider = new ViewModelProvider(this);
        UserViewModel userViewModel = viewModelProvider.get(UserViewModel.class);
        //觀察 用戶信息
        userViewModel.getUserLiveData().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                // update ui.
                tvUserName.setText(s);
            }
        });

        userViewModel.getLoadingLiveData().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean aBoolean) {
                pbLoading.setVisibility(aBoolean?View.VISIBLE:View.GONE);
            }
        });
        //點(diǎn)擊按鈕獲取用戶信息
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                userViewModel.getUserInfo();
            }
        });
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.i(TAG, "onStop: ");
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }
}
復(fù)制代碼

頁面有個(gè)按鈕用于點(diǎn)擊獲取用戶信息,有個(gè)TextView展示用戶信息袜茧。 在onCreate()中先 創(chuàng)建ViewModelProvider實(shí)例菜拓,傳入的參數(shù)是ViewModelStoreOwner,Activity和Fragment都是其實(shí)現(xiàn)笛厦。然后通過ViewModelProvider的get方法 獲取ViewModel實(shí)例纳鼎,然后就是 觀察ViewModel中的LiveData

運(yùn)行后递递,點(diǎn)擊按鈕 會(huì)彈出進(jìn)度條喷橙,2s后展示用戶信息。接著旋轉(zhuǎn)手機(jī)登舞,我們發(fā)現(xiàn)用戶信息依然存在贰逾。來看下效果:

Activity旋轉(zhuǎn)重建后數(shù)據(jù)恢復(fù)

旋轉(zhuǎn)手機(jī)后確實(shí)是重建了Activity的,日志打印如下:

2021-01-06 20:35:44.984 28269-28269/com.hfy.androidlearning I/UserActivity: onStop: 
2021-01-06 20:35:44.986 28269-28269/com.hfy.androidlearning I/UserActivity: onDestroy: 
2021-01-06 20:35:45.025 28269-28269/com.hfy.androidlearning I/UserActivity: onCreate: 
復(fù)制代碼

總結(jié)下:

  1. ViewModel的使用很簡單菠秒,作用和原來的Presenter一致疙剑。只是要結(jié)合LiveData氯迂,UI層觀察即可。
  2. ViewModel的創(chuàng)建必須通過ViewModelProvider言缤。
  3. 注意到ViewModel中沒有持有任何UI相關(guān)的引用嚼蚀。
  4. 旋轉(zhuǎn)手機(jī)重建Activity后,數(shù)據(jù)確實(shí)恢復(fù)了管挟。

2.2 Fragment間數(shù)據(jù)共享

Activity 中的多個(gè)Fragment需要相互通信是一種很常見的情況轿曙。假設(shè)有一個(gè)ListFragment,用戶從列表中選擇一項(xiàng)僻孝,會(huì)有另一個(gè)DetailFragment顯示選定項(xiàng)的詳情內(nèi)容导帝。在之前 你可能會(huì)定義接口或者使用EventBus來實(shí)現(xiàn)數(shù)據(jù)的傳遞共享。

現(xiàn)在就可以使用 ViewModel 來實(shí)現(xiàn)穿铆。這兩個(gè) Fragment 可以使用其 Activity 范圍共享 ViewModel 來處理此類通信您单,如以下示例代碼所示:

//ViewModel
public class SharedViewModel extends ViewModel {
//被選中的Item
    private final MutableLiveData<UserContent.UserItem> selected = new MutableLiveData<UserContent.UserItem>();

    public void select(UserContent.UserItem user) {
        selected.setValue(user);
    }
    public LiveData<UserContent.UserItem> getSelected() {
        return selected;
    }
}

//ListFragment
public class MyListFragment extends Fragment {
   ...
    private SharedViewModel model;
   ...
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //獲取ViewModel,注意ViewModelProvider實(shí)例傳入的是宿主Activity
        model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        adapter.setListner(new MyItemRecyclerViewAdapter.ItemCLickListner(){
            @Override
            public void onClickItem(UserContent.UserItem userItem) {
                model.select(userItem);
            }
        });
    }
}

//DetailFragment
public class DetailFragment extends Fragment {

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextView detail = view.findViewById(R.id.tv_detail);
        //獲取ViewModel,觀察被選中的Item
        SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        model.getSelected().observe(getViewLifecycleOwner(), new Observer<UserContent.UserItem>() {
            @Override
            public void onChanged(UserContent.UserItem userItem) {
                //展示詳情
                detail.setText(userItem.toString());
            }
        });
    }
}
復(fù)制代碼

代碼很簡單荞雏,ListFragment中在點(diǎn)擊Item時(shí)更新ViewModel的LiveData數(shù)據(jù)虐秦,然后DetailFragment監(jiān)聽這個(gè)LiveData數(shù)據(jù)即可。

要注意的是凤优,這兩個(gè) Fragment 通過ViewModelProvider獲取ViewModel時(shí) 傳入的都是它們宿主Activity悦陋。這樣,當(dāng)這兩個(gè) Fragment 各自獲取 ViewModelProvider 時(shí)别洪,它們會(huì)收到相同的 SharedViewModel 實(shí)例(其范圍限定為該 Activity)叨恨。

此方法具有以下 優(yōu)勢

  1. Activity 不需要執(zhí)行任何操作,也不需要對(duì)此通信有任何了解挖垛。
  2. 除了 SharedViewModel 約定之外,F(xiàn)ragment 不需要相互了解秉颗。如果其中一個(gè) Fragment 消失痢毒,另一個(gè) Fragment 將繼續(xù)照常工作。
  3. 每個(gè) Fragment 都有自己的生命周期蚕甥,而不受另一個(gè) Fragment 的生命周期的影響哪替。如果一個(gè) Fragment 替換另一個(gè) Fragment,界面將繼續(xù)工作而沒有任何問題菇怀。

最后來看下效果:

Activity內(nèi)部多個(gè)Fragment通過ViewModel共享數(shù)據(jù)

三凭舶、源碼分析

經(jīng)過前面的介紹,我們知道ViewModel的核心點(diǎn) 就是 因配置更新而界面(Activity/Fragment)重建后爱沟,ViewModel實(shí)例依然存在帅霜,這個(gè)如何實(shí)現(xiàn)的呢? 這就是我們源碼分析的重點(diǎn)了呼伸。

在獲取ViewModel實(shí)例時(shí)身冀,我們并不是直接new的,而是使用ViewModelProvider來獲取,猜測關(guān)鍵點(diǎn)應(yīng)該就在這里了搂根。

3.1 ViewModel的存儲(chǔ)和獲取

先來看下ViewModel類:

public abstract class ViewModel {
    ...
    private volatile boolean mCleared = false;
    //在ViewModel將被清除時(shí)調(diào)用
    //當(dāng)ViewModel觀察了一些數(shù)據(jù)珍促,可以在這里做解注冊 防止內(nèi)存泄漏
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
    @MainThread
    final void clear() {
        mCleared = true;
        ...
        onCleared();
    }
...
}
復(fù)制代碼

ViewModel類 是抽象類,內(nèi)部沒有啥邏輯剩愧,有個(gè)clear()方法會(huì)在ViewModel將被清除時(shí)調(diào)用猪叙。

然后ViewModel實(shí)例的獲取是通過ViewModelProvider類,見名知意仁卷,即ViewModel提供者沐悦,來看下它的構(gòu)造方法:

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
復(fù)制代碼

例子中我們使用的是只需傳ViewModelStoreOwner的構(gòu)造方法,最后走到兩個(gè)參數(shù)ViewModelStore五督、factory的構(gòu)造方法藏否。繼續(xù)見名知意:ViewModelStoreOwner——ViewModel存儲(chǔ)器擁有者;ViewModelStore——ViewModel存儲(chǔ)器充包,用來存ViewModel的地方副签;Factory——?jiǎng)?chuàng)建ViewModel實(shí)例的工廠。

ViewModelStoreOwner是個(gè)接口:

public interface ViewModelStoreOwner {
    //獲取ViewModelStore基矮,即獲取ViewModel存儲(chǔ)器
    ViewModelStore getViewModelStore();
}
復(fù)制代碼

實(shí)現(xiàn)類有Activity/Fragment淆储,也就是說 Activity/Fragment 都是 ViewModel存儲(chǔ)器的擁有者,具體是怎樣實(shí)現(xiàn) 獲取ViewModelStore的呢家浇?

先不急本砰,我們先看 ViewModelStore 如何存儲(chǔ)ViewModel、以及ViewModel實(shí)例如何獲取的钢悲。

/**
 * 用于存儲(chǔ)ViewModels.
 * ViewModelStore實(shí)例 必須要能 在系統(tǒng)配置改變后 依然存在点额。
 */
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);
    }
    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }
    /**
     * 調(diào)用ViewModel的clear()方法,然后清除ViewModel
     * 如果ViewModelStore的擁有者(Activity/Fragment)銷毀后不會(huì)重建莺琳,那么就需要調(diào)用此方法
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}
復(fù)制代碼

ViewModelStore代碼很簡單还棱,viewModel作為Value存儲(chǔ)在HashMap中。

再來看下創(chuàng)建ViewModel實(shí)例的工廠Factory惭等,也就是NewInstanceFactory:

    public static class NewInstanceFactory implements Factory {
    ...
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }
復(fù)制代碼

很簡單珍手,就是通過傳入的class 反射獲取ViewModel實(shí)例。

回到例子中辞做,我們使用viewModelProvider.get(UserViewModel.class)來獲取UserViewModel實(shí)例琳要,那么來看下get()方法:

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");

    //拿到Key,也即是ViewModelStore中的Map的用于存 ViewModel的 Key
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //從ViewModelStore獲取ViewModel實(shí)例
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            //如果從ViewModelStore獲取到秤茅,直接返回
            return (T) viewModel;
        } 

        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
        //沒有獲取到稚补,就使用Factory創(chuàng)建
            viewModel = (mFactory).create(modelClass);
        }
        //存入ViewModelStore 然后返回
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
復(fù)制代碼

邏輯很清晰,先嘗試從ViewModelStore獲取ViewModel實(shí)例嫂伞,key是"androidx.lifecycle.ViewModelProvider.DefaultKey:xxx.SharedViewModel"孔厉,如果沒有獲取到拯钻,就使用Factory創(chuàng)建,然后存入ViewModelStore撰豺。

到這里粪般,我們知道了 ViewModel如何存儲(chǔ)、實(shí)例如何獲取的污桦,但開頭說的分析重點(diǎn):“因配置更新而界面重建后亩歹,ViewModel實(shí)例依然存在”,這個(gè)還沒分析到凡橱。

3.2 ViewModelStore的存儲(chǔ)和獲取

回到上面的疑問小作,看看 Activity/Fragment 是怎樣實(shí)現(xiàn) 獲取ViewModelStore的,先來看ComponentActivity中對(duì)ViewModelStoreOwner的實(shí)現(xiàn):

//ComponentActivity.java
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
        //activity還沒關(guān)聯(lián)Application稼钩,即不能在onCreate之前去獲取viewModel
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
        //如果存儲(chǔ)器是空顾稀,就先嘗試 從lastNonConfigurationInstance從獲取
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
            //如果lastNonConfigurationInstance不存在,就new一個(gè)
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
復(fù)制代碼

這里就是重點(diǎn)了坝撑。先嘗試 從NonConfigurationInstance從獲取 ViewModelStore實(shí)例静秆,如果NonConfigurationInstance不存在,就new一個(gè)mViewModelStore巡李。 并且還注意到抚笔,在onRetainNonConfigurationInstance()方法中 會(huì)把mViewModelStore賦值給NonConfigurationInstances:

    //在Activity因配置改變 而正要銷毀時(shí),且新Activity會(huì)立即創(chuàng)建侨拦,那么系統(tǒng)就會(huì)調(diào)用此方法
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        ...
        if (viewModelStore == null && custom == null) {
            return null;
        }

    //new了一個(gè)NonConfigurationInstances殊橙,mViewModelStore賦值過來
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
復(fù)制代碼

onRetainNonConfigurationInstance()方法很重要:在Activity因配置改變 而正要銷毀時(shí),且新Activity會(huì)立即創(chuàng)建狱从,那么系統(tǒng)就會(huì)調(diào)用此方法膨蛮。 也就說,配置改變時(shí) 系統(tǒng)把viewModelStore存在了NonConfigurationInstances中矫夯。

NonConfigurationInstances是個(gè)啥呢鸽疾?

//ComponentActivity
    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
復(fù)制代碼

ComponentActivity靜態(tài)內(nèi)部類,依然見名知意训貌,非配置實(shí)例,即 與系統(tǒng)配置 無關(guān)的 實(shí)例冒窍。所以屏幕旋轉(zhuǎn)等的配置改變 不會(huì)影響到這個(gè)實(shí)例递沪? 繼續(xù)看這個(gè)猜想是否正確。

我們看下getLastNonConfigurationInstance():

//Acticity.java

NonConfigurationInstances mLastNonConfigurationInstances;

//返回onRetainNonConfigurationInstance()返回的實(shí)例
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null;
}

static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }
復(fù)制代碼

方法是在Acticity.java中综液,它返回的是Acticity.java中的NonConfigurationInstances的屬性activity款慨,也就是onRetainNonConfigurationInstance()方法返回的實(shí)例。(注意上面那個(gè)是ComponentActivity中的NonConfigurationInstances谬莹,是兩個(gè)類)

來繼續(xù)看mLastNonConfigurationInstances是哪來的檩奠,通過尋找調(diào)用找到在attach()方法中:

final void attach(Context context, ActivityThread aThread, ...
            NonConfigurationInstances lastNonConfigurationInstances,... ) {
            ...
            mLastNonConfigurationInstances = lastNonConfigurationInstances;
            ...
       }
復(fù)制代碼

mLastNonConfigurationInstances是在Activity的attach方法中賦值桩了。 在《Activity的啟動(dòng)過程詳解》中我們分析過,attach方法是為Activity關(guān)聯(lián)上下文環(huán)境埠戳,是在Activity 啟動(dòng)的核心流程——ActivityThread的performLaunchActivity方法中調(diào)用井誉,這里的lastNonConfigurationInstances是存在 ActivityClientRecord中的一個(gè)組件信息。

ActivityClientRecord是存在ActivityThread的mActivities中:

//ActivityThrtead.java
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
復(fù)制代碼

那么整胃,ActivityThread 中的 ActivityClientRecord 是不受 activity 重建的影響颗圣,那么ActivityClientRecord中l(wèi)astNonConfigurationInstances也不受影響,那么其中的Object activity也不受影響屁使,那么ComponentActivity中的NonConfigurationInstances的viewModelStore不受影響在岂,那么viewModel也就不受影響了。

那么蛮寂,到這里 核心問題 “配置更改重建后ViewModel依然存在” 的原理就分析完了蔽午。

四、對(duì)比onSaveInstanceState()

系統(tǒng)提供了onSaveInstanceState()用于讓開發(fā)者保存一些數(shù)據(jù)酬蹋,以方便界面銷毀重建時(shí)恢復(fù)數(shù)據(jù)及老。那么和 使用ViewModel恢復(fù)數(shù)據(jù) 有哪些區(qū)別呢?

4.1 使用場景

在我很久之前一篇文章《Activity生命周期》中有提到:

onSaveInstanceState調(diào)用時(shí)機(jī):

當(dāng)某個(gè)activity變得“容易”被系統(tǒng)銷毀時(shí)除嘹,該activity的onSaveInstanceState就會(huì)被執(zhí)行写半,除非該activity是被用戶主動(dòng)銷毀的,例如當(dāng)用戶按BACK鍵的時(shí)候尉咕。 注意上面的雙引號(hào)叠蝇,何為“容易”?言下之意就是該activity還沒有被銷毀年缎,而僅僅是一種可能性悔捶。

這種可能性有哪些?有這么幾種情況:

1单芜、當(dāng)用戶按下HOME鍵時(shí)蜕该。 這是顯而易見的,系統(tǒng)不知道你按下HOME后要運(yùn)行多少其他的程序洲鸠,自然也不知道activity A是否會(huì)被銷毀堂淡,故系統(tǒng)會(huì)調(diào)用onSaveInstanceState,讓用戶有機(jī)會(huì)保存某些非永久性的數(shù)據(jù)扒腕。以下幾種情況的分析都遵循該原則 绢淀。

2、長按HOME鍵瘾腰,選擇運(yùn)行其他的程序時(shí)皆的。

3、按下電源按鍵(關(guān)閉屏幕顯示)時(shí)蹋盆。

4费薄、從activity A中啟動(dòng)一個(gè)新的activity時(shí)硝全。

5、屏幕方向切換時(shí)楞抡,例如從豎屏切換到橫屏?xí)r伟众。 在屏幕切換之前,系統(tǒng)會(huì)銷毀activity A拌倍,在屏幕切換之后系統(tǒng)又會(huì)自動(dòng)地創(chuàng)建activity A赂鲤,所以onSaveInstanceState一定會(huì)被執(zhí)行。

總而言之柱恤,onSaveInstanceState的調(diào)用遵循一個(gè)重要原則数初,即當(dāng)系統(tǒng)“未經(jīng)你許可”時(shí)銷毀了你的activity,則onSaveInstanceState會(huì)被系統(tǒng)調(diào)用梗顺,這是系統(tǒng)的責(zé)任泡孩,因?yàn)樗仨氁峁┮粋€(gè)機(jī)會(huì)讓你保存你的數(shù)據(jù)(當(dāng)然你不保存那就隨便你了)。

而使用ViewModel恢復(fù)數(shù)據(jù) 則 只有在 因配置更改界面銷毀重建 的情況寺谤。

4.2 存儲(chǔ)方式

ViewModel是存在內(nèi)存中仑鸥,讀寫速度快,而通過onSaveInstanceState是在 序列化到磁盤中变屁。

4.3 存儲(chǔ)數(shù)據(jù)的限制

ViewModel眼俊,可以存復(fù)雜數(shù)據(jù),大小限制就是App的可用內(nèi)存粟关。而 onSaveInstanceState只能存可序列化和反序列化的對(duì)象疮胖,且大小有限制(一般Bundle限制大小1M)。

五闷板、總結(jié)

本文先介紹了ViewModel的概念——為界面準(zhǔn)備數(shù)據(jù)的模型澎灸,然后它的特點(diǎn):因配置更改界面銷毀重建后依然存在、不持有UI應(yīng)用遮晚;接著介紹了 使用方式性昭、Fragment數(shù)據(jù)共享。最后詳細(xì)分析了ViewModel源碼及核心原理县遣。

并且可以看到LiveData和ViewModel搭配使用糜颠,可以代替MVP中的Presenter解決很多問題。ViewModel是我們后續(xù)建立MVVM架構(gòu)的重要組件萧求。 這也是我們必須掌握和理解的部分括蝠。

下一篇將介紹基于LifeCycle、LiveData饭聚、ViewModel的MVVM架構(gòu),終于要到MVVM了搁拙,敬請關(guān)注秒梳。

今天就到這里啦~

六法绵、分享

分享一份《Jetpack架構(gòu)組件從入門到精通》的pdf學(xué)習(xí)筆記給大家,內(nèi)容涵括了Jetpack幾乎所有你能想到的知識(shí)點(diǎn)酪碘,而每一個(gè)知識(shí)點(diǎn)都有詳細(xì)的源碼解析朋譬,以及實(shí)戰(zhàn)講解!

需要的小伙伴兴垦,可以點(diǎn)擊這里直接獲柔阌!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末探越,一起剝皮案震驚了整個(gè)濱河市狡赐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钦幔,老刑警劉巖枕屉,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鲤氢,居然都是意外死亡搀擂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門卷玉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哨颂,“玉大人,你說我怎么就攤上這事相种⊥眨” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵蚂子,是天一觀的道長沃测。 經(jīng)常有香客問我,道長食茎,這世上最難降的妖魔是什么蒂破? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮别渔,結(jié)果婚禮上附迷,老公的妹妹穿的比我還像新娘。我一直安慰自己哎媚,他們只是感情好喇伯,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拨与,像睡著了一般稻据。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上买喧,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天捻悯,我揣著相機(jī)與錄音匆赃,去河邊找鬼。 笑死今缚,一個(gè)胖子當(dāng)著我的面吹牛算柳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姓言,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼瞬项,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了何荚?” 一聲冷哼從身側(cè)響起囱淋,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兽泣,沒想到半個(gè)月后绎橘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唠倦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年称鳞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稠鼻。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冈止,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出候齿,到底是詐尸還是另有隱情熙暴,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布慌盯,位于F島的核電站周霉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏亚皂。R本人自食惡果不足惜俱箱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灭必。 院中可真熱鬧狞谱,春花似錦、人聲如沸禁漓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽播歼。三九已至伶跷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撩穿。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工磷支, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人食寡。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像廓潜,于是被迫代替她去往敵國和親抵皱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容