热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ViewModel源码研究之聊聊onSaveInstanceState和onRetainNonConfigurationInstance的区别

ViewModel源码研究之聊聊onSaveInstanceState和onRetainNonConfigurationInstance的区别-1.前言最近在研究ViewMode

1. 前言

最近在研究ViewModel实现原理。ViewModel有两个特性。

  1. 当配置发生改变时(例如:旋转屏幕),重新创建的Activity能够通过ViewModel将数据还原回来,
  2. 当按返回键或者调用finish方法时,ViewModel能够感知到onDestroy事件,同时将ViewModel保存的Closeable对象关闭掉(例如:主动关闭协程

当屏幕旋转时,会调用ActivityonRetainNonConfigurationInstance方法。ViewModel组件正是通过该方法将ViewModel保存起来,给重建的Activity使用。

//androidx.activity:activity:1.2.2@aar

//ComponentActivity.java

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还有个类似的方法onSaveInstanceState()onSaveInstanceState()onRetainNonConfigurationInstance() 的区别是:

  1. onSaveInstanceState() 调用的场景是:activity1启动activity2。生命周期调用顺序如下:

activity1.onPause()->activity2.onCreate()->activity2.onStart()->activity2.onResume()->activity1.onStop()->activity1.onSaveInstanceState(),

  1. onRetainNonConfigurationInstance() 调用场景是当configuration发生改变时,例如:旋转屏幕。

那么问题来了,一共有三个

  1. 它们存储的状态数据颗粒度一样吗?

  2. 它们把状态数据存储到哪里去了?

  3. 如果系统后台将Activity杀掉后,它们都能把状态恢复回来吗?

为了搞清楚这些问题,首先我在 "小站交流群" 提出了这些问题,幸运得是得到了一些积极的反馈。得到了一些结论和线索之后,便开始从源码中寻找答案,期间也遇到了一些问题,比如:ActivityManagerServiceactivityStopped方法的远程代理调用找不到,在群友们的帮助下,最终顺利找到,交流的过程中还是有不少收获。

2. onSaveInstanceState(Bundle outState)方法详解

首先在ComponentActivityonSaveInstanceState(Bundle outState) 方法中加个断点。调用栈如下: 重点关注ActivityThread.callActivityOnSaveInstanceState(Bundle outState)

2.1 ActivityThread.callActivityOnSaveInstanceState(Bundle outState)

//ActivityThread.java

private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
    r.state = new Bundle();
    r.state.setAllowFds(false);
    if (r.isPersistable()) {
        r.persistentState = new PersistableBundle();
        mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
                r.persistentState);
    } else {
        mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
    }
}

我们注意到r.state = new Bundle(), 原来outState参数是在这里创建的。Bundle可以用来组件间传递数据,也可以用来进程间传递数据。

重点关注ActivityThread.handleStopActivity()

2.2 ActivityThread.handleStopActivity()

public void handleStopActivity(IBinder token, boolean show, int configChanges,
        PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
    final ActivityClientRecord r = mActivities.get(token);
    r.activity.mConfigChangeFlags |= configChanges;

    final StopInfo stopInfo = new StopInfo();
    performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
            reason);

    if (localLOGV) Slog.v(
        TAG, "Finishing stop of " + r + ": show=" + show
        + " win=" + r.window);

    updateVisibility(r, show);

    // Make sure any pending writes are now committed.
    if (!r.isPreHoneycomb()) {
        QueuedWork.waitToFinish();
    }

    stopInfo.setActivity(r);
    stopInfo.setState(r.state);
    stopInfo.setPersistentState(r.persistentState);
    pendingActions.setStopInfo(stopInfo);
    mSomeActivitiesChanged = true;
}

注意到pendingActions.setStopInfo(stopInfo)

2.3 PendingTransactionActions$StopInfo.run()

@Override
public void run() {
    // Tell activity manager we have been stopped.
    try {
        if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + mActivity);
        // TODO(lifecycler): Use interface callback instead of AMS.
        ActivityManager.getService().activityStopped(
                mActivity.token, mState, mPersistentState, mDescription);
    } catch (RemoteException ex) {
        // Dump statistics about bundle to help developers debug
        final LogWriter writer = new LogWriter(Log.WARN, TAG);
        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
        pw.println("Bundle stats:");
        Bundle.dumpStats(pw, mState);
        pw.println("PersistableBundle stats:");
        Bundle.dumpStats(pw, mPersistentState);

        if (ex instanceof TransactionTooLargeException
                && mActivity.packageInfo.getTargetSdkVersion() 

该方法调用了ActivityManager.getService().activityStopped(mActivity.token, mState, mPersistentState, mDescription)方法。还将2.1中创建的mState当参数传进来了。

2.4 ActivityManagerService.activityStopped()

//ActivityManagerService.java
@Override
public final void activityStopped(IBinder token, Bundle icicle,
        PersistableBundle persistentState, CharSequence description) {
    if (DEBUG_ALL) Slog.v(TAG, "Activity stopped: token=" + token);

    // Refuse possible leaked file descriptors
    if (icicle != null && icicle.hasFileDescriptors()) {
        throw new IllegalArgumentException("File descriptors passed in Bundle");
    }

    final long origId = Binder.clearCallingIdentity();

    synchronized (this) {
        final ActivityRecord r = ActivityRecord.isInStackLocked(token);
        if (r != null) {
            r.activityStoppedLocked(icicle, persistentState, description);
        }
    }

    trimApplications();

    Binder.restoreCallingIdentity(origId);
}

重点关注r.activityStoppedLocked(icicle, persistentState, description)

2.5 ActivityRecord.activityStoppedLocked

final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState,
        CharSequence description) {
    final ActivityStack stack = getStack();
    if (mState != STOPPING) {
        Slog.i(TAG, "Activity reported stop, but no longer stopping: " + this);
        stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this);
        return;
    }
    if (newPersistentState != null) {
        persistentState = newPersistentState;
        service.notifyTaskPersisterLocked(task, false);
    }
    if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Saving icicle of " + this + ": " + icicle);

    if (newIcicle != null) {
        icicle = newIcicle;
        haveState = true;
        launchCount = 0;
        updateTaskDescription(description);
    }
    if (!stopped) {
        if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + this + " (stop complete)");
        stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this);
        stopped = true;
        setState(STOPPED, "activityStoppedLocked");

        mWindowContainerController.notifyAppStopped();

        if (finishing) {
            clearOptionsLocked();
        } else {
            if (deferRelaunchUntilPaused) {
                stack.destroyActivityLocked(this, true /* removeFromApp */, "stop-config");
                mStackSupervisor.resumeFocusedStackTopActivityLocked();
            } else {
                mStackSupervisor.updatePreviousProcessLocked(this);
            }
        }
    }
}

我们注意到最终bundle数据会保存在ActivityRecordicicle对象中。


总结onSaveInstanceState方法是当Activity调用了onStop后,会调用到ActivityThreadcallActivityOnSaveInstanceState()方法,把Activity需要保存的数据放入Bundle对象中,并且随后通过IPC进程间通信机制,调用ActivityManagerService的activityStopped方法,将Bundle对象保存到AMS端的ActivityRecord中。

2.6 被杀端后恢复数据过程

2.7 ActivityStackSupervisor.realStartActivityLocked

//ActivityStackSupervisor.java
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
          boolean andResume, boolean checkConfig) throws RemoteException {
     // 忽略其它代码
     // Create activity launch transaction.
      final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
              r.appToken);
      clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
              System.identityHashCode(r), r.info,
              // TODO: Have this take the merged configuration instead of separate global
              // and override configs.
              mergedConfiguration.getGlobalConfiguration(),
              mergedConfiguration.getOverrideConfiguration(), r.compat,
              r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
              r.persistentState, results, newIntents, mService.isNextTransitionForward(),
              profilerInfo));
    // 忽略其它代码
}

我们看到最终是通过ActivityRecord.icicle恢复数据。

2.8 LaunchActivityItem.execute()

//LaunchActivityItem.java
@Override
public void execute(ClientTransactionHandler client, IBinder token,
        PendingTransactionActions pendingActions) {
    Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
            mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
            mPendingResults, mPendingNewIntents, mIsForward,
            mProfilerInfo, client);
    client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
    Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}

2.9 ActivityThread.performLaunchActivity(ActivityClientRecord r, Intent customIntent)

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent){
   activity.mCalled = false;
    if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    } else {
        mInstrumentation.callActivityOnCreate(activity, r.state);
    }
}

3. onRetainNonConfigurationInstance()

该方法是在重建Activity时调用performDestoryActivity时会保存数据。

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
          int configChanges, boolean getNonConfigInstance, String reason) {
  ActivityClientRecord r = mActivities.get(token);
  //省略一些代码
      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);
        }
      }
  }

我们可以看到onRetainNonConfigurationInstance方法返回的Object会赋值给ActivityClientRecordlastNonConfigurationInstances

4. 答案
  1. 颗粒度不一样。onSaveInstanceState()是保存到Bundle中,只能保存Bundle能接受的数据类型,比如一些基本类型的数据。而onRetainNonConfigurationInstance() 可以保存任何类型的数据,数据类型是Object

  2. onSaveInstanceState()数据最终存储到ActivityManagerServiceActivityRecord中了,也就是存到系统进程中去了。而onRetainNonConfigurationInstance() 数据是存储到ActivityClientRecord中,也就是存到应用本身的进程中了

  3. onSaveInstanceState存到系统进程中,所以App被杀之后还是能恢复的。而onRetainNonConfigurationInstance存到本身进程中,App被杀是没法恢复的。

欢迎关注“字节小站”同名公众号


推荐阅读
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了如何清除Eclipse中SVN用户的设置。首先需要查看使用的SVN接口,然后根据接口类型找到相应的目录并删除相关文件。最后使用SVN更新或提交来应用更改。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
author-avatar
淘客淘宝客
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有