Android架构组件(Architecture Components)之 ViewModel详解

写在前面

组件架构三剑客终于来到了最后一篇:ViewModel,关于LifecycleLiveData可以看之前的文章。ViewModel和Lifecycle和LiveData的关联并不大,可以单独拿出来使用。这里用的依赖主要是AndroidX里面的,其他版本可能有些不同,但核心逻辑应该还是一致的。

implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha01'

我们都知道,ViewModel的核心功能之一就是帮我们保存数据,特别是当Activity重建时还能将数据保存下来。页面重建保存数据,看到这里我们可能很容易的就想到了onSaveInstanceState()方法,我们可以在这里保存数据,以便Activity重新创建时在onCreate()方法中接收到保存下来的数据。但它是有局限的,只能保存一些简单的数据或者一些经过序列化的数据,并且数据量还不能太大。ViewModel的出现,正好弥补了这一不足。

ViewModel结构与Lifecycle的结构类似,Activity/Fragment除了实现一个LifecycleOwner接口外,还实现了一个ViewModelStoreOwner接口,它只有一个方法用来获取ViewModelStore,然后通过ViewModelStore来负责添加和移除ViewModel。

viewmodel.png

ViewModelProvider

ViewModel的创建是ViewModelProvider提供的。要看ViewModel是怎么被创建并添加进ViewModelStore的,还得先从我们最熟悉的api入手。

// 这里的this是指Activity
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

先创建一个ViewModelProvider,再通过它来获取ViewModel。

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

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    this(store, new FactoryWrapper(factory));
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull KeyedFactory factory) {
    // 要注意这里的mFactory是KeyedFactory
    mFactory = factory;
    mViewModelStore = store;
}

Factory

这个方法的目的是要构造一个ViewModelProvider,先来讲讲工厂Factory,ViewModelStore
放到后面讲。默认我们没有传工厂进来,这里会帮我们构建一个AndroidViewModelFactory对象。因为这里面出现了很多Factory相关的类,所以我觉得还是有必要先将Factory的结构讲一下,有助于了解。

factory.png

结合上面的代码和类图来看,这个框架会默认给我们提供一个AndroidViewModelFactory工厂对象,然后又将它封装成了一个KeyedFactory对象,再加上ViewModelStore对象,一起构造出了ViewModelProvider。短短的一个方法里面出现了工厂模式(Factory)和装饰器模式(FactoryWrapper),真的是很佩服。

ViewModel

拿到ViewModelProvider对象后,再来看它的get方法:

@NonNull
@MainThread
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);
}

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

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    // mFactory指的是FactoryWrapper
    viewModel = mFactory.create(key, modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

首先是根据class获取一个key值,然后先从mViewModelStore中查询有没有相应的ViewModel,当然我们第一次调用肯定是拿不到的,需要走下面的创建步骤,创建完再添加到ViewModelStore中去。

private static class FactoryWrapper implements KeyedFactory {
    private final Factory mFactory;

    FactoryWrapper(Factory factory) {
        mFactory = factory;
    }

    @Override
    public <T extends ViewModel> T create(String key, Class<T> modelClass) {
        // mFactory指的是AndroidViewModelFactory
        return mFactory.create(modelClass);
    }
}

很奇怪的是这里的key并没有被用到,不知道后面会不会添加一些新的东西进来。

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.getConstructor(Application.class).newInstance(mApplication);
        } catch (...) {
            // 一些catch
        }
    }
    return super.create(modelClass);
}

如果这个ViewModel是继承自AndroidViewModel的话,就用AndroidViewModelFactory创建一个,否则,用NewInstanceFactory创建一个普通的ViewModel。

Android给的建议是不要在ViewModel中使用到任何与Android相关的代码,最简单的检查办法就是看有没有import进*.android.*相关的东西。但是这又有点不现实,因为我们经常需要用到Context去获取一些资源。为了解决这个问题,Android就给我们提供了AndroidViewModel。所以如果有这个需求的话可以让你的ViewModel继承AndroidViewModel。
另外,从ViewModel的创建过程来看,如果我们需要在构造函数里传一些别的参数的话,就需要自己去构建工厂类了。

讲完了Factory,再回过头来看ViewModelStore,Activity/Fragment创建ViewModel的过程唯一的区别就在于ViewModelStore获取方式的不同。

activity.getViewModelStore();
fragment.getViewModelStore();

Activity.getViewModelStore()

@NonNull
@Override
public ViewModelStore getViewModelStore() {

    ......
    
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

首次调用这个方法,肯定是通过new方法拿到的,可以猜出ViewModelStore的缓存就是通过NonConfigurationInstances缓存下来的。而它又是在onRetainNonConfigurationInstance()方法中保存下来的,然后使用getLastNonConfigurationInstance()获取出来的。这个与onSaveInstanceState()onRestoreInstanceState()是类似的。

@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

关于onRetainNonConfigurationInstance()getLastNonConfigurationInstance()这两个方法可以来Activity中看一下具体的流程。首先状态得保存下来,才能在重建的时候取出来。既然是要保存状态,那肯定是在onDestroy()的时候保存的,所以直接来看看ActivityThread$performDestroyActivity()

// ActivityThread.java
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    Class<? extends Activity> activityClass = null;
    if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
    if (r != null) {
        activityClass = r.activity.getClass();
        r.activity.mConfigChangeFlags |= configChanges;
        if (finishing) {
            r.activity.mFinished = true;
        }

        // 如果还没调用onPause()的话,调用onPause()
        performPauseActivityIfNeeded(r, "destroy");

        if (!r.stopped) {
            // 调用onStop()
            callActivityOnStop(r, false /* saveState */, "destroy");
        }
        if (getNonConfigInstance) {
            try {
                // 划重点,保存状态
                r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Unable to retain activity "
                            + r.intent.getComponent().toShortString()
                            + ": " + e.toString(), e);
                }
            }
        }
        try {
            r.activity.mCalled = false;
            // 调用onDestroy()
            mInstrumentation.callActivityOnDestroy(r.activity);
            if (!r.activity.mCalled) {
                throw new SuperNotCalledException(
                    "Activity " + safeToComponentShortString(r.intent) +
                    " did not call through to super.onDestroy()");
            }
            if (r.window != null) {
                r.window.closeAllPanels();
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException(
                        "Unable to destroy activity " + safeToComponentShortString(r.intent)
                        + ": " + e.toString(), e);
            }
        }
        r.setState(ON_DESTROY);
    }
    mActivities.remove(token);
    StrictMode.decrementExpectedActivityCount(activityClass);
    return r;
}

再继续跟到Activity里面

NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

    mFragments.doLoaderStart();
    mFragments.doLoaderStop(true);
    ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

    if (activity == null && children == null && fragments == null && loaders == null
            && mVoiceInteractor == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    if (mVoiceInteractor != null) {
        mVoiceInteractor.retainInstance();
        nci.voiceInteractor = mVoiceInteractor;
    }
    return nci;
}

第一行就是调用onRetainNonConfigurationInstance()来保存Activity的状态。这个方法返回的NonConfigurationInstances对象保存在ActivityClientRecord中,然后在重启Activity的onAttach()方法中将NonConfigurationInstances拿回来,从而实现了数据的不丢失。

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback) {
    
    ......
    
    // 取回NonConfigurationInstances
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
}

Fragment.getViewModelStore()

// Fragment.java
@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (mFragmentManager == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    return mFragmentManager.getViewModelStore(this);
}

// FragmentManagerImpl.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    return mNonConfig.getViewModelStore(f);
}

// FragmentManagerViewModel.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore == null) {
        viewModelStore = new ViewModelStore();
        mViewModelStores.put(f.mWho, viewModelStore);
    }
    return viewModelStore;
}

可以看到,最后是通过FragmentManagerViewModel来保存ViewModelStore的,key是相应的Fragment的ID。哦对了,FragmentManagerViewModel自己也是一个ViewModel。

Fragment的数据恢复是在FragmentActivity$onCreate()里面做的。

@SuppressWarnings("deprecation")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // 在这里恢复数据
    mFragments.attachHost(null /*parent*/);

    super.onCreate(savedInstanceState);
    
    ......
}

最终走到FragmentManagerImpl中去:

public void attachController(@NonNull FragmentHostCallback host,
                             @NonNull FragmentContainer container, @Nullable Fragment parent) {
    if (mHost != null) throw new IllegalStateException("Already attached");
    mHost = host;
    mContainer = container;
    mParent = parent;
    if (parent != null) {
        mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
    } else if (host instanceof ViewModelStoreOwner) {
        // 这里是通过Activity的getViewModelStore()获取ViewModelStore的
        ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
        mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
    } else {
        mNonConfig = new FragmentManagerViewModel(false);
    }
}

在这里初始化mNonConfig,所以我们才能通过mNonConfig.getViewModelStore()去获取ViewModelStore。重新创建Activity的时候,是通过Activity的getViewModelStore()去拿到ViewModelStore的,再通过它去拿到FragmentManagerViewModel。这里感觉有点绕。可以这么说吧,Fragment的ViewModelStore是存放到FragmentManagerViewModel中的,然后FragmentManagerViewModel又被放到了Activity的ViewModelStore中,Activity在保存数据的时候自然也就将FragmentManagerViewModel保存了下来,从而将Fragment的ViewModelStore保存了下来。

另外,网上有一些文章说Fragment的ViewModelStore是通过Fragment$setRetainInstance()来保存的,可能是引用的版本号不一样吧,毕竟Android还在继续更新中。我这里通过调试发现是以这种方式保存的。可能以后版本不一样又会有所改动了。

最后

到这里,Lifecycle, LiveData和ViewModel的原理,总算是解析完了。其中至少是用到了观察者模式,工厂模式,装饰器模式,自己分析完之后还是有所收获的。也可以看到ViewModel的原理和Activity的生命周期最加紧密,先给自己挖个坑,后面找时间再来写一篇Activity的启动流程的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容