Android架構組件——ViewModel

概述

ViewModel,從字面上理解的話,它肯定是跟視圖(View)以及數據(Model)相關的。正像它字面意思一樣,它是負責準備和管理和UI組件(Fragment/Activity)相關的數據類,也就是說ViewModel是用來管理UI相關的數據的,同時ViewModel還可以用來負責UI組件間的通信。

之前存在的問題

ViewModel用來存儲和管理UI相關的數據,可於將一個Activity或Fragment組件相關的數據邏輯抽象出來,並能適配組件的生命週期,如當屏幕旋轉Activity重建後,ViewModel中的數據依然有效。

引入ViewModel之前,存在如下幾個問題:

  • 通常Android系統來管理UI controllers(如Activity、Fragment)的生命週期,由系統響應用戶交互或者重建組件,用戶無法操控。當組件被銷燬並重建後,原來組件相關的數據也會丟失,如果數據類型比較簡單,同時數據量也不大,可以通過onSaveInstanceState()存儲數據,組件重建之後通過onCreate(),從中讀取Bundle恢復數據。但如果是大量數據,不方便序列化及反序列化,則上述方法將不適用。
  • UI controllers經常會發送很多異步請求,有可能會出現UI組件已銷燬,而請求還未返回的情況,因此UI controllers需要做額外的工作以防止內存泄露。
    當Activity因爲配置變化而銷燬重建時,一般數據會重新請求,其實這是一種浪費,最好就是能夠保留上次的數據。
  • UI controllers其實只需要負責展示UI數據、響應用戶交互和系統交互即可。但往往開發者會在Activity或Fragment中寫許多數據請求和處理的工作,造成UI controllers類代碼膨脹,也會導致單元測試難以進行。我們應該遵循職責分離原則,將數據相關的事情從UI controllers中分離出來。

ViewModel基本使用

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // 異步調用獲取用戶列表
    }
}

新的Activity如下:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // 更新 UI
        });
    }
}

如果Activity被重新創建了,它會收到被之前Activity創建的相同MyViewModel實例。當所屬Activity終止後,框架調用ViewModel的onCleared()方法清除資源。

因爲ViewModel在指定的Activity或Fragment實例外存活,它應該永遠不能引用一個View,或持有任何包含Activity context引用的類。如果ViewModel需要Application的context(如獲取系統服務),可以擴展AndroidViewmodel,並擁有一個構造器接收Application。

在Fragment間共享數據

一個Activity中的多個Fragment相互通訊是很常見的。之前每個Fragment需要定義接口描述,所屬Activity將二者捆綁在一起。此外,每個Fragment必須處理其他Fragment未創建或不可見的情況。通過使用ViewModel可以解決這個痛點,這些Fragment可以使用它們的Activity共享ViewModel來處理通訊:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onActivityCreated() {
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends LifecycleFragment {
    public void onActivityCreated() {
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // update UI
        });
    }
}

注意:上面兩個Fragment都用到了如下代碼來獲取ViewModel,getActivity()返回的是同一個宿主Activity,因此兩個Fragment之間返回的是同一個SharedViewModel對象。

SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

這種方式的好處包括:

  • Activity不需要做任何事情,也不需要知道通訊的事情
  • Fragment不需要知道彼此,除了SharedViewModel進行聯繫。如果它們(Fragment)其中一個消失了,其餘的仍然能夠像往常一樣工作。
  • 每個Fragment有自己的生命週期,而且不會受其它Fragment生命週期的影響。事實上,一個Fragment替換另一個Fragment,UI的工作也不會受到任何影響。

ViewModel的生命週期

ViewModel對象的範圍由獲取ViewModel時傳遞至ViewModelProvider的Lifecycle所決定。ViewModel始終處在內存中,直到Lifecycle永久地離開—對於Activity來說,是當它終止(finish)的時候,對於Fragment來說,是當它分離(detached)的時候。
這裏寫圖片描述
上圖左側爲Activity的生命週期過程,期間有一個旋轉屏幕的操作;右側則爲ViewModel的生命週期過程。

一般通過如下代碼初始化ViewModel:

viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);

this參數一般爲Activity或Fragment,因此ViewModelProvider可以獲取組件的生命週期。

Activity在生命週期中可能會觸發多次onCreate(),而ViewModel則只會在第一次onCreate()時創建,然後直到最後Activity銷燬。

ViewModel相關類圖

借用Android架構組件(三)——ViewModel的類圖:
這裏寫圖片描述

  • ViewModelProviders是ViewModel工具類,該類提供了通過Fragment和Activity得到ViewModel的方法,而具體實現又是由ViewModelProvider實現的。

  • ViewModelProvider是實現ViewModel創建、獲取的工具類。在ViewModelProvider中定義了一個創建ViewModel的接口類——Factory。ViewModelProvider中有個ViewModelStore對象,用於存儲ViewModel對象。

  • ViewModelStore是存儲ViewModel的類,具體實現是通過HashMap來保存ViewModle對象。

  • ViewModel是個抽象類,裏面只定義了一個onCleared()方法,該方法在ViewModel不在被使用時調用。ViewModel有一個子類AndroidViewModel,這個類是便於要在ViewModel中使用Context對象,因爲我們前面提到不能在ViewModel中持有Activity的引用。

  • ViewModelStores是ViewModelStore的工廠方法類,它會關聯HolderFragment,HolderFragment有個嵌套類——HolderFragmentManager。

ViewModel相關時序圖

追溯創建一個ViewModel的源碼,會察覺需要的步驟有點多。下面以在Fragment中得到ViewModel對象爲例看下整個過程的時序圖。
借用Android架構組件(三)——ViewModel的時序圖:
這裏寫圖片描述

時序圖看起來比較複雜,但是它只描述了兩個過程:

  1. 得到ViewModel對象。
  2. HolderFragment被銷燬時,ViewModel收到onCleared()通知。

ViewModel相關源碼分析

ViewModelProviders類的具體實現:

public class ViewModelProviders {

    private static Application checkApplication(Activity activity) {
        Application application = activity.getApplication();
        if (application == null) {
            throw new IllegalStateException("Your activity/fragment is not yet attached to "
                    + "Application. You can't request ViewModel before onCreate call.");
        }
        return application;
    }

    private static Activity checkActivity(Fragment fragment) {
        Activity activity = fragment.getActivity();
        if (activity == null) {
            throw new IllegalStateException("Can't create ViewModelProvider for detached fragment");
        }
        return activity;
    }

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull Fragment fragment) {
        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(
                        checkApplication(checkActivity(fragment)));
        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
    }

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(
                        checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull Fragment fragment, @NonNull Factory factory) {
        checkApplication(checkActivity(fragment));
        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
    }

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @NonNull Factory factory) {
        checkApplication(activity);
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }

ViewModelProviders提供了四個of()方法,四個方法功能類似,其中of(FragmentActivity activity, Factory factory)和of(Fragment fragment, Factory factory)提供了自定義創建ViewModel的方法。
1. 判斷Fragment的是否Attached to Activity,Activity的Application對象是否爲空。
2. 創建ViewModel對象看似很簡單,一行代碼搞定。

new ViewModelProvider(ViewModelStores.of(fragment), factory)

先看看ViewModelStores.of()方法:

    @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        if (fragment instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) fragment).getViewModelStore();
        }
        return holderFragmentFor(fragment).getViewModelStore();
    }

繼續深入發現其實是實現了一個接口:

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

holderFragmentFor()是HolderFragment的靜態方法,HolderFragment繼承自Fragment。我們先看holderFragment()方法的具體實現

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static HolderFragment holderFragmentFor(Fragment fragment) {
        return sHolderFragmentManager.holderFragmentFor(fragment);
    }

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }

繼續看HolderFragmentManager.holderFragmentFor()方法的具體實現

HolderFragment holderFragmentFor(Fragment parentFragment) {
            FragmentManager fm = parentFragment.getChildFragmentManager();
            HolderFragment holder = findHolderFragment(fm);
            if (holder != null) {
                return holder;
            }
            holder = mNotCommittedFragmentHolders.get(parentFragment);
            if (holder != null) {
                return holder;
            }

            parentFragment.getFragmentManager()
                    .registerFragmentLifecycleCallbacks(mParentDestroyedCallback, false);
            holder = createHolderFragment(fm);
            mNotCommittedFragmentHolders.put(parentFragment, holder);
            return holder;
        }

private FragmentLifecycleCallbacks mParentDestroyedCallback =
    new FragmentLifecycleCallbacks() {
        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment parentFragment) {
            super.onFragmentDestroyed(fm, parentFragment);
            HolderFragment fragment = mNotCommittedFragmentHolders.remove(
                    parentFragment);
            if (fragment != null) {
                Log.e(LOG_TAG, "Failed to save a ViewModel for " + parentFragment);
            }
        }
    };

private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
    HolderFragment holder = new HolderFragment(); // 創建HolderFragment對象
    fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
    return holder;
}

public HolderFragment() {
    //這個是關鍵,這就使得Activity被recreate時,Fragment的onDestroy()和onCreate()不會被調用
    setRetainInstance(true); 
}

setRetainInstance(boolean) 是Fragment中的一個方法。將這個方法設置爲true就可以使當前Fragment在Activity重建時存活下來, 如果不設置或者設置爲 false, 當前 Fragment 會在 Activity 重建時同樣發生重建, 以至於被新建的對象所替代。

在setRetainInstance(boolean)爲true的 Fragment 中放一個專門用於存儲ViewModel的Map, 自然Map中所有的ViewModel都會倖免於Activity重建,讓Activity, Fragment都綁定一個這樣的Fragment, 將ViewModel存放到這個 Fragment 的 Map 中, ViewModel 組件就這樣實現了。

到此爲止,我們已經得到了ViewStore對象,前面我們在創建ViewModelProvider對象是通過這行代碼實現的new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory)現在再看下ViewModelProvider的構造方法

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }

現在就可以通過ViewModelProvider.get()方法得到ViewModel對象,繼續看下該方法的具體實現

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key); //從緩存中查找是否有已有ViewModel對象。

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass); //創建ViewModel對象,然後緩存起來。
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

ViewModelProvider.get()方法比較簡單,註釋中都寫明瞭。最後我們看下ViewModelStore類的具體實現

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.get(key);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
        mMap.put(key, viewModel);
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

ViewModelStore是緩存ViewModel的類,put()、get()方法用於存取ViewModel對象,另外提供了clear()方法用於清空緩存的ViewModel對象,在該方法中會調用ViewModel.onCleared()方法通知ViewModel對象不再被使用。

ViewModel收到onCleared()通知
HolderFragment的onDestroy()方法

    @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }

在onDestroy()方法中調用了ViewModelStore.clear()方法,我們知道在該方法中會調用ViewModel的onCleared()方法。在你看了HolderFragment源碼後,或許你會有個疑問,mViewModelStore保存的ViewModel對象是在哪裏添加的呢? 細心的話,你會發現在ViewModelProvider的構造方法中,已經將HolderFragment中的ViwModelStore對象mViewModelStore的引用傳遞給了ViewModelProvider中的mViewModelStore,而在ViewModelProvider.get()方法中會向mViewModelStore添加ViewModel對象。

總結

  • ViewModel職責是爲Activity或Fragment管理、請求數據,具體數據請求邏輯不應該寫在ViewModel中,否則ViewModel的職責會變得太重,此處需要一個引入一個Repository,負責數據請求相關工作。具體請參考 Android架構組件。
  • ViewModel可以用於Activity內不同Fragment的交互,也可以用作Fragment之間一種解耦方式。
  • ViewModel也可以負責處理部分Activity/Fragment與應用其他模塊的交互。
  • ViewModel生命週期(以Activity爲例)起始於Activity第一次onCreate(),結束於Activity最終finish時。

官方:
https://developer.android.google.cn/topic/libraries/architecture/viewmodel.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章