“終于懂了“系列:Jetpack AAC完整解析(三)ViewModel 完全掌握晶乔!

Jetpack AAC 系列文章:

“終于懂了“系列:Jetpack AAC完整解析(一)Lifecycle 完全掌握珍坊!

“終于懂了“系列:Jetpack AAC完整解析(二)LiveData 完全掌握!

“終于懂了“系列:Jetpack AAC完整解析(三)ViewModel 完全掌握正罢!

“終于懂了“系列:Jetpack AAC完整解析(四)MVVM - Android架構(gòu)探索阵漏!

“終于懂了“系列:Jetpack AAC完整解析(五)DataBinding 重新認知!

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

注意虑乖,如果你對MVVM架構(gòu)中的VM和本篇的ViewModel都沒有一定認識的話,那么就不要將兩者進行聯(lián)想了晾虑。目前疹味,你就理解為沒有任何關系。后面會有專門篇幅介紹MVVM帜篇。

一糙捺、ViewModel介紹

ViewModel是Jetpack AAC的重要組件,同時也有一個同名抽象類笙隙。

ViewModel洪灯,意為 視圖模型,即 為界面準備數(shù)據(jù)的模型竟痰。簡單理解就是签钩,ViewModel為UI層提供數(shù)據(jù)。
官方文檔定義如下:

ViewModel 以注重生命周期的方式存儲和管理界面相關的數(shù)據(jù)坏快。(作用)

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

到這里,你可能還是不清楚ViewModel到底是干啥的莽鸿,別急昧旨,往下看拾给。

1.1 出場背景

在詳細介紹ViewModel前,先來看下背景和問題點兔沃。

  1. Activity可能會在某些場景(例如屏幕旋轉(zhuǎn))銷毀和重新創(chuàng)建界面蒋得,那么存儲在其中的界面相關數(shù)據(jù)都會丟失。例如乒疏,界面含用戶信息列表额衙,因配置更改而重新創(chuàng)建 Activity 后,新 Activity 必須重新請求用戶列表怕吴,這會造成資源的浪費入偷。能否直接恢復之前的數(shù)據(jù)呢?對于簡單的數(shù)據(jù)械哟,Activity 可以使用 onSaveInstanceState() 方法保存 然后從 onCreate() 中的Bundle恢復數(shù)據(jù),但此方法僅適合可以序列化再反序列化的少量數(shù)據(jù)(IPC對Bundle有1M的限制)殿雪,而不適合數(shù)量可能較大的數(shù)據(jù)暇咆,如用戶信息列表或位圖。 那么如何做到 因配置更改而新建Activity后的數(shù)據(jù)恢復呢丙曙?

  2. UI層(如 Activity 和 Fragment)經(jīng)常需要通過邏輯層(如MVP中的Presenter)進行異步請求爸业,可能需要一些時間才能返回結(jié)果,如果邏輯層持有UI層應用(如context)亏镰,那么UI層需要管理這些請求扯旷,確保界面銷毀后清理這些調(diào)用以避免潛在的內(nèi)存泄露,但此項管理需要大量的維護工作索抓。 那么如何更好的避免因異步請求帶來的內(nèi)存泄漏呢钧忽?

這時候ViewModel就閃亮出場了——ViewModel用于代替MVP中的Presenter,為UI層準備數(shù)據(jù)逼肯,用于解決上面兩個問題耸黑。

1.2 特點

具體地,相比于Presenter篮幢,ViewModel有以下特點:

1.2.1 生命周期長于Activity

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

ViewModel生命周期

看到在因屏幕旋轉(zhuǎn)而重新創(chuàng)建Activity后,ViewModel對象依然會保留三椿。 只有Activity真正Finish的時ViewModel才會被清除缺菌。

也就是說,因系統(tǒng)配置變更Activity銷毀重建搜锰,ViewModel對象會保留并關聯(lián)到新的Activity伴郁。而Activity的正常銷毀(系統(tǒng)不會重建Activity)時,ViewModel對象是會清除的纽乱。

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

1.2.2 不持有UI層引用

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

而ViewModel是不需要持有UI層引用的顽爹,那結(jié)果怎么給到UI層呢纤泵?答案就是使用上一篇中介紹的基于觀察者模式的LiveData。 并且镜粤,ViewModel也不能持有UI層引用捏题,因為ViewModel的生命周期更長。

所以肉渴,ViewModel不需要也不能 持有UI層引用公荧,那么就避免了可能的內(nèi)存泄漏,同時實現(xiàn)了解耦同规。這就解決了第二個問題循狰。

二、ViewModel使用

2.1 基本使用

了解了ViewModel作用解特點券勺,下面來看看如何結(jié)合LivaData使用的绪钥。(gradle依賴在第一篇中已經(jīng)介紹過了。)

步驟:

  1. 繼承ViewModel自定義MyViewModel
  2. 在MyViewModel中編寫獲取UI數(shù)據(jù)的邏輯
  3. 使用LiveData將獲取到的UI數(shù)據(jù)拋出
  4. 在Activity/Fragment中使用ViewModelProvider獲取MyViewModel實例
  5. 觀察MyViewModel中的LiveData數(shù)據(jù)关炼,進行對應的UI更新程腹。

舉個例子,如果您需要在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)絡請求 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 = "我是胡飛洋,公眾號名字也是胡飛洋社痛,歡迎關注~";
                return userName;
            }
        }.execute();
    }
    
    public LiveData<String> getUserLiveData() {
        return userLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }
}

UserViewModel繼承ViewModel甸祭,然后邏輯很簡單:假裝網(wǎng)絡請求 2s后 返回用戶信息,其中userLiveData用于拋出用戶信息褥影,loadingLiveData用于控制進度條顯示池户。

再看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實例
        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);
            }
        });
        //點擊按鈕獲取用戶信息
        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: ");
    }
}

頁面有個按鈕用于點擊獲取用戶信息,有個TextView展示用戶信息。 在onCreate()中先 創(chuàng)建ViewModelProvider實例,傳入的參數(shù)是ViewModelStoreOwner翔试,Activity和Fragment都是其實現(xiàn)粒梦。然后通過ViewModelProvider的get方法 獲取ViewModel實例,然后就是 觀察ViewModel中的LiveData

運行后,點擊按鈕 會彈出進度條,2s后展示用戶信息耸成。接著旋轉(zhuǎn)手機报亩,我們發(fā)現(xiàn)用戶信息依然存在。來看下效果:

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

旋轉(zhuǎn)手機后確實是重建了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: 

總結(jié)下:

  1. ViewModel的使用很簡單弦追,作用和原來的Presenter一致。只是要結(jié)合LiveData花竞,UI層觀察即可劲件。
  2. ViewModel的創(chuàng)建必須通過ViewModelProvider。
  3. 注意到ViewModel中沒有持有任何UI相關的引用约急。
  4. 旋轉(zhuǎn)手機重建Activity后零远,數(shù)據(jù)確實恢復了。

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

Activity 中的多個Fragment需要相互通信是一種很常見的情況厌蔽。假設有一個ListFragment牵辣,用戶從列表中選擇一項,會有另一個DetailFragment顯示選定項的詳情內(nèi)容奴饮。在之前 你可能會定義接口或者使用EventBus來實現(xiàn)數(shù)據(jù)的傳遞共享服猪。

現(xiàn)在就可以使用 ViewModel 來實現(xiàn)。這兩個 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實例傳入的是宿主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());
            }
        });
    }
}

代碼很簡單近她,ListFragment中在點擊Item時更新ViewModel的LiveData數(shù)據(jù)叉瘩,然后DetailFragment監(jiān)聽這個LiveData數(shù)據(jù)即可。

要注意的是粘捎,這兩個 Fragment 通過ViewModelProvider獲取ViewModel時 傳入的都是它們宿主Activity薇缅。這樣,當這兩個 Fragment 各自獲取 ViewModelProvider 時攒磨,它們會收到相同的 SharedViewModel 實例(其范圍限定為該 Activity)泳桦。

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

  1. Activity 不需要執(zhí)行任何操作,也不需要對此通信有任何了解娩缰。
  2. 除了 SharedViewModel 約定之外灸撰,F(xiàn)ragment 不需要相互了解。如果其中一個 Fragment 消失拼坎,另一個 Fragment 將繼續(xù)照常工作浮毯。
  3. 每個 Fragment 都有自己的生命周期,而不受另一個 Fragment 的生命周期的影響泰鸡。如果一個 Fragment 替換另一個 Fragment债蓝,界面將繼續(xù)工作而沒有任何問題。

最后來看下效果:

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

三盛龄、源碼分析

經(jīng)過前面的介紹饰迹,我們知道ViewModel的核心點 就是 因配置更新而界面(Activity/Fragment)重建后芳誓,ViewModel實例依然存在,這個如何實現(xiàn)的呢啊鸭? 這就是我們源碼分析的重點了锹淌。

在獲取ViewModel實例時,我們并不是直接new的莉掂,而是使用ViewModelProvider來獲取葛圃,猜測關鍵點應該就在這里了。

3.1 ViewModel的存儲和獲取

先來看下ViewModel類:

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

ViewModel類 是抽象類库正,內(nèi)部沒有啥邏輯,有個clear()方法會在ViewModel將被清除時調(diào)用厘唾。

然后ViewModel實例的獲取是通過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;
    }

例子中我們使用的是只需傳ViewModelStoreOwner的構(gòu)造方法喷楣,最后走到兩個參數(shù)ViewModelStore、factory的構(gòu)造方法鹤树。繼續(xù)見名知意:ViewModelStoreOwner——ViewModel存儲器擁有者铣焊;ViewModelStore——ViewModel存儲器,用來存ViewModel的地方罕伯;Factory——創(chuàng)建ViewModel實例的工廠曲伊。

ViewModelStoreOwner是個接口:

public interface ViewModelStoreOwner {
    //獲取ViewModelStore,即獲取ViewModel存儲器
    ViewModelStore getViewModelStore();
}

實現(xiàn)類有Activity/Fragment追他,也就是說 Activity/Fragment 都是 ViewModel存儲器的擁有者坟募,具體是怎樣實現(xiàn) 獲取ViewModelStore的呢?

先不急邑狸,我們先看 ViewModelStore 如何存儲ViewModel懈糯、以及ViewModel實例如何獲取的。

/**
 * 用于存儲ViewModels.
 * ViewModelStore實例 必須要能 在系統(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)銷毀后不會重建,那么就需要調(diào)用此方法
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModelStore代碼很簡單硅堆,viewModel作為Value存儲在HashMap中蜂奸。

再來看下創(chuàng)建ViewModel實例的工廠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);
            }
        }
    }

很簡單硬萍,就是通過傳入的class 反射獲取ViewModel實例扩所。

回到例子中,我們使用viewModelProvider.get(UserViewModel.class)來獲取UserViewModel實例朴乖,那么來看下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實例
        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;
    }

邏輯很清晰袁勺,先嘗試從ViewModelStore獲取ViewModel實例雹食,key是"androidx.lifecycle.ViewModelProvider.DefaultKey:xxx.SharedViewModel",如果沒有獲取到期丰,就使用Factory創(chuàng)建群叶,然后存入ViewModelStore。

到這里钝荡,我們知道了 ViewModel如何存儲街立、實例如何獲取的,但開頭說的分析重點:“因配置更新而界面重建后埠通,ViewModel實例依然存在”赎离,這個還沒分析到。

3.2 ViewModelStore的存儲和獲取

回到上面的疑問端辱,看看 Activity/Fragment 是怎樣實現(xiàn) 獲取ViewModelStore的梁剔,先來看ComponentActivity中對ViewModelStoreOwner的實現(xiàn):

//ComponentActivity.java
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
        //activity還沒關聯(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) {
        //如果存儲器是空舞蔽,就先嘗試 從lastNonConfigurationInstance從獲取
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
            //如果lastNonConfigurationInstance不存在荣病,就new一個
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

這里就是重點了。先嘗試 從NonConfigurationInstance從獲取 ViewModelStore實例渗柿,如果NonConfigurationInstance不存在个盆,就new一個mViewModelStore。 并且還注意到做祝,在onRetainNonConfigurationInstance()方法中 會把mViewModelStore賦值給NonConfigurationInstances:

    //在Activity因配置改變 而正要銷毀時,且新Activity會立即創(chuàng)建鸡岗,那么系統(tǒng)就會調(diào)用此方法
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
        
        ViewModelStore viewModelStore = mViewModelStore;
        ...
        if (viewModelStore == null && custom == null) {
            return null;
        }

    //new了一個NonConfigurationInstances混槐,mViewModelStore賦值過來
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

onRetainNonConfigurationInstance()方法很重要:在Activity因配置改變 而正要銷毀時,且新Activity會立即創(chuàng)建轩性,那么系統(tǒng)就會調(diào)用此方法声登。 也就說,配置改變時 系統(tǒng)把viewModelStore存在了NonConfigurationInstances中揣苏。

NonConfigurationInstances是個啥呢悯嗓?

//ComponentActivity
    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }

ComponentActivity靜態(tài)內(nèi)部類,依然見名知意卸察,非配置實例脯厨,即 與系統(tǒng)配置 無關的 實例。所以屏幕旋轉(zhuǎn)等的配置改變 不會影響到這個實例坑质? 繼續(xù)看這個猜想是否正確合武。

我們看下getLastNonConfigurationInstance():

//Acticity.java

NonConfigurationInstances mLastNonConfigurationInstances;

//返回onRetainNonConfigurationInstance()返回的實例
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;
    }

方法是在Acticity.java中临梗,它返回的是Acticity.java中的NonConfigurationInstances的屬性activity,也就是onRetainNonConfigurationInstance()方法返回的實例稼跳。(注意上面那個是ComponentActivity中的NonConfigurationInstances盟庞,是兩個類)

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

final void attach(Context context, ActivityThread aThread, ...
            NonConfigurationInstances lastNonConfigurationInstances,... ) {
            ...
            mLastNonConfigurationInstances = lastNonConfigurationInstances;
            ...
       }

mLastNonConfigurationInstances是在Activity的attach方法中賦值汤善。 在《Activity的啟動過程詳解》中我們分析過什猖,attach方法是為Activity關聯(lián)上下文環(huán)境,是在Activity 啟動的核心流程——ActivityThread的performLaunchActivity方法中調(diào)用红淡,這里的lastNonConfigurationInstances是存在 ActivityClientRecord中的一個組件信息不狮。

ActivityClientRecord是存在ActivityThread的mActivities中:

//ActivityThrtead.java
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

那么,ActivityThread 中的 ActivityClientRecord 是不受 activity 重建的影響锉屈,那么ActivityClientRecord中l(wèi)astNonConfigurationInstances也不受影響荤傲,那么其中的Object activity也不受影響,那么ComponentActivity中的NonConfigurationInstances的viewModelStore不受影響颈渊,那么viewModel也就不受影響了遂黍。

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

四雾家、對比onSaveInstanceState()

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

4.1 使用場景

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

onSaveInstanceState調(diào)用時機:

當某個activity變得“容易”被系統(tǒng)銷毀時,該activity的onSaveInstanceState就會被執(zhí)行竹揍,除非該activity是被用戶主動銷毀的敬飒,例如當用戶按BACK鍵的時候。 注意上面的雙引號芬位,何為“容易”无拗?言下之意就是該activity還沒有被銷毀,而僅僅是一種可能性昧碉。

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

1、當用戶按下HOME鍵時被饿。 這是顯而易見的四康,系統(tǒng)不知道你按下HOME后要運行多少其他的程序,自然也不知道activity A是否會被銷毀狭握,故系統(tǒng)會調(diào)用onSaveInstanceState闪金,讓用戶有機會保存某些非永久性的數(shù)據(jù)。以下幾種情況的分析都遵循該原則 论颅。

2毕泌、長按HOME鍵喝检,選擇運行其他的程序時。

3撼泛、按下電源按鍵(關閉屏幕顯示)時挠说。

4、從activity A中啟動一個新的activity時愿题。

5损俭、屏幕方向切換時,例如從豎屏切換到橫屏時潘酗。 在屏幕切換之前杆兵,系統(tǒng)會銷毀activity A,在屏幕切換之后系統(tǒng)又會自動地創(chuàng)建activity A仔夺,所以onSaveInstanceState一定會被執(zhí)行琐脏。

總而言之,onSaveInstanceState的調(diào)用遵循一個重要原則缸兔,即當系統(tǒng)“未經(jīng)你許可”時銷毀了你的activity日裙,則onSaveInstanceState會被系統(tǒng)調(diào)用,這是系統(tǒng)的責任惰蜜,因為它必須要提供一個機會讓你保存你的數(shù)據(jù)(當然你不保存那就隨便你了)昂拂。

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

4.2 存儲方式

ViewModel是存在內(nèi)存中抛猖,讀寫速度快格侯,而通過onSaveInstanceState是在 序列化到磁盤中。

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

ViewModel财著,可以存復雜數(shù)據(jù)联四,大小限制就是App的可用內(nèi)存。而 onSaveInstanceState只能存可序列化和反序列化的對象撑教,且大小有限制(一般Bundle限制大小1M)朝墩。

五、總結(jié)

本文先介紹了ViewModel的概念——為界面準備數(shù)據(jù)的模型驮履,然后它的特點:因配置更改界面銷毀重建后依然存在鱼辙、不持有UI應用廉嚼;接著介紹了 使用方式玫镐、Fragment數(shù)據(jù)共享。最后詳細分析了ViewModel源碼及核心原理怠噪。

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

下一篇將介紹基于LifeCycle葛闷、LiveData、ViewModel的MVVM架構(gòu)双藕,終于要到MVVM了淑趾,敬請關注。

今天就到這里啦~

.

感謝與參考:

ViewModel官方文檔

.

你的 點贊忧陪、評論扣泊,是對我的巨大鼓勵!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘶摊,一起剝皮案震驚了整個濱河市延蟹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叶堆,老刑警劉巖阱飘,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異虱颗,居然都是意外死亡沥匈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門上枕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咐熙,“玉大人,你說我怎么就攤上這事辨萍∑迥眨” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵锈玉,是天一觀的道長爪飘。 經(jīng)常有香客問我,道長拉背,這世上最難降的妖魔是什么师崎? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮椅棺,結(jié)果婚禮上犁罩,老公的妹妹穿的比我還像新娘。我一直安慰自己两疚,他們只是感情好床估,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诱渤,像睡著了一般丐巫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天递胧,我揣著相機與錄音碑韵,去河邊找鬼。 笑死缎脾,一個胖子當著我的面吹牛祝闻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遗菠,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼治筒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舷蒲?” 一聲冷哼從身側(cè)響起耸袜,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牲平,沒想到半個月后堤框,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡纵柿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年蜈抓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昂儒。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡沟使,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渊跋,到底是詐尸還是另有隱情腊嗡,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布拾酝,位于F島的核電站燕少,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蒿囤。R本人自食惡果不足惜客们,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望材诽。 院中可真熱鬧底挫,春花似錦、人聲如沸脸侥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湿痢。三九已至涝缝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間譬重,已是汗流浹背拒逮。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留臀规,地道東北人滩援。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像塔嬉,于是被迫代替她去往敵國和親玩徊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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