Android ViewModel,再學(xué)不會你砍我

之前工作用了很久MVP架構(gòu)了,雖然很好的解決了M層與V層的耦合關(guān)系,但巨多的接口,難以復(fù)用、難以單測的問題一直縈繞心頭,久久不能平復(fù)~~~,于是我將目光轉(zhuǎn)向了MVVM。

MVVM與MVP相比最大的區(qū)別就是用ViewModel(后文簡稱VM)代替了原來的P層,這里的VM就是ViewModel。一句話概括它的特點---對數(shù)據(jù)狀態(tài)的持有和維護(hù)。換言之,它將原來P層關(guān)于數(shù)據(jù)的邏輯運算與處理統(tǒng)一放到了VM中,而剩余的V層的操作建議使用Databinding,從而形成最為簡潔高效的MVVM架構(gòu)。說到這呢,推薦一篇舊文DataBinding,再學(xué)不會你砍我(系兄弟就砍偶系列?)。

回到VM的特點---對數(shù)據(jù)狀態(tài)的持有和維護(hù)。為什么需要做這些呢?事實上就是為了解決下面兩個開發(fā)中常見的問題。

  • Activity配置更改重建時(比如屏幕旋轉(zhuǎn))保留數(shù)據(jù)
  • UI組件(Activity與Fragment、Fragment與Fragment)間實現(xiàn)數(shù)據(jù)共享。

對于第一條不用VM的情況下只能通過onSaveInstanceState保存數(shù)據(jù),當(dāng)activity重建后再通過onCreateonRestoreInstanceState方法的bundle中取出,但如果數(shù)據(jù)量較大,數(shù)據(jù)的序列化和反序列化將產(chǎn)生一定的性能開銷。

對于第二條如果不用VM,各個UI組件都要持有共享數(shù)據(jù)的引用,這會帶來兩個麻煩,第一,如果新增了共享數(shù)據(jù),各個UI組件需要再次聲明并初始化新增的共享數(shù)據(jù);第二,某個UI組件對共享數(shù)據(jù)修改,無法直接通知其他UI組件,需手動實現(xiàn)觀察者模式。而VM結(jié)合LiveData就可以很輕松的實現(xiàn)這一點。

LiveData作為數(shù)據(jù)變化的驅(qū)動器,VM借助它可以寫出十分簡潔的MVVM代碼。

接下來我們來看一下VM到底是如何實現(xiàn)上述需求的,而事實上核心可以轉(zhuǎn)化為下面兩個問題。

問個正事.png

問題

  1. VM是如何解決Activity與Fragment、Fragment之間數(shù)據(jù)共享的問題?
  2. VM是如何在Activity發(fā)生旋轉(zhuǎn)時保留數(shù)據(jù)的?不是也走onDestroy了嗎?

ViewModel是什么?

回答上面問題之前我們要先了解一下VM到底是個啥。

public abstract class ViewModel {
    protected void onCleared() {
    }
}

就是個抽象類甚至連抽象方法都沒有,簡單的令人發(fā)指。onCleared方法提供了釋放VM中資源的一個機(jī)會,在Activity/Fragment的onDestroy生命周期中被調(diào)用。類庫內(nèi)部提供了一個實現(xiàn)類AndroidViewModel。

public class AndroidViewModel extends ViewModel {
    private Application mApplication;

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

    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

AndroidViewModel內(nèi)持有Application的引用,所以通??梢宰鲆恍┤芷诘墓ぷ鳌槭裁床荒艹钟幸粋€Activity呢?這與ViewModel的生命周期有關(guān),我們稍后解釋。

創(chuàng)建ViewModel

接下來看看VM的創(chuàng)建,通常我們是這樣創(chuàng)建一個VM的。

val viewModel = ViewModelProviders.of(this).get(AndroidViewModel::class.java)

這里的this可以是FragmentActivity或Fragment,他們都是support-v4包中控件,為的是向下兼容。
我們以FragmentActivity為例看看of方法做了什么。

### 1.1 ViewModelProviders
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    return of(activity, null);
}

public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    //這里用的工廠模式,vm的創(chuàng)建交由工廠完成,默認(rèn)使用AndroidViewModelFactory
    return new ViewModelProvider(ViewModelStores.of(activity), factory);
}

這兩個方法的返回值都是ViewModelProvider,ViewModelProviders是操作ViewModelProvider的工具類,內(nèi)部都是獲取ViewModelProvider的靜態(tài)方法,而真正的VM的業(yè)務(wù)在ViewModelProvider中。

同理ViewModelStores和ViewModelStore的關(guān)系亦是如此。既然要保留VM的數(shù)據(jù),必然要有個存儲單元。ViewModelStore就是這個存儲單元,因為一個Activity中可能有多個VM,所以需要一個Map來維護(hù)關(guān)系表,key為VM的名字,value為VM對象,簡單的令人發(fā)指。

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

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

ViewModelStores僅負(fù)責(zé)提供工具方法創(chuàng)建ViewModelStore。

### 1.2
public class ViewModelStores {
    private ViewModelStores() {
    }

    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }
    ...
}

ViewModelStoreOwner又是什么鬼?它是抽象了一個獲取ViewModelStore的接口。常見的實現(xiàn)類有Fragment/FragmentActivity,這也不難理解,因為要想在配置改變時保留數(shù)據(jù),首先得要將數(shù)據(jù)存儲起來。

public interface ViewModelStoreOwner {
    ViewModelStore getViewModelStore();
}

感覺這時需要一張類圖了,不然你是不是想砍死我?

format.jpg

事實上目前提到的這幾個類已經(jīng)幾乎涵蓋了viewmodel庫中所有的類。

我們回到1.1中創(chuàng)建ViewModelProvider的代碼。

return new ViewModelProvider(ViewModelStores.of(activity), factory);

我們將activity中的VM存儲單元和VM的創(chuàng)建工廠(AndroidViewModelFactory)傳入得到一個ViewModelProvider對象,最終通過其get方法得到VM。

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");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

以Default_Key作為前綴加上VM的完整類名為key,獲取VM對象。

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

    //先看ViewModelStore中是否存在,如果存了就直接返回
    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    }
    ...
    //如果不存在,創(chuàng)建一個新的VM并存入ViewModelStore
    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

默認(rèn)情況下mFactory為AndroidViewModelFactory,來看下它是如何創(chuàng)建VM的。

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
    ...
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            try {
                //通過反射創(chuàng)建AndroidViewModel對象
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            } catch (NoSuchMethodException e) {
                ...
            } 
        }
        return super.create(modelClass);
    }
}

至此VM的創(chuàng)建我們就講完了,可以總結(jié)一下:

  1. ViewModelStore是存儲VM的數(shù)據(jù)單元,存儲結(jié)構(gòu)為Map,F(xiàn)ragment/FragmentActivity持有其引用。
  2. ViewModelProvider通過get方法創(chuàng)建一個VM,創(chuàng)建之前會先檢查ViewModelStore中是否存在,若不存在則通過反射創(chuàng)建一個VM。

UI組件間數(shù)據(jù)共享

講到這我們回過頭來看看開篇提的第一個問題:Activity與Fragment,F(xiàn)ragment之間是如何共享數(shù)據(jù)的。
數(shù)據(jù)要實現(xiàn)共享最簡單的方式就是大家都讀取一份數(shù)據(jù)源。
我們來看看數(shù)據(jù)源ViewModelStore具體是在哪里創(chuàng)建的。

上面講的代碼片段1.2可知,有兩條路徑創(chuàng)建。

public static ViewModelStore of(@NonNull FragmentActivity activity) {
    if (activity instanceof ViewModelStoreOwner) { ①
        return ((ViewModelStoreOwner) activity).getViewModelStore();
    }
    return holderFragmentFor(activity).getViewModelStore();②
}

①如果FragmentActivity是ViewModelStoreOwner類型通過activity的getViewModelStore方法創(chuàng)建。

②否則通過一個HolderFragment來創(chuàng)建。

先來看①

### FragmentActivity
private ViewModelStore mViewModelStore;
public ViewModelStore getViewModelStore() {
    ...
    if (mViewModelStore == null) {
        mViewModelStore = new ViewModelStore();
    }
    return mViewModelStore;
}

簡單的令人發(fā)ck,既然是成員變量那每次調(diào)用必定返回同一個VM,只要保證調(diào)用ViewModelProviders.of(activity).get(AndroidViewModel::class.java)傳遞的activity是同一個對象。

再來看②,整體思路是添加一個不可見的HolderFragment到當(dāng)前Activity中,再將數(shù)據(jù)源記錄在這個fragment中。

### HolderFragment extends Fragment implements ViewModelStoreOwner
private ViewModelStore mViewModelStore = new ViewModelStore();

public static HolderFragment holderFragmentFor(FragmentActivity activity) {
    return sHolderFragmentManager.holderFragmentFor(activity);
}

HolderFragment holderFragmentFor(FragmentActivity activity) {
    FragmentManager fm = activity.getSupportFragmentManager();
    //先查一下當(dāng)前有沒有這個HolderFragment
    HolderFragment holder = findHolderFragment(fm);
    if (holder != null) {
        return holder;
    }
    ...
    //沒有就創(chuàng)建一個
    holder = createHolderFragment(fm);
    ...
    return holder;
}

private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
    //創(chuàng)建一個HolderFragment、打上tag,再添加到當(dāng)前界面。
    HolderFragment holder = new HolderFragment();
    fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
    return holder;
}

之所以不可見,是因為這個HolderFragment并未復(fù)寫onCreateView。這么做的原因是早期的版本想利用Fragment的setRetainInstance()API接口,來實現(xiàn)當(dāng)Activity因配置發(fā)生改變時保留這個不可見的Fragment,生命周期只走onDetach和onAttach。既然沒有被重建,那么它持有的數(shù)據(jù)自然就會被保留,其他主動退出的情況走到onDestroy會清空數(shù)據(jù)。

### HolderFragment
public HolderFragment() {
    setRetainInstance(true);
}
//被銷毀時清除數(shù)據(jù)源
public void onDestroy() {
    super.onDestroy();
    mViewModelStore.clear();
}

那么在實際使用中會走哪條路徑呢?我們反觀源碼FragmentActivity是實現(xiàn)了ViewModelStoreOwner接口的,那此處代碼不是肯定走①嗎?②是不是走錯片場了?此時我感覺我的智商受到了侮辱。

智商不足.jpeg

原來在appcompat-v7版本在27.1.0之前FragmentActivity并沒有實現(xiàn)ViewModelStoreOwner接口,也就是統(tǒng)一用HolderFragment實現(xiàn)。google大大可能是覺得這個HolderFragment有點太騷操作了就讓后續(xù)版本的FragmentActivity直接支持了VM存儲。因為添加的HolderFragment也是有維護(hù)成本的,且上層可以通過FragmentManager獲取到它,然后對它進(jìn)行其他騷操作,想想都刺激。。。

深井冰.jpg

上面我們分析了創(chuàng)建VM時傳入FragmentActivity的情況,那傳入Fragment的情況呢?實際上跟FragmentActivity幾乎一樣,在Fragment中也有一個mViewModelStore成員變量,注意這里哦,非常關(guān)鍵。也就是說如果我們通過下面的代碼創(chuàng)建的是兩個VM,是不能共享數(shù)據(jù)的。

### MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
    //傳遞activity
    val vm = ViewModelProviders.of(this).get(AndroidViewModel::class.java)
}

### TestFragment
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //傳遞fragment
    val vm = ViewModelProviders.of(this).get(AndroidViewModel::class.java)
}

正確的寫法應(yīng)該是二者在of方法中傳入相同的對象

### TestFragment.onCreate(savedInstanceState: Bundle?)
//通過getActivity方法取得Fragment所在的Activity
val vm = activity?.run {
    //run函數(shù) 這里的this指代activity
    ViewModelProviders.of(this)[AndroidViewModel::class.java]
}

Fragment之間同理,如果Fragment間同層級,可以統(tǒng)一通過Activity或共同的parentFragment(如果有)獲取VM;如果有嵌套關(guān)系,可以使用parentFragment對象獲取VM。

Activity配置發(fā)生變化時數(shù)據(jù)保持

其實在上面的內(nèi)容中已經(jīng)講了HolderFragment是如何保留數(shù)據(jù)的,就是利用Fragment的setRetainInstance()API接口完成的。

google為了優(yōu)化這點在新版本里直接讓FragmentActivity支持了數(shù)據(jù)的保持,官方的配圖很好的闡釋了VM在Activity因配置變化銷毀重建時的生命周期。

viewmodel-lifecycle.png

下面我們來分析一下是如何做到的,先看onDestroy的銷毀邏輯。

### FragmentActivity
protected void onDestroy() {
    super.onDestroy();
    ...
    if (mViewModelStore != null && !mRetaining) {
        mViewModelStore.clear();
    }
    ...
}

mRetaining為true時將會保留VM數(shù)據(jù),那它何時為true呢?

### FragmentActivity
public final Object onRetainNonConfigurationInstance() {
    if (mStopped) {
        //方法內(nèi)部會將mRetaining設(shè)置為true
        doReallyStop(true);
    }
    ...
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    //保存VM數(shù)據(jù)
    nci.viewModelStore = mViewModelStore;
    nci.fragments = fragments;
    return nci;//數(shù)據(jù)返回會被記錄
}

onRetainNonConfigurationInstance方法被retainNonConfigurationInstances方法調(diào)用,而它會被ActivityThread中performDestroyActivity方法調(diào)用,它執(zhí)行在onDestroy生命周期之前。

### ActivityThread
performDestroyActivity(...boolean getNonConfigInstance,...) {
    ...
    if (getNonConfigInstance) {
        //如果配置發(fā)生改變記錄下來
        r.lastNonConfigurationInstances
                = r.activity.retainNonConfigurationInstances();
    }
}

這樣VM數(shù)據(jù)就被封裝到了NonConfigurationInstances一個對象中了。

那何時被還原呢?答案是Activity的attach時。

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

VM數(shù)據(jù)源mViewModelStore在onCreate時被重新賦值。

### FragmentActivity
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //還原數(shù)據(jù)
    NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
    if (nc != null) {
        mViewModelStore = nc.viewModelStore;
    }
    ...
}

最后我們整理一下整體流程圖。

代碼流程.png

從上面的流程可以看出VM在Activity因配置變化導(dǎo)致重建時會被保留,從生命周期的角度來說,ViewModel的生命周期可能會長于Activity的生命周期。

這說明我們在使用ViewModel時一定要注意,不能讓其引用Activity或View,否則可能導(dǎo)致內(nèi)存泄漏。

好了,整個ViewModel的分析就結(jié)束了,希望你在了解其工作原理的同時對MVVM模式也有一些新的認(rèn)識。獨立的看ViewModel沒有什么實際意義,更像是數(shù)據(jù)容器,結(jié)合LiveData使用更容易理解,后續(xù)章節(jié)會繼續(xù)分享LiveData的內(nèi)容。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容