Jetpack系列-ViewModel使用和源码分析

1 简介和简单使用

1.1 简介

ViewModel以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存,保证数据的安全持久性。

如果Activity/Fragment销毁或重新创建界面,则存储在其中的任何瞬态界面相关数据都会丢失。对于简单的数据,Activity可以使用onSaveInstanceState()方法从onCreate()中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据。

另一个问题是,Activity/Fragment经常需要进行可能需要一些时间才能返回的异步调用。Activity/Fragment需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏。此项管理需要大量的维护工作,并且在重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。

ViewModelActiivity重建时会自动保存,可以确保数据不会丢失,并且ViewModel的生命周期比较长,ViewModel对象存在的时间范围是获取ViewModel时传递给ViewModelProviderLifecycleViewModel将一直留在内存中,直到限定其存在时间范围的Lifecycle永久消失:对于Activity,是在Activity完成时;而对于Fragment,是在Fragment分离时。

ViewModel的生命周期:

Google官方文档:developer.android.google.cn/topic/libra…

1.2 使用

创建RandomVideModel来保存数据,继承RandomVideModel

class RandomVideModel : ViewModel() {

    var num: Int = 0

}

Activity中实例化RandomVideModel,并从中获取数据。

class VideModelActivity : AppCompatActivity() {

    private lateinit var randomVideModel: RandomVideModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_vide_model)

        randomVideModel = ViewModelProvider(
            this,
            ViewModelProvider.NewInstanceFactory()
        ).get(RandomVideModel::class.java)

        tv_content.text = "${randomVideModel.num}"

        btn_random.setOnClickListener {
            tv_content.text = "${++randomVideModel.num}"
        }
    }
}

2 源码分析

首先看下ViewModel的源码,ViewModel代码很简单,内部用一个HashMap存储数据。

public abstract class ViewModel {
    // Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
    @Nullable
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    ...
}

ViewModel有3个子类,AndroidViewModelFragmentManagerViewModelLoaderViewModel

AndroidViewModel内部持有一个Application,可以方便使用上下文。

可以看到,是业务层ActivityViewModel不是直接new出来的,而是通过实例化一个ViewModelProvider对象,然后调用ViewModelProvider的get方法,传入了ViewModel的class文件,所以,ViewModel是通过反射创建出来的。

ViewModelProvider的构造方法,传入两个参数,一个是ViewModelStoreOwner,一个是FactoryAppCompatActivity继承的是ComponentActivityComponentActivity实现了ViewModelStoreOwner接口,所以这里传this就可以。另一个参数传Factory的实现类NewInstanceFactory

public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
    owner.viewModelStore,
    factory
)

接着通过this调用了构造方法ViewModelProvider(ViewModelStore,Factory)ViewModelStore就是存储ViewModel的类,里边是一个HashMap,提供了putgetclear方法。

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

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    //遍历HashMap,清除所有数据
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

回到业务层Activity中,调用ViewModelProviderget方法传入了自定义ViewModel的class。

@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    val canonicalName = modelClass.canonicalName
    ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

接着调用get(key: String, modelClass: Class<T>)。在get方法中,创建了ViewModel对象,然后将ViewModel存放到ViewModelStore中。

@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    var viewModel = store[key]
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)?.onRequery(viewModel)
        return viewModel as T
    } else {
        @Suppress("ControlFlowWithEmptyBody")
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    viewModel = if (factory is KeyedFactory) {
        factory.create(key, modelClass)
    } else {
        //由于传进来的是NewInstanceFactory,会走到这里。
        factory.create(modelClass)
    }
    //将ViewModel存放到ViewModelStore中。
    store.put(key, viewModel)
    return viewModel
}

由于传入的是NewInstanceFactory,所以最终调用的是Factory接口中的create(modelClass: Class<T>)

public interface Factory {
    public fun <T : ViewModel> create(modelClass: Class<T>): T
}

而具体实现在NewInstanceFactory中,NewInstanceFactory中的create方法中最终通过反射创建了ViewModel对象。

@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    //noinspection TryWithIdenticalCatches
    try {
        //通过反射创建ViewModel对象。
        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);
    }
}

来到ComponentActivity中,上面说过,ComponentActivity实现了ViewModelStoreOwner接口。

public interface ViewModelStoreOwner {
    /**
    * Returns owned {@link ViewModelStore}
    *
    * @return a {@code ViewModelStore}
    */
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelStoreOwner中有一个接口方法getViewModelStoreComponentActivity中实现了该方法。

@Override
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.");
    }
    ensureViewModelStore();
    return mViewModelStore;
}

调用了ensureViewModelStore去拿到ViewModelStore

void ensureViewModelStore() {
    if (mViewModelStore == null) {
        //这里的NonConfigurationInstances是ComponentActivity中的静态内部类。
        NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

ensureViewModelStore方法中实例化了一个NonConfigurationInstances,调用了getLastNonConfigurationInstance,从NonConfigurationInstances中拿ViewModelStore

public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
        ? mLastNonConfigurationInstances.activity : null;
}

而这个方法中又调用了mLastNonConfigurationInstances.activity,点进去跳转到了Activity类中的静态内部类NonConfigurationInstances

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}

这里的activityretainNonConfigurationInstances方法中被赋值。

NonConfigurationInstances retainNonConfigurationInstances() {
    //调用onRetainNonConfigurationInstance获取activity
    Object activity = onRetainNonConfigurationInstance();
    ...
    NonConfigurationInstances nci = new NonConfigurationInstances();
    //赋值给NonConfigurationInstances中的activity
    nci.activity = activity;
    ...
    return nci;
}

接着来到了Activity类中的onRetainNonConfigurationInstance,该方法的具体实现在ComponentActivity类中。

Activity中的onRetainNonConfigurationInstance

public Object onRetainNonConfigurationInstance() {
    return null;
}

ComponentActivity中的onRetainNonConfigurationInstance

public final Object onRetainNonConfigurationInstance() {
    // Maintain backward compatibility.
    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;
}

到这里,业务层Activity就拿到了ViewModelStore,就可以操作ViewModel中的数据了。

而如何保证数据的稳健性不会丢失,可以看ComponentActivity的构造方法。在ComponentActivity无参构造方法中有如下代码:

public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
    ...
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                                   @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                //如果配置了
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
 }

这里对Lifecycle状态进行了监听,当状态处于ON_DESTROY,也就是页面销毁时,先判断isChangingConfigurations是否为true,为true的话,就不用调用getViewModelStore().clear(),这样就保证了ViewModel中数据的持久稳健性。

public boolean isChangingConfigurations() {
    return mChangingConfigurations;
}

mChangingConfigurations的值,默认是false,但是在因为Configuration的改变被销毁了又重建的时候(最常见的就是横竖屏切换),mChangingConfigurations的值为true,ViewModelStore不调用clear,数据不会清除。这就保证了Activity销毁重建的时候数据不会丢失,还可以继续正常使用。

/** true if the activity is being destroyed in order to recreate it with a new configuration */
/*package*/ boolean mChangingConfigurations = false;

4 流程图

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

推荐阅读更多精彩内容