ViewModel是如何在配置更改后继续留存数据的

ViewModel是如何在配置更改后继续留存数据的

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

那么我们今天就探索下,配置更改后,是如何继续留存数据的。

首先我们看下如何创建ViewModel实例:

class CustomFactory : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return when (modelClass) {
            MainViewModel::class.java -> {
                MainViewModel()
            }
            else -> throw IllegalArgumentException("Unknown class $modelClass")
        } as T
    }
}

创建ViewModel实例

// 1
val viewModelProvider = ViewModelProvider(this, CustomFactory())
// 2
val viewModel: MainViewModel= viewModelProvider.get(MainViewModel::class.java)
// 或者使用KTX来创建
//val model : MainViewModel by viewModels { CustomFactory() }

ViewModelProvider源码

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull ViewModelProvider.Factory factory) {
	 // 3
    this(owner.getViewModelStore(), factory);
}

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

ViewModelStoreOwner源码

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

看看上面这段代码做了什么:

  1. 首先创建一个ViewModelProvider实例,ViewModelProvider构造函数的第一个参数是ViewModelStoreOwner,那为什么可以传入Activity实例呢,是因为ComponentActivity实现了ViewModelStoreOwner这个接口;

  2. 通过 get(@NonNull Class<T> modelClass) 方法 获取到 ViewModel 的实例;

  3. 通过 owner.getViewModelStore()获取到 ViewModelStore,也就是ComponentActivitygetViewModelStore()方法。

然后我们看下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");
    } else {
        return this.get("androidx.lifecycle.ViewModelProvider.DefaultKey:" + canonicalName, modelClass);
    }
}

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = this.mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        if (this.mFactory instanceof ViewModelProvider.OnRequeryFactory) {
            ((ViewModelProvider.OnRequeryFactory)this.mFactory).onRequery(viewModel);
        }

        return viewModel;
    } else {
        if (viewModel != null) {
        }

        if (this.mFactory instanceof ViewModelProvider.KeyedFactory) {
            viewModel = ((ViewModelProvider.KeyedFactory)this.mFactory).create(key, modelClass);
        } else {
            viewModel = this.mFactory.create(modelClass);
        }

        this.mViewModelStore.put(key, viewModel);
        return viewModel;
    }
}

从在上面的代码中可以发现,ViewModelStore中持有一个HashMap,如果ViewModelStore中有缓存的ViewModel实例,就直接返回,否则创建新的实例并存入到ViewModelStore中。

接下来我们看下 ComponentActivity#getViewModelStore()方法:

@NonNull
public ViewModelStore getViewModelStore() {
    if (this.getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
    } else {
        this.ensureViewModelStore();
        return this.mViewModelStore;
    }
}

void ensureViewModelStore() {
    if (this.mViewModelStore == null) {
        ComponentActivity.NonConfigurationInstances nc = (ComponentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
        if (nc != null) {
            this.mViewModelStore = nc.viewModelStore;
        }

        if (this.mViewModelStore == null) {
            this.mViewModelStore = new ViewModelStore();
        }
    }

}

从以上代码中可以看出:

  1. 先是从ActivitygetLastNonConfigurationInstance()获取一个NonConfigurationInstances实例nc

  2. 如果这个nc不等于空,就将nc的viewModelStore赋值给ComponentActivitymViewModelStore字段;

  3. 如果mViewModelStore还是为null,就创建一个新的mViewModelStore对象;

很明显ActivitygetLastNonConfigurationInstance()是缓存ViewModelStore的核心。

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

这个方法返回的是 ActivityonRetainNonConfigurationInstance()持有的对象,但是Activity的这个方法返回的是null

根据源码注释可知,一旦配置更改销毁Activity,系统将为新配置创建一个新的Activity实例时,将由Android系统调用此方法

我们可以在这个方法返回任何对象,包括Activity实例本身,稍后可以通过在新Activity实例中调用getLastNonfigurationInstance()来检索到它。

继续跟踪源码发现在ActivityretainNonConfigurationInstances()中调用了onRetainNonConfigurationInstance()方法。

NonConfigurationInstances retainNonConfigurationInstances() {
	// 1
    Object activity = onRetainNonConfigurationInstance();
    HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    ......
    ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
	// 2
    if (activity == null && children == null && fragments == null && loaders == null
            && mVoiceInteractor == null) {
        return null;
    }
	// 3
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    ......
    return nci;
}

从以上代码中可以看出:

  1. 先是调用onRetainNonConfigurationInstance()获取一个对象,这个对象可以是任何我们想要在配置更改时要保存的对象;

  2. 只要这些对象全部为null,才会不返回数据;

  3. 创建一个静态内部类NonConfigurationInstances的实例nci,然后将赋值给nciactivity字段;

那么谁实现了 ActivityonRetainNonConfigurationInstance()呢,通过追踪代码发现是ComponentActivity重写了这个方法:

public final Object onRetainNonConfigurationInstance() {
    Object custom = this.onRetainCustomNonConfigurationInstance();
    ViewModelStore viewModelStore = this.mViewModelStore;
    ComponentActivity.NonConfigurationInstances nci;
    if (viewModelStore == null) {
        nci = (ComponentActivity.NonConfigurationInstances) this.getLastNonConfigurationInstance();
        if (nci != null) {
            viewModelStore = nci.viewModelStore;
        }
    }
    if (viewModelStore == null && custom == null) {
        return null;
    } else {
        nci = new ComponentActivity.NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
}

总结一下:

  1. 如果我们想要在系统配置发生更改时保存任何对象,并在系统为新配置创建新Activity实例时恢复这个对象,那么我们只需要重写Activity的onRetainNonConfigurationInstance()方法即可。
  2. 根据第一条,当配置更改销毁Activity时,onRetainNonConfigurationInstance()保存了ViewModelStore,而ViewModel又通过ViewModelStore来存取,因此当Activity重建时,就能获取到之前的ViewModel

系统是如何保留和恢复NonConfigurationInstances

从上一个章节我们知道,ActivityonRetainNonConfigurationInstance()是由系统调用的,那么系统在什么时机调用的呢?

根据经验,ActivityThread负责Activity的调度和执行,那么我们就去ActivityThread中搜索下ActivityretainNonConfigurationInstances()方法。

提示:Android Framework源码一般会有如下的关系链路:

schedule(安排上) ————> handler(处理) ————> perform(执行) ————> on(在进行)

ActivityThreadperformDestroyActivity()方法

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;
        }
        performPauseActivityIfNeeded(r, "destroy");
        if (!r.stopped) {
            callActivityOnStop(r, false /* saveState */, "destroy");
        }
        // 1
        if (getNonConfigInstance) {
            try {
                r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    ...
                }
            }
        }
        try {
            r.activity.mCalled = false;
            mInstrumentation.callActivityOnDestroy(r.activity);
            if (!r.activity.mCalled) {
                ...
            }
            if (r.window != null) {
                r.window.closeAllPanels();
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            ...
        }
        r.setState(ON_DESTROY);
    }
    schedulePurgeIdler();
    
    synchronized (mResourcesManager) {
        mActivities.remove(token);
    }
    StrictMode.decrementExpectedActivityCount(activityClass);
    return r;
}

Activity因配置更改被销毁重建时,会调用ActivityThreadhandleRelaunchActivityInner()方法:

private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
                                         List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
                                         PendingTransactionActions pendingActions, boolean startsNotResumed,
                                         Configuration overrideConfig, String reason) {
    ...
	// 1
    handleDestroyActivity(r.token, false, configChanges, true, reason);

    ...
	// 2
    handleLaunchActivity(r, pendingActions, customIntent);
}

从上面的代码可以看出:

  1. 首先执行handleDestroyActivity ,并且将 getNonConfigInstance 设为 true, 这样就可以将ActivityonRetainNonConfigurationInstance() 保留的数据,保存到 ActivityClientRecord 中;
  2. 然后执行 handleLaunchActivity(),重建Activity,并将数据恢复。

ActivityThreadperformLaunchActivity()

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }

    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }

    if (r.activityInfo.targetActivity != null) {
        component = new ComponentName(r.activityInfo.packageName,
                r.activityInfo.targetActivity);
    }

    ContextImpl appContext = createBaseContextForActivity(r);
    // 1
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            ...
        }
    }


    Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    ...
    if (activity != null) {
        ...
        appContext.getResources().addLoaders(
                app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
		// 2
        appContext.setOuterContext(activity);
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config,
                r.referrer, r.voiceInteractor, window, r.configCallback,
                r.assistToken);

        if (customIntent != null) {
            activity.mIntent = customIntent;
        }
        r.lastNonConfigurationInstances = null;
        ...
        int theme = r.activityInfo.getThemeResource();
        if (theme != 0) {
            activity.setTheme(theme);
        }

        activity.mCalled = false;
        if (r.isPersistable()) {
            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        } else {
            mInstrumentation.callActivityOnCreate(activity, r.state);
        }
        ...
        r.activity = activity;
        ...
    }
    r.setState(ON_CREATE);

    synchronized (mResourcesManager) {
        mActivities.put(r.token, r);
    }

    ...
    return activity;
}

从以上代码可以看出:

  1. 创建Activity的实例;
  2. attach 数据,将因配置更改销毁Activity留存到ActivityClientRecord中持有的 lastNonConfigurationInstances 赋值给新建的 Activity
  3. 之后就可以通调用ActivitygetLastNonConfigurationInstance() 检索到 onRetainNonConfigurationInstance() 方法保留的对象。

Activityattach()方法

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, IBinder assistToken) {
    attachBaseContext(context);

    mFragments.attachHost(null /*parent*/);

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    ...
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
    ...
}

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

流程图
上图是Framework源码留存和恢复NonConfigurationInstances的大致调用流程。

ViewModel 和 onSaveInstanceState 情况一样吗

onSaveInstanceState 方法的使用:

public override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putSerializable("currentScreen", currentScreen)
    outState.putParcelableArrayList("backstack", backstack)
}

onSaveInstanceState 方法在 Activity 可能被杀死之前被调用,以便当它在将来某个时间返回时可以恢复它的状态。例如,如果Activity B 在 Activity A 的前面被启动,在某个时间点 Activity A 被杀死回收资源,Activity A 将有机会通过这个方法保存其用户界面的当前状态,这样当用户返回到 Activity A 时,用户界面的状态可以通过 onCreate()onRestoreInstanceState() 来恢复。

如果该方法被调用,该方法将发生在 android.os.Build.VERSION_CODESP 以后平台版本的应用程序的 onStop 之后。对于以较早平台版本为目标的应用程序,该方法将出现在 onStop 之前,并且不能保证它将出现在 onPause 之前还是之后。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.leak_canary_leak_activity)
    if (savedInstanceState == null) {
       ...
    } else {
        currentScreen = savedInstanceState.getSerializable("currentScreen") as Screen
        backstack = savedInstanceState.getParcelableArrayList<Parcelable>(
            "backstack"
        ) as ArrayList<BackstackFrame>
    }
}

Activity 通常会在下面三种情况下被销毁:

  1. 从当前界面永久离开:用户导航至其他界面或直接关闭 Activity (通过点击返回按钮或执行的操作调用了 finish 方法)。对应 Activity 实例被永久关闭;

  2. Activity 配置 (configuration) 被改变: 例如,旋转屏幕等操作,会使 Activity 需要立即重建;

  3. 应用在后台时,其进程被系统杀死:这种情况发生在设备剩余运行内存不足,系统又亟须释放一些内存的时候。当进程在后台被杀死后,用户又返回该应用时,Activity 也需要被重建。

在后两种情况中,我们通常都希望重建 ActivityViewModel 会帮我们处理第二种情况,因为在这种情况下 ViewModel 没有被销毁;而在第三种情况下, ViewModel 被销毁了。所以一旦出现了第三种情况,便需要在 ActivityonSaveInstanceState 相关回调中保存和恢复 ViewModel 中的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值