1. 程式人生 > >剖析 Android 架構元件之 ViewModel

剖析 Android 架構元件之 ViewModel

ViewModel 是 Android 架構元件之一,用於分離 UI 邏輯與 UI 資料。在發生 Configuration Changes 時,它不會被銷燬。在介面重建後,方便開發者呈現介面銷燬前的 UI 狀態。

本文主要分析 ViewModel 的以下3個方面:

  • 獲取和建立過程。
  • Configuration Changes 存活原理。
  • 銷燬過程。

1. 依賴庫

implementation "androidx.fragment:fragment:1.0.0"
implementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0"
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" 複製程式碼

2. 主要類與介面

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProvider.Factory;
import
androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelStore; import androidx.lifecycle.ViewModelStoreOwner; 複製程式碼

3. ViewModel

ViewModel 是一個抽象類,類中只定義了一個空實現的 onCleared() 方法。

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
@SuppressWarnings("WeakerAccess") protected void onCleared() { } } 複製程式碼

3.1 AndroidViewModel

AndroidViewModel 類擴充套件了 ViewModel 類,增加了 Application 欄位,在構造方法初始化,並提供了 getApplication() 方法。

public class AndroidViewModel extends ViewModel {
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}
複製程式碼

4. 獲取和建立過程分析

獲取 ViewModel 物件程式碼如下:

ViewModelProviders.of(activityOrFragment).get(ViewModel::class.java)
複製程式碼

4.1 ViewModelProviders

ViewModelProviders 類提供了4個靜態工廠方法 of() 建立新的 ViewModelProvider 物件。

ViewModelProviders.of(Fragment)
ViewModelProviders.of(FragmentActivity)
ViewModelProviders.of(Fragment, Factory)
ViewModelProviders.of(FragmentActivity, Factory)
複製程式碼

4.2 ViewModelProvider

ViewModelProvider 負責提供 ViewModel 物件,類中定義了以下兩個欄位:

private final Factory mFactory;
private final ViewModelStore mViewModelStore;
複製程式碼

先說說這兩個類的功能。

4.3 ViewModelProvider.Factory

Factory 介面定義了一個建立 ViewModel 的介面 create()ViewModelProvider 在需要時呼叫該方法新建 ViewModel 物件。

public interface Factory {
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
複製程式碼

Android 已經內建了2個 Factory 實現類,分別是:

  • AndroidViewModelFactory 實現類,可以建立 ViewModelAndroidViewModel 子類物件。
  • NewInstanceFactory 類,只可以建立 ViewModel 子類物件。

它們的實現都是通過反射機制呼叫 ViewModel 子類的構造方法建立物件。

public static class NewInstanceFactory implements Factory {
    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        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);
        }
    }
}
複製程式碼

AndroidViewModelFactory 繼承 NewInstanceFactory 類,是個單例,支援建立 AndroidViewModel 子類物件。

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static AndroidViewModelFactory sInstance;

    public static AndroidViewModelFactory getInstance(Application application) {
        if (sInstance == null) {
            sInstance = new AndroidViewModelFactory(application);
        }
        return sInstance;
    }

    private Application mApplication;

    public AndroidViewModelFactory(Application application) {
        mApplication = application;
    }

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            try {
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
        return super.create(modelClass);
    }
}
複製程式碼

4.4 ViewModelStore

ViewModelStore 類中維護一個 Map<String, ViewModel> 物件儲存已建立的 ViewModel 物件,並提供 put()get() 方法。

public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
    
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }
}
複製程式碼

4.5 ViewModelStoreOwner

ViewModelStore 是來自於 FragmentActivityFragment,它們實現了 ViewModelStoreOwner 介面,返回當前 UI 作用域裡的 ViewModelStore 物件。

public interface ViewModelStoreOwner {
    ViewModelStore getViewModelStore();
}
複製程式碼

Fragment 類中的實現如下:

public ViewModelStore getViewModelStore() {
    if (getContext() == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    if (mViewModelStore == null) {
        mViewModelStore = new ViewModelStore();
    }
    return mViewModelStore;
}
複製程式碼

FragmentActivity 類中的實現如下:

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) {
        mViewModelStore = new ViewModelStore();
    }
    return mViewModelStore;
}
複製程式碼

4.6 建立 ViewModelProvider

回到 of() 方法的實現。

public static ViewModelProvider of(FragmentActivity activity, Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}
複製程式碼

在建立 ViewModelProvider 物件時需要傳入 ViewModelStoreFactory 物件。若 factorynull,將使用 AndroidViewModelFactory 單例物件。

4.7 獲取 ViewModel 物件

呼叫 ViewModelProvider 物件的 get() 方法獲取 ViewModel 物件,如果在 ViewModelStore 裡不存在,則使用 Factory 建立一個新的物件並存放到 ViewModelStore 裡。

public <T extends ViewModel> T get(String key, Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);

    return (T) viewModel;
}
複製程式碼

5. Configuration Changes 存活原理

ActivityFragment 被系統重建時,ViewModel 物件不會被銷燬,新的 ActivityFragment 物件拿到的是同一個 ViewModel 物件。

FragmentActivity#onRetainNonConfigurationInstance() 方法中,會將 ViewModelStore 物件保留起來。

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

    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

    if (fragments == null && mViewModelStore == null && custom == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = mViewModelStore;
    nci.fragments = fragments;
    return nci;
}
複製程式碼

然後在 onCreate() 方法能獲取之前保留起來的 ViewModelStore 物件。

protected void onCreate(Bundle savedInstanceState) {
    mFragments.attachHost(null /*parent*/);
    super.onCreate(savedInstanceState);

    NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
    if (nc != null) {
        mViewModelStore = nc.viewModelStore;
    }
    // ...
}
複製程式碼

Fragment 作用域裡是如何實現的呢?在 FragmentActivityonRetainNonConfigurationInstance() 方法中裡有這樣一句程式碼:

FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
複製程式碼

實現保留的機制是一樣的,只不過放在 FragmentManagerNonConfig 物件中。是在 FragmentManager#saveNonConfig() 方法中將 ViewModelStore 物件儲存到 FragmentManagerNonConfig 裡的。

void saveNonConfig() {
    ArrayList<Fragment> fragments = null;
    ArrayList<FragmentManagerNonConfig> childFragments = null;
    ArrayList<ViewModelStore> viewModelStores = null;
    if (mActive != null) {
        for (int i=0; i<mActive.size(); i++) {
            Fragment f = mActive.valueAt(i);
            if (f != null) {
                if (f.mRetainInstance) {
                    if (fragments == null) {
                        fragments = new ArrayList<Fragment>();
                    }
                    fragments.add(f);
                    f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
                    if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
                }
                FragmentManagerNonConfig child;
                if (f.mChildFragmentManager != null) {
                    f.mChildFragmentManager.saveNonConfig();
                    child = f.mChildFragmentManager.mSavedNonConfig;
                } else {
                    // f.mChildNonConfig may be not null, when the parent fragment is
                    // in the backstack.
                    child = f.mChildNonConfig;
                }

                if (childFragments == null && child != null) {
                    childFragments = new ArrayList<>(mActive.size());
                    for (int j = 0; j < i; j++) {
                        childFragments.add(null);
                    }
                }

                if (childFragments != null) {
                    childFragments.add(child);
                }
                if (viewModelStores == null && f.mViewModelStore != null) {
                    viewModelStores = new ArrayList<>(mActive.size());
                    for (int j = 0; j < i; j++) {
                        viewModelStores.add(null);
                    }
                }

                if (viewModelStores != null) {
                    viewModelStores.add(f.mViewModelStore);
                }
            }
        }
    }
    if (fragments == null && childFragments == null && viewModelStores == null) {
        mSavedNonConfig = null;
    } else {
        mSavedNonConfig = new FragmentManagerNonConfig(fragments, childFragments,
        viewModelStores);
    }
}
複製程式碼

該方法的呼叫順序是:FragmentActivity#onSaveInstanceState() -> FragmentManager#saveAllState() -> FragmentManager#saveNonConfig()

6. 銷燬過程

FragmentActivity 類的 onDestory() 方法中。

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mViewModelStore != null && !isChangingConfigurations()) {
        mViewModelStore.clear();
    }
    mFragments.dispatchDestroy();
}
複製程式碼

Fragment 類的 onDestory() 方法中。

public void onDestroy() {
    mCalled = true;
    FragmentActivity activity = getActivity();
    boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
    if (mViewModelStore != null && !isChangingConfigurations) {
        mViewModelStore.clear();
    }
}
複製程式碼

先判斷是否有發生 Configuration Changes,如果沒有則會呼叫 ViewModelStoreclear() 方法,再一一呼叫每一個 ViewModelonCleared() 方法。

public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.onCleared();
    }
    mMap.clear();
}
複製程式碼

7. 總結

以上便是 ViewModel 3個主要過程的剖析,這裡做一下總結。

  • 通過 ViewModelProviders 建立 ViewModelProvider 物件,呼叫該物件的 get() 方法獲取 ViewModel 物件。 當 ViewModelStore 裡不存在想要的物件,ViewModelProvider 會使用 Factory 新建一個物件並存放到 ViewModelStore 裡。
  • 當發生 發生 Configuration Changes 時,FragmentActivity 利用 getLastNonConfigurationInstance()onRetainNonConfigurationInstance() 方法實現 ViewModelStore 的保留與恢復,進而實現 ViewModel 物件的保活。
  • FragmentActivityFragment 被銷燬時,會根據是否發生 Configuration Changes 來決定是否銷燬 ViewModel