MVVM-ViewModel介紹與源碼解析

一 概述

ViewModel 在Activity或者Fragment生命週期內只有一個的存儲數據。ViewModel 裏面的數據不會由於屏幕的旋轉或者其餘配置(好比切換到多窗口模式)而丟失。可是在正常的finish()或者按返回鍵的時候,在Activity或者Fragment走到onDestroy的時候回清除ViewModel裏面的數據,避免內存泄漏。雖然屏幕旋轉Activity也會走onDestroy,可是會判斷是不是由於屏幕旋轉而致使的。因此ViewModel是一個很合格的存儲數據的類php

二 ViewModel生命週期

ViewModel 對象存在的時間比Activity的生命週期要長,禁止在ViewModel的持有Activity、Fragment、View等對象,這樣會引發內存泄漏。若是須要在ViewModel中引用Context的話,google爲咱們提供了AndroidViewModel 類來供咱們使用。java

![](https://user-gold-cdn.xitu.io/2020/5/21/17237572af65339b?w=1018&h=1090&f=png&s=126007)

三 ViewModel的好處

  1. 不會由於屏幕旋轉或者其餘配置改變(好比切換到多窗口模式)而銷燬,避免數據從新建立,好比網絡數據從新加載的狀況
  2. 當Activity或者Fragment的正常銷燬的時候,自動清除數據,也就是綁定了Activity或者Fragment的生命週期,避免了內存泄漏,
  3. 多個Fragment之間或者Activity和Fragment之間更好地傳遞數據,進行交互
  4. 減小Activityu或者Fragment的壓力,Activityu或者Fragment的只是顯示數據的界面,幫助Activity和Fragment處理一些數據邏輯

3.1 因爲官網的例子有點老,因此寫一個共享的例子

Activity 中的兩個或更多 Fragment 須要相互通訊是一種很常見的狀況。Activity和Fragment的之間互相調用,下面是一個Activity和兩個Fragment之間的互相調用android

具體的demo地址github

3.1.1 構建一個Avtivitygit

class ViewModelDemoActivity : AppCompatActivity() {
    // 建立以Activity爲維度的ViewModel
    private val viewModel: DemoViewModel by lazy { ViewModelProvider(this).get(DemoViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       var binding = DataBindingUtil.setContentView<ActivityViewmodelDemoBinding>(
            this,
            R.layout.activity_viewmodel_demo
        )
        viewModel.dataLive.observe(this, object : Observer<String> {
            override fun onChanged(s: String?) {
                // 當橫豎屏變換時,會從新走這裏,畢竟是View也都從新繪製了,只不過user裏面立馬有值
                tv_name.text = s
            }
        })

        tv_name.setOnClickListener{
            // activity 裏面的點擊去改變值
            viewModel.dataLive.value= "Activity觸發的改變"
        }
        viewModel.getName()
    }
    companion object {
        fun startMe(activity: Activity) {
            activity.startActivity(Intent(activity, ViewModelDemoActivity::class.java))
        }
    }
}
複製代碼

3.1.2 ViewModel的代碼

class DemoViewModel : ViewModel() {
     val dataLive: MutableLiveData<String> =
         MutableLiveData<String>()


    fun getName(){
        viewModelScope.launch {
            delay(1000)
            dataLive.value = "王者榮耀"
        }
    }
}
複製代碼

3.1.3 佈局文件

兩個Fragment是經過靜態方式添加到Activity的github

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">


    <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent">

        <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ED6E6E" android:textSize="30sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />

        <fragment android:id="@+id/one" android:name="com.nzy.mvvmsimple.viewmodel.ShareOneFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="@+id/guidline" app:layout_constraintTop_toBottomOf="@+id/tv_name" />

        <fragment android:id="@+id/two" app:layout_constraintTop_toBottomOf="@+id/tv_name" android:name="com.nzy.mvvmsimple.viewmodel.ShareTwoFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="@+id/guidline" app:layout_constraintRight_toRightOf="parent" />

        <androidx.constraintlayout.widget.Guideline android:id="@+id/guidline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.5" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
複製代碼

3.1.4 兩個 Fragment

注意: 兩個Fragment獲取ViewModel的維度,要以Activity爲維度數據庫

class ShareOneFragment : Fragment() {
    // 建立以Activity爲維度的ViewModel,注意 這裏是 requireActivity() 不是 getActivity(),requireActivity()不可能爲空,getActivity()有可能爲空
    private val viewModel: DemoViewModel
            by lazy { ViewModelProvider(requireActivity()).get(DemoViewModel::class.java) }

    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? {
        return inflater.inflate(R.layout.fragment_share_one, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        // 這裏傳入是 viewLifecycleOwner(getViewLifecycleOwner()),而不會Fragment自己
        viewModel.dataLive.observe(viewLifecycleOwner, Observer {
            tv_name_one.text = it
        })

        // 在 FragmentOne 中去改變值
        tv_one.setOnClickListener {
            viewModel.dataLive.value = "Fragment-One,改變的值"
        }
    }

    companion object {
        @JvmStatic
        fun newInstance() =
            ShareOneFragment()
    }
}

複製代碼

四 ViewModel 和 onSaveInstanceState 區別

4.1 ViewModel

  1. 緩存在內存中的,至關於本地緩存和網絡緩存讀寫比較快
  2. 能夠存較大的值,好比bitmap、大對象等等
  3. 數據綁定了Activity或者Fragment的生命週期,當Activity正常關閉的時候,都會清除ViewModel中的數據.好比
    • 調用finish()
    • 按返回鍵退出,
    • 用戶直接殺進程或者是放後臺內存不足,被回收了
  4. 在Activity旋轉或者其餘配置改變的時候,ViewModel裏面是還有值得,不會銷燬,
  5. ViewModel也可使用本地存儲,經過SavedStateHandle實現的,使用SavedStateViewModelFactory這個工廠,這裏就很少介紹了,已是正式版本了,引用是implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0

4.2 onSaveInstanceState

  1. 僅適合能夠序列化再反序列化的少許數據
  2. 不適合數量可能較大的數據,如用數組或Bitmap。能夠存一些簡單的基本類型和簡單的小對象、例如字符串,和一些id
  3. 會把數據存儲到磁盤上

注意 對於複雜的大型數據,請使用 數據庫 或者 SharedPreferences數組

五 ViewModel源碼解析

下面的Fragment 和 Activity 都是androidx 包下面的,而且 lifecycle的版本是 2.2.0緩存

5.1 源碼

ViewModel的源碼不多,基本上就是一個map 裏面用來存儲關於協程和SavedStateHandleController(暫時知道這個),onCleared() 方法是供咱們本身調用,就是當Activity或者Fragmengt的關閉的時候,須要清理一些資源,好比Rxjava的流bash

//來存儲 一些東西,當Activity 關閉的時候 會清除這裏的數據
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;

    //來存儲 一些東西,當Activity 關閉的時候 本身實現去清楚一些數據,好比Rxjava中的流
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
複製代碼

5.1.1 得到ViewModelProvider

private val viewModel: DemoViewModel by lazy { ViewModelProvider(this).get(DemoViewModel::class.java) }
複製代碼

看ViewModelProvicder的構造方法網絡

//建立一個ViewModelProvider , 用來建立ViewModels的,並將其保留在給定ViewModelStoreOwner的存儲區中,若是owner是 HasDefaultViewModelProviderFactory 的子類,就用HasDefaultViewModelProviderFactory的工廠,像Fragment 和 ComponentActivity 都是實現了HasDefaultViewModelProviderFactory的接口 
 public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
    
 public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
複製代碼

上面默認的方法是; 用來建立ViewModels的,並將其保留在給定ViewModelStoreOwner的存儲區中,若是owner是 HasDefaultViewModelProviderFactory 的子類,就用HasDefaultViewModelProviderFactory的工廠,像Fragment 和 ComponentActivity(是FragmentActivity的爹,AppCompatActivity的爺爺) 都是實現了HasDefaultViewModelProviderFactory的接口,獲取到的Frctory是 SavedStateViewModelFactory。 不然就用NewInstanceFactory去建立ViewModel,好比須要咱們在ViewModel中傳遞參數的時候,咱們能夠寫本身的Factory繼承NewInstanceFactory,走咱們本身方法Create()

5.1.1.1 ComponentActivity中獲取Factory的方法

public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        // 看這個activity是否已經attache到application
        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 (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }
複製代碼

5.1.1.2 Fragment中獲取Factory的方法

public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    requireActivity().getApplication(),
                    this,
                    getArguments());
        }
        return mDefaultFactory;
    }
複製代碼

5.1.2 本身定義的Factory

使用

private val viewModel:UserViewModel by lazy { ViewModelProvider(this,UserViewModelFactory(repository)).get(UserViewModel::class.java) }
複製代碼

本身定義的Factory

class UserViewModelFactory(
    private val repository: UserRepository
) : ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        // 傳入一個 Repository 參數
        return UserViewModel(repository) as T
    }
}
複製代碼

5.1.3 ViewModelStoreOwner: 存儲ViewModel的擁有者

Fragment 和 ComponentActivity(是FragmentActivity的爹,AppCompatActivity的爺爺)都實現了 ViewModelStoreOwner 這個接口,

public interface ViewModelStoreOwner {
// ViewModelStore 是用來存儲 ViewModel的實例的
    @NonNull
    ViewModelStore getViewModelStore();
}
複製代碼

5.1.3.1 ComponentActivity中的得到 ViewModelStroe 的方法

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實例,NonConfigurationInstances整個activity都裝在裏面了,NonConfigurationInstances是跟配置改變沒有關係的一個實例。
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                //從NonConfigurationInstances 恢復 ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            // 若是爲null,證實歷來沒有建立過,從新new 出來
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
複製代碼

5.1.3.2 在看一下ComponentActivity:: onRetainNonConfigurationInstance

public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        
        if (viewModelStore == null) {
           // 若是是null,說明之前沒有調用過 getViewModelStore()方法,也就是沒有調用過ViewModelProvider(requireActivity()).get(DemoViewModel::class.java)的方法來獲取  ViewModel。因此咱們看一下最後一個的NonConfigurationInstance裏面是否存在viewModelStore
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
            // 若果nc 不等於null,就證實之前存儲過,因此從這裏取出來
                viewModelStore = nc.viewModelStore;
            }
        }
        // custom這個返回null,子類也沒重寫他的方法,因此是必須是null
        if (viewModelStore == null && custom == null) {
            return null;
        }

        // 若是viewModelStore不是null,也就是說最後一個NonConfigurationInstance裏面有值,直接new出來NonConfigurationInstances並賦值,返回出去
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        retu
複製代碼

而後看看 NonConfigurationInstances類,是ComponentActivity的一個靜態內部類,用來存儲viewModelStore的,不管配置是否改變,這個類的實例不會改,裏面的ViewModelStore不會消失

static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
複製代碼

5.1.3.3 retainNonConfigurationInstances 方法執行來存貯NonConfigurationInstances,返回結果就是NonConfigurationInstances,同時把NonConfigurationInstances存儲到ActivityClientRecord中去,ActivityClientRecord是一個Activity的記錄本。以後在Activity的Attach的時候,會把NonConfigurationInstances賦值給成員變量

在Activity調用onDestroy的以前會調用activity的retainNonConfigurationInstances

NonConfigurationInstances retainNonConfigurationInstances() {
     ....
 }
複製代碼

總結一下:

  • 先經過NonConfigurationInstances來拿ViewModelStore,NonConfigurationInstances是一個靜態內部類,不會由於配置改變(好比屏幕旋轉),而從新建立
  • 若是NonConfigurationInstances沒有拿到,證實這就是個新的ViewModelStore,因此直接走建立ViewModelStore流程

5.1.4 ViewModelStore 用來存儲ViewModel的容器,,裏面有個HashMap,用來存儲ViewModel的,存儲的key下面會講到

public class ViewModelStore {

//  這個就是存儲ViewModel的Hashmap
    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());
    }

  // 清除viewmodel的裏面的東西
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}
複製代碼

5.1.5 得到ViewModel

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    // 得到 viewmodel這個類的全限定名
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        // 用一個DEFAULT_KEY常量 + 一個這個類的全限定名當作key
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
複製代碼

繼續看get(String,Class) 方法

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // 你能夠認爲mViewModelStore就是個map,上面也講了,其實它裏面就是一個map來存儲ViewModel的
        // 從map裏面拿到 拿到這個 ViewModel
        ViewModel viewModel = mViewModelStore.get(key);
        // viewModel 這個對象是否能夠轉化成這個類,若是 viewModel 是null的話,這裏也返回false
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        // mFactory 若是不是本身實現的話,那就是SavedStateViewModelFactory 實現於 KeyedFactory,因此第一次拿的時候 ,走到這裏
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        // 把獲取到的ViewModel緩存到HashMap中
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
複製代碼

5.1.6 在何時清除ViewModel

當 ComponentActivity 的構造方法中有如下代碼,當Activity走到destroy的時候清楚ViewModel,裏面有個isChangingConfigurations方法,表示 是不是配置改變引發的(好比 Activity屏幕旋轉),若是是就不清除。

getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
複製代碼
相關文章
相關標籤/搜索