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

Jetpack AAC 系列文章:

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

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

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

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

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

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

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

一、ViewModel介紹

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

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

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

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

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

1.1 出場(chǎng)背景

在詳細(xì)介紹ViewModel前,先來(lái)看下背景和問(wèn)題點(diǎn)。

  1. Activity可能會(huì)在某些場(chǎng)景(例如屏幕旋轉(zhuǎn))銷毀和重新創(chuàng)建界面,那么存儲(chǔ)在其中的界面相關(guān)數(shù)據(jù)都會(huì)丟失。例如,界面含用戶信息列表,因配置更改而重新創(chuàng)建 Activity 后,新 Activity 必須重新請(qǐng)求用戶列表,這會(huì)造成資源的浪費(fèi)。能否直接恢復(fù)之前的數(shù)據(jù)呢?對(duì)于簡(jiǎn)單的數(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)常需要通過(guò)邏輯層(如MVP中的Presenter)進(jìn)行異步請(qǐng)求,可能需要一些時(shí)間才能返回結(jié)果,如果邏輯層持有UI層應(yīng)用(如context),那么UI層需要管理這些請(qǐng)求,確保界面銷毀后清理這些調(diào)用以避免潛在的內(nèi)存泄露,但此項(xiàng)管理需要大量的維護(hù)工作。 那么如何更好的避免因異步請(qǐng)求帶來(lái)的內(nèi)存泄漏呢?

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

1.2 特點(diǎn)

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

1.2.1 生命周期長(zhǎng)于Activity

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

ViewModel生命周期

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

也就是說(shuō),因系統(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è)問(wèn)題。

1.2.2 不持有UI層引用

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

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

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

二、ViewModel使用

2.1 基本使用

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

步驟:

  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ò)請(qǐ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 = "我是胡飛洋,公眾號(hào)名字也是胡飛洋,歡迎關(guān)注~";
                return userName;
            }
        }.execute();
    }
    
    public LiveData<String> getUserLiveData() {
        return userLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }
}

UserViewModel繼承ViewModel,然后邏輯很簡(jiǎn)單:假裝網(wǎng)絡(luò)請(qǐng)求 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: ");
    }
}

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

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

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: 

總結(jié)下:

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

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

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

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

//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());
            }
        });
    }
}

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

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

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

  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ù)工作而沒(méi)有任何問(wèn)題。

最后來(lái)看下效果:

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

三、源碼分析

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

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

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

先來(lái)看下ViewModel類:

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

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

然后ViewModel實(shí)例的獲取是通過(guò)ViewModelProvider類,見(jiàn)名知意,即ViewModel提供者,來(lái)看下它的構(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)造方法,最后走到兩個(gè)參數(shù)ViewModelStore、factory的構(gòu)造方法。繼續(xù)見(jiàn)名知意:ViewModelStoreOwner——ViewModel存儲(chǔ)器擁有者;ViewModelStore——ViewModel存儲(chǔ)器,用來(lái)存ViewModel的地方;Factory——?jiǎng)?chuàng)建ViewModel實(shí)例的工廠。

ViewModelStoreOwner是個(gè)接口:

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

實(shí)現(xiàn)類有Activity/Fragment,也就是說(shuō) 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();
    }
}

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

再來(lái)看下創(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);
            }
        }
    }

很簡(jiǎn)單,就是通過(guò)傳入的class 反射獲取ViewModel實(shí)例。

回到例子中,我們使用viewModelProvider.get(UserViewModel.class)來(lái)獲取UserViewModel實(shí)例,那么來(lái)看下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 {
        //沒(méi)有獲取到,就使用Factory創(chuàng)建
            viewModel = (mFactory).create(modelClass);
        }
        //存入ViewModelStore 然后返回
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

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

到這里,我們知道了 ViewModel如何存儲(chǔ)、實(shí)例如何獲取的,但開(kāi)頭說(shuō)的分析重點(diǎn):“因配置更新而界面重建后,ViewModel實(shí)例依然存在”,這個(gè)還沒(méi)分析到。

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

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

//ComponentActivity.java
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
        //activity還沒(méi)關(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;
    }

這里就是重點(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賦值過(guò)來(lái)
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

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

NonConfigurationInstances是個(gè)啥呢?

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

ComponentActivity靜態(tài)內(nèi)部類,依然見(jiàn)名知意,非配置實(shí)例,即 與系統(tǒng)配置 無(wú)關(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;
    }

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

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

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

mLastNonConfigurationInstances是在Activity的attach方法中賦值。 在《Activity的啟動(dòng)過(guò)程詳解》中我們分析過(guò),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<>();

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

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

四、對(duì)比onSaveInstanceState()

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

4.1 使用場(chǎng)景

在我很久之前一篇文章《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還沒(méi)有被銷毀,而僅僅是一種可能性。

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

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

2、長(zhǎng)按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)存中,讀寫速度快,而通過(guò)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解決很多問(wèn)題。ViewModel是我們后續(xù)建立MVVM架構(gòu)的重要組件。 這也是我們必須掌握和理解的部分。

下一篇將介紹基于LifeCycle、LiveData、ViewModel的MVVM架構(gòu),終于要到MVVM了,敬請(qǐng)關(guān)注。

今天就到這里啦~

.

感謝與參考:

ViewModel官方文檔

.

你的 點(diǎn)贊、評(píng)論,是對(duì)我的巨大鼓勵(lì)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

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