Android13 Activity生命周期源码梳理(一:基本认知)

Activity 生命周期概览

Activity的生命周期应该是广大 Androider 最为熟知的一个知识点了,毕竟 Activity 是应用开发平时打交道最多的一个系统组件。从宏观角度看各个生命周期只不过是Android系统这个庞大的机器开放给用户(开发者)填放生产原料的操作口子而已,具体运作还是完全依赖于系统机器的逻辑与机制。所以生命周期虽然看似是老生常谈的问题,但是这道口子之内隐藏着 Android 这台机器极其复杂的内部构造与优化机制,在实际的生产环境下也会遇到各种奇怪偶现的问题,这时候生命周期往往是一个很好的分析定位入手点,下面从几个关键点来分析一下 Activity 的生命周期流转过程。

下面这张图可以很好概括各个生命周期的流转过程:

下面这张图可以很好概括各个生命周期流转过程中给用户呈现的状态:

  • onCreate与onDestroy决定activity创建与销毁的过程。
  • onStart与onStop决定activity是否可见的过程。
  • onResume与onPause决定activity是否在前台的过程。

基本认知

下面介绍一些生命周期流转过程中需要了解的基本认知或者说关键点,繁杂的源码梳理放在后面,先抓关键点,了解大框架,再慢慢深入细化。

生命周期回调顺序

Activity a 启动了一个Activity b,他们的生命周期回调方法是:

scss 复制代码
onPause(a) -- onCreate(b) -- onStart(b) -- onResume(b) -- onStop(a)

在 b 中调用 finish() 方法,它们的生命周期执行顺序是:

scss 复制代码
onPause(b) - onRestart(a) - onStart(a) - onResume(a) - onStop(b) - onDestroy(b)

关键问题:a进入后台之后为啥马上进入b的创建显示流程最后才是a的不可见以及销毁流程?

简单讲就是为了用户体验而设计的优化,Android总是先处理用户感知强烈的任务,新的activity尽快进入前台并呈现内容无疑是用户打开新界面最期望的结果,而不可见的页面状态处理就可以等到高优先级任务都处理完之后再进行处理,当然这里也会有超时机制确保 onStop 与 onDestroy 指定时间内被执行,用户体验体验为先,按照优先级安排任务。(这里先对整个流程有个大概的印象,后面可以在源码梳理中完全理解这个流程运行的细节)

正常event日志示例

在实际的开发过程中,由于各种业务需求以及团队分工,各个 APP 会出现各种牛鬼蛇神的复杂场景需求以及各种稀奇古怪的实现方式,像弹窗的activity,透明的activity等等,以及涉及到开机以及切换系统用户的复杂高负载场景往往会出现各种偶现疑难问题。当出现复杂偶现问题的时候,第一步需要做的就是尽快确定职责团队,当你拿到测试抓取的几百兆上 GB 的系统日志的时候,第一步肯定是筛选出问题时间点附近的日志,如果是怀疑与生命周期相关的问题,第二步往往就是梳理一下系统event日志中activity的生命周期看是否能提供一些线索。

获取event日志命令: adb logcat -b events

下面是在模拟器上从gms界面打开dialer应用正常情况的event日志,可以用于参考对比。

less 复制代码
在gms界面打开dialer应用正常日志供参考:

04-18 20:02:06.989   578   663 I am_unfreeze: [6503,com.google.android.gms,6]
04-18 20:02:07.270   578   673 I input_interaction: Interaction with: b290d68 Taskbar (server), [Gesture Monitor] swipe-up (server), [Gesture Monitor] edge-swipe (server), PointerEventDispatcher0 (server), 
04-18 20:02:08.655   578  2409 I wm_task_created: 22
04-18 20:02:08.690   578  2409 I wm_task_moved: [22,22,0,1,3]
04-18 20:02:08.690   578  2409 I wm_task_to_front: [0,22,0]
04-18 20:02:08.693   578  2409 I wm_create_task: [0,22,22,0]
04-18 20:02:08.694   578  2409 I wm_create_activity: [0,210082640,22,com.google.android.dialer/.extensions.GoogleDialtactsActivity,android.intent.action.MAIN,NULL,NULL,270532608]
04-18 20:02:08.697   578  2409 I wm_task_moved: [22,22,0,1,3]
04-18 20:02:08.704  6550  6550 I wm_on_top_resumed_lost_called: [96798285,com.google.android.gms.auth.uiflows.minutemaid.MinuteMaidActivity,topStateChangedWhenResumed]
04-18 20:02:08.712   578  2409 I wm_pause_activity: [0,96798285,com.google.android.gms/.auth.uiflows.minutemaid.MinuteMaidActivity,userLeaving=true,pauseBackTasks]
04-18 20:02:08.742   578   598 I am_uid_running: 10125
04-18 20:02:08.759  6550  6550 I wm_on_paused_called: [96798285,com.google.android.gms.auth.uiflows.minutemaid.MinuteMaidActivity,performPause,1]
04-18 20:02:08.764   578   775 I wm_add_to_stopping: [0,96798285,com.google.android.gms/.auth.uiflows.minutemaid.MinuteMaidActivity,makeInvisible]
04-18 20:02:08.807   578   673 I input_focus: [Focus leaving f83961b com.google.android.gms/com.google.android.gms.auth.uiflows.minutemaid.MinuteMaidActivity (server),reason=NO_WINDOW]
04-18 20:02:08.958   578   606 I am_proc_start: [0,6768,10125,com.google.android.dialer,next-top-activity,{com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity}]
04-18 20:02:09.036   578   775 I wm_new_intent: [0,210082640,22,com.google.android.dialer/.extensions.GoogleDialtactsActivity,android.intent.action.MAIN,NULL,NULL,270532608]
04-18 20:02:09.036   578   775 I wm_task_moved: [22,22,0,1,3]
04-18 20:02:09.063   578  1785 I am_proc_bound: [0,6768,com.google.android.dialer]
04-18 20:02:09.065   578  1785 I wm_restart_activity: [0,210082640,22,com.google.android.dialer/.extensions.GoogleDialtactsActivity]
04-18 20:02:09.066   578  1785 I wm_set_resumed_activity: [0,com.google.android.dialer/.extensions.GoogleDialtactsActivity,minimalResumeActivityLocked - onActivityStateChanged]
04-18 20:02:09.068   578   598 I am_uid_active: 10125
04-18 20:02:09.532   578   663 I am_unfreeze: [3251,com.google.android.contacts,3]
04-18 20:02:09.616  6768  6768 I wm_on_create_called: [210082640,com.google.android.dialer.extensions.GoogleDialtactsActivity,performCreate,152]
04-18 20:02:09.744   578  2409 I am_uid_active: 10082
04-18 20:02:09.805  6768  6768 I wm_on_start_called: [210082640,com.google.android.dialer.extensions.GoogleDialtactsActivity,handleStartActivity,187]
04-18 20:02:09.805   578   663 I am_unfreeze: [1436,android.process.acore,7]
04-18 20:02:09.947  6768  6768 I wm_on_resume_called: [210082640,com.google.android.dialer.extensions.GoogleDialtactsActivity,RESUME_ACTIVITY,113]
04-18 20:02:10.005  6768  6768 I wm_on_top_resumed_gained_called: [210082640,com.google.android.dialer.extensions.GoogleDialtactsActivity,topStateChangedWhenResumed]
04-18 20:02:10.028   578   599 I input_focus: [Focus request 61338b3 com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity,reason=UpdateInputWindows]
04-18 20:02:10.408   578   673 I input_focus: [Focus entering 61338b3 com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity (server),reason=Window became focusable. Previous reason: NOT_VISIBLE]
04-18 20:02:10.426   578   595 I wm_activity_launch_time: [0,210082640,com.google.android.dialer/.extensions.GoogleDialtactsActivity,1800]
04-18 20:02:10.773   578  2395 I wm_stop_activity: [0,96798285,com.google.android.gms/.auth.uiflows.minutemaid.MinuteMaidActivity]
04-18 20:02:10.870  6550  6550 I wm_on_stop_called: [96798285,com.google.android.gms.auth.uiflows.minutemaid.MinuteMaidActivity,STOP_ACTIVITY_ITEM,2]

如上 event 日志所示,每个 activity 生命周期都会伴随两次相关 event 日志打印,分别代表对应生命周期执行之前与之后,正常情况下都是成对出现的,比如:

css 复制代码
wm_pause_activity:系统准备对此 activity 执行 pause 生命周期了,马上会发送 pause 事务去客户端执行 activity 的onPause 回调。
wm_on_paused_called:客户端(APP)activity 的onPause 回调已经执行结束。

关键点:可以通过对应生命周期 event 日志大概判断对应 APP 是否在生命周期函数里执行了耗时操作,以及出现异常情况的生命周期阶段。

生命周期事务

ClientTransactionItem

在系统中 Activity 的各个生命周期的执行动作/命令被封装成了一个个事务,从而简化了服务端(系统)与客户端(APP)之间的沟通协作,使得activity的生命周期总是能够按照顺序执行下去。 所有这些生命周期事务类都实现了 BaseClientRequest 接口,实现了 preExecute ,execute ,postExecute 3个方法。 其中最关键的 execute 方法表示真正触发应用端执行对应的生命周期,而 preExecute,,postExecute 分别对应生命周期前后该做的事。

ini 复制代码
frameworks/base/core/java/android/app/servertransaction/

1. 继承链路
BaseClientRequest[inf] extends ObjectPoolItem[inf]
    ClientTransactionItem[abs] implements BaseClientRequest[inf], Parcelable[inf]
        ActivityTransactionItem[abs] extends ClientTransactionItem[abs] 
            ActivityLifecycleItem[abs] extends ActivityTransactionItem[abs] 
                xxxActivityItem extends ActivityLifecycleItem[abs] 
    
2. ObjectPool 
- 缓存数据结构:Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap = new HashMap<>();
- 每一个class对应的ArrayList最大缓存容量为50:final int MAX_POOL_SIZE = 50;

3. item 初始化举例
//1.先从缓存取
PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
//2.缓存中获取不到则初始化,recycle的时候会加入ObjectPool
if (instance == null) {
    instance = new PauseActivityItem();
}

4. Activity生命周期相关Item 罗列 (系统中还有其他作用的TransactionItem定义,此处省略)
LaunchActivityItem      onCreate
StartActivityItem       Start
ResumeActivityItem      onResume
PauseActivityItem       Pause
StopActivityItem        Stop
ActivityRelaunchItem    relaunch
ActivityResultItem      result
ClientTransaction

ClientTransaction是一个服务端按照顺序保存要发送到客户端的消息的容器,其中利用一个ArrayList来保存所有ClientTransactionItem,确保后续按照顺序执行。

csharp 复制代码
base/core/java/android/app/servertransaction/ClientTransaction.java

/**
* A container that holds a sequence of messages, which may be sent to a client.
* This includes a list of callbacks and a final lifecycle state.
*/
public class ClientTransaction implements Parcelable, ObjectPoolItem {

    /** A list of individual callbacks to a client. */
    @UnsupportedAppUsage
    private List<ClientTransactionItem> mActivityCallbacks;
    
    /**
    * Add a message to the end of the sequence of callbacks.
    * @param activityCallback A single message that can contain a lifecycle request/callback.
    */
    public void addCallback(ClientTransactionItem activityCallback) {
        if (mActivityCallbacks == null) {
            mActivityCallbacks = new ArrayList<>();
        }
        mActivityCallbacks.add(activityCallback);
    }
    
}
TransactionExecutor

TransactionExecutor是一个封装了ClientTransaction执行的相关函数,确保transaction按照正确的顺序执行下去的管理工具类。其中通过cycleToPath计算出当前状态到指定生命周期需要经过多少个中间状态,然后按照顺序执行,比如当前是 onCreate 状态,需要执行到 onResume 状态的话,中间必须执行 onStart 状态,否则生命周期就乱了。

scss 复制代码
frameworks/base/core/java/android/app/servertransaction/TransactionExecutor.java

/**
 * Class that manages transaction execution in the correct order.
 * @hide
 */
public class TransactionExecutor {
     ......

    /**
    * Resolve transaction.
    * First all callbacks will be executed in the order they appear in the list. If a callback
    * requires a certain pre- or post-execution state, the client will be transitioned accordingly.
    * Then the client will cycle to the final lifecycle state if provided. Otherwise, it will
    * either remain in the initial state, or last state needed by a callback.
    */
    public void execute(ClientTransaction transaction) {

           // ...

            // 执行 callback
            executeCallbacks(transaction);
            // 执行 lifecycleState
            executeLifecycleState(transaction);

            mPendingActions.clear();

        } 
     

    /** Cycle through all states requested by callbacks and execute them at proper times. */
    public void executeCallbacks(ClientTransaction transaction) {
            ...
            //按顺序遍历执行ClientTransactionItem
            final int size = callbacks.size();
            for (int i = 0; i < size; ++i) {
                ......
                final ClientTransactionItem item = callbacks.get(i);
                item.execute(mTransactionHandler, token, mPendingActions);
                item.postExecute(mTransactionHandler, token, mPendingActions);

                if (postExecutionState != UNDEFINED && r != null) {
                    // Skip the very last transition and perform it by explicit state request instead.
                final boolean shouldExcludeLastTransition =
                            i == lastCallbackRequestingState && finalState == postExecutionState;
                    cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
                }
            }
        }
   
       /** Transition to the final state if requested by the transaction. */
       private void executeLifecycleState(ClientTransaction transaction) {
            final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
            // ...

            // 第二个参数为执行完时的生命周状态
            cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */, transaction);

            // Execute the final transition with proper parameters.
            lifecycleItem.execute(mTransactionHandler, token, mPendingActions);

            lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions);
        }
        

    private void cycleToPath(ActivityClientRecord r, int finish, boolean excludeLastState,
                ClientTransaction transaction) {
            // 获取当前 Activity 的生命周期状态,即开始时的状态    
            final int start = r.getLifecycleState();
            // 获取要执行的生命周期数组
            final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState);
            // 按顺序执行 Activity 的生命周期
            performLifecycleSequence(r, path, transaction);
        }

        
}

核心 execute 方法里主要执行了两件事:

  • 通过 executeCallbacks 方法执行所有被添加进来的 ClientTransactionItem
  • 通过 executeLifecycleState 方法将 Activity 的生命周期执行到指定的状态

生命周期超时处理

在一些异常日志中会发现 onStop 的执行日志总是在 onPause 的执行日志11 秒之后打印,同时伴随如下日志打印:

kotlin 复制代码
Slog.w(TAG, "Activity stop timeout for " + ActivityRecord.this);

这是因为 Android 虽然会优先执行新启动 activity 的生命周期流程,但是对于进入后台的 activity 也会确保其执行完整生命周期流程,在执行pause,stop以及destroy操作时都会执行超时检测,超时之后会强制执行对应过程。

同时为了不影响下一个应用回到前台的响应速度,pause的超时时间只有500毫秒,所以这个生命周期执行的任务需要尽量轻量化。另一方面由于在启动新的activity之后,老的activity的onPause生命周期函数一定会立即优先执行(onStop与onDestroy则可能延迟数秒之后才执行),所以对于一些时间有效性敏感的任务则需要放到此生命周期中执行更为保险。

scss 复制代码
base/services/core/java/com/android/server/wm/ActivityRecord.java

//从ActivityRecord的removeTimeouts方法可知activity生命周期过程中的大概超时机制
void removeTimeouts() {
    mTaskSupervisor.removeIdleTimeoutForActivity(this);
    removePauseTimeout();
    removeStopTimeout();
    removeDestroyTimeout();
    finishLaunchTickingLocked();
}
超时消息与时间定义

注释解释了对应时间定义的原因:

arduino 复制代码
base/services/core/java/com/android/server/wm/ActivityRecord.java

// How long we wait until giving up on the last activity to pause.  This
// is short because it directly impacts the responsiveness of starting the
// next activity.
private static final int PAUSE_TIMEOUT = 500;

// Ticks during which we check progress while waiting for an app to launch.
private static final int LAUNCH_TICK = 500;

// How long we wait for the activity to tell us it has stopped before
// giving up.  This is a good amount of time because we really need this
// from the application in order to get its saved state. Once the stop
// is complete we may start destroying client resources triggering
// crashes if the UI thread was hung. We put this timeout one second behind
// the ANR timeout so these situations will generate ANR instead of
// Surface lost or other errors.
private static final int STOP_TIMEOUT = 11 * 1000;

// How long we wait until giving up on an activity telling us it has
// finished destroying itself.
private static final int DESTROY_TIMEOUT = 10 * 1000;
php 复制代码
public class ActivityTaskSupervisor implements RecentTasks.Callbacks {

    /** How long we wait until giving up on the last activity telling us it is idle. */
    private static final int IDLE_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
    
    
    // How long we can hold the launch wake lock before giving up.
    private static final int LAUNCH_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
    

}


public class Build {

    public static final int HW_TIMEOUT_MULTIPLIER =
        SystemProperties.getInt("ro.hw_timeout_multiplier", 1);
    
}
pause超时

在TaskFragment的startPausing方法中发送超时消息,pause超时500毫秒。

typescript 复制代码
base/services/core/java/com/android/server/wm/TaskFragment.java

boolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming,
        String reason) {
        ......
            if (mPausingActivity != null) {
                if (pauseImmediately) {
                    // If the caller said they don't want to wait for the pause, then complete
                 // the pause now.
                completePause(false, resuming);
                    return false;
                
                } else {
                    prev.schedulePauseTimeout();
                    // All activities will be stopped when sleeping, don't need to wait for pause.
                    if (!uiSleeping) {
                        // Unset readiness since we now need to wait until this pause is complete.
                        mTransitionController.setReady(this, false /* ready */ );
                    }
                    return true;
                }            
            }        
        
        }

/**
* Schedule a pause timeout in case the app doesn't respond. We don't give it much time because
* this directly impacts the responsiveness seen by the user.
*/
void schedulePauseTimeout() {
    pauseTime = SystemClock.uptimeMillis();
    mAtmService.mH.postDelayed(mPauseTimeoutRunnable, PAUSE_TIMEOUT);
    ProtoLog.v(WM_DEBUG_STATES, "Waiting for pause to complete...");
}
java 复制代码
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

private final Runnable mPauseTimeoutRunnable = new Runnable() {
    @Override
    public void run() {
        // We don't at this point know if the activity is fullscreen,
        // so we need to be conservative and assume it isn't.
        Slog.w(TAG, "Activity pause timeout for " + ActivityRecord.this);
        synchronized (mAtmService.mGlobalLock) {
            if (!hasProcess()) {
                return;
            }
            mAtmService.logAppTooSlow(app, pauseTime, "pausing " + ActivityRecord.this);
            activityPaused(true);
        }
    }
};

activity 正常pause之后移除超时消息:

scss 复制代码
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

void activityPaused(boolean timeout) {
    ProtoLog.v(WM_DEBUG_STATES, "Activity paused: token=%s, timeout=%b", token,
            timeout);

    final TaskFragment taskFragment = getTaskFragment();
    if (taskFragment != null) {
        //移除超时消息
        removePauseTimeout();
        ......
    }     
}        

private void removePauseTimeout() {
    mAtmService.mH.removeCallbacks(mPauseTimeoutRunnable);
}
Launch 超时

Activity的launch过程涉及到LAUNCH_TIMEOUT_MSGIDLE_TIMEOUT_MSG两个超时消息。同时在非user版本固件中会通过LAUNCH_TICK 消息进行递归持续检测activity 启动过程消息队列任务执行缓慢的情况。

如果启动activity的过程中涉及到进程的创建与启动,那么进程创建过程会涉及到 PROC_START_TIMEOUT_MSG 的检测,默认为10秒,此处不展开分析创建进程的过程,所以省略。

由于这些超时消息并不直接关系到生命周期的执行,所以此处略去详细分析,读者可以在源码中搜索这些关键词查看对应逻辑与细节。

stop超时

在前台activity处于idle状态时,会通过processStoppingAndFinishingActivities方法开始执行后台activity的stop与destroy流程, stop超时11秒,一旦超时任务被执行,则会立即强制执行activity的stop流程,确保整个生命周期流程被完整执行。

java 复制代码
base/services/core/java/com/android/server/wm/ActivityRecord.java


private final Runnable mStopTimeoutRunnable = new Runnable() {
    @Override
    public void run() {
        synchronized (mAtmService.mGlobalLock) {
            Slog.w(TAG, "Activity stop timeout for " + ActivityRecord.this);
            if (isInHistory()) {
                activityStopped(
                        null /*icicle*/, null /*persistentState*/, null /*description*/);
            }
        }
    }
};


void stopIfPossible() {
    ......
    //打印event日志
    EventLogTags.writeWmStopActivity(
            mUserId, System.identityHashCode(this), shortComponentName);
    //执行stop事务
    mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
            StopActivityItem.obtain(configChangeFlags));
    //发送超时消息,处理超时
    mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
    ......
}

移除此mStopTimeoutRunnable的时机较多,比如正常stop或者在历史任务列表被remove等情况都会取消stop超时的检测。

destroy超时

destroy超时10秒。

scss 复制代码
base/services/core/java/com/android/server/wm/ActivityRecord.java


private final Runnable mDestroyTimeoutRunnable = new Runnable() {
    @Override
    public void run() {
        synchronized (mAtmService.mGlobalLock) {
            Slog.w(TAG, "Activity destroy timeout for " + ActivityRecord.this);
            destroyed("destroyTimeout");
        }
    }
};

boolean destroyImmediately(String reason) {

    //打印destroy日志
    EventLogTags.writeWmDestroyActivity(mUserId, System.identityHashCode(this),
            task.mTaskId, shortComponentName, reason);

    if (hasProcess()) {
        //执行destroy事务
        mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
            DestroyActivityItem.obtain(finishing, configChangeFlags));
    
        if (finishing && !skipDestroy) {
            ProtoLog.v(WM_DEBUG_STATES, "Moving to DESTROYING: %s (destroy requested)", this);
            setState(DESTROYING,
                    "destroyActivityLocked. finishing and not skipping destroy");
            //发送超时消息,处理超时
            mAtmService.mH.postDelayed(mDestroyTimeoutRunnable, DESTROY_TIMEOUT);
        }        

    }
}

activity成功调用onDestroy之后便会移除DestroyTimeout消息。

csharp 复制代码
base/services/core/java/com/android/server/wm/ActivityRecord.java

/**
* This method is to only be called from the client via binder when the activity is destroyed
* AND finished.
*/
void destroyed(String reason) {
    removeDestroyTimeout();

    ProtoLog.d(WM_DEBUG_CONTAINERS, "activityDestroyedLocked: r=%s", this);

    if (!isState(DESTROYING, DESTROYED)) {
        throw new IllegalStateException(
                "Reported destroyed for activity that is not destroying: r=" + this);
    }

    if (isInRootTaskLocked()) {
        cleanUp(true /* cleanServices */ , false /* setState */ );
        removeFromHistory(reason);
    }

    mRootWindowContainer.resumeFocusedTasksTopActivities();
}

private void removeDestroyTimeout() {
    mAtmService.mH.removeCallbacks(mDestroyTimeoutRunnable);
}

OnSaveInstanceState与OnRestoreInstanceState

  • OnSaveInstanceState : 在onStop生命周期中,具体是在callActivityOnStop方法中会调用Activity的OnSaveInstanceState方法进行相关参数的保存。(在Android P之前是onSaveInstanceState方法是在onStop之前回调的,从Android P开始onSaveInstanceState方法是在onStop之后回调的。)

  • OnRestoreInstanceState : 在onStart生命周期中,具体是在handleStartActivity方法中会调用Activity的OnRestoreInstanceState方法进行之前保存的参数的恢复。

相关推荐
大白要努力!4 分钟前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟1 小时前
Android音频采集
android·音视频
小白也想学C2 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程2 小时前
初级数据结构——树
android·java·数据结构
闲暇部落4 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX6 小时前
Android 分区相关介绍
android
大白要努力!7 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee7 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood8 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-11 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记