面试题:android中A Activity 打开B Activity,为什么A Activity的onStop()方法最后被调用
Posted 一叶飘舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题:android中A Activity 打开B Activity,为什么A Activity的onStop()方法最后被调用相关的知识,希望对你有一定的参考价值。
如下是一段典型的Activity间切换的日志,从A Activity切换到B Activity:
10-17 20:54:42.247: I/com.example.servicetest.AActivity(5817): onCreate() 1166919192 taskID=66
10-17 20:54:42.263: I/com.example.servicetest.AActivity(5817): onStart() 1166919192 taskID=66
10-17 20:54:42.263: I/com.example.servicetest.AActivity(5817): onResume() 1166919192 taskID=66
10-17 20:54:46.997: I/com.example.servicetest.AActivity(5817): onPause() 1166919192 taskID=66
10-17 20:54:47.021: I/com.example.servicetest.BActivity(5817): onCreate() 1166971824 taskID=66
10-17 20:54:47.028: I/com.example.servicetest.BActivity(5817): onStart() 1166971824 taskID=66
10-17 20:54:47.028: I/com.example.servicetest.BActivity(5817): onResume() 1166971824 taskID=66
10-17 20:54:47.099: I/com.example.servicetest.AActivity(5817): onStop() 1166919192 taskID=66
当触发从AActivity切换到BActivity时的日志如下:
10-17 20:54:46.997: I/com.example.servicetest. AActivity(5817): onPause() 1166919192 taskID=66
10-17 20:54:47.021: I/com.example.servicetest.BActivity(5817): onCreate() 1166971824 taskID=66
10-17 20:54:47.028: I/com.example.servicetest.BActivity(5817): onStart() 1166971824 taskID=66
10-17 20:54:47.028: I/com.example.servicetest.BActivity(5817): onResume() 1166971824 taskID=66
10-17 20:54:47.099: I/com.example.servicetest. AActivity(5817): onStop() 1166919192 taskID=66
先AActivity的onPause()被调用,然后是BActivity的初始化流程(onCreate() --> onStart() --> onResume()),再然后是AActivity的onStop()被调用。
问题来了,为什么不是先AActivity的onPause()、onStop()被调用,然后再BActivity的初始化流程(onCreate() --> onStart() --> onResume())?
或者又为什么不是先BActivity的初始化流程(onCreate() --> onStart() --> onResume()),再AActivity的onPause()、onStop()被调用?
如果所有的初始化都在onCreate()中实现,会有什么问题?
首先,Activity的onCreate()被调用时,Activity还不可见,如果要做一些动画,既然视图还不存在,在onCreate中来启动动画,明显有问题;
其次,AActivity 切换到 BActivity,再切换到 AActivity(我们假定是AActivity的同一个实例),由于实例已经存在,所以onCreate不会再被调用,那AActivity从后台切换至前台时,有可能需要一些初始化,那就没法再被调用到了,也有问题;
如果所有的初始化都在onStart()中实现,会有什么问题?
首先,onCreate()注释中,是明确建议 setContentView()、findViewById() 要在 onCreate() 中被调用,但我实测了一下,在onStart()中调用 setContentView()、findViewById() 功能也是正常的;
其次,onStart() 被调用时,Activity可能是可见了,但还不是可交互的,onResume()的注释中都明确地说了这不是Activity对用户是可见的最好的指示器,onStart() 在这之前被调用,那有一些特殊的初始化相关的逻辑在这里被调用也会有问题。
如果把所有的去初始化都在onStop()中实现,会有什么问题?
1、 在 onResume() 的注释中,建议是在onResume()中打开独占设备(比如相机),与onResume()对应的是onPause(),所以所有的去初始化操作放在onStop()中执行,可能会引出新的问题;
2、onStop() 的注释中明确地写了,在内存不足而导致系统无法保留此进程的情况下,onStop() 可能都不会被执行。
Activity间跳转时,为什么是先AActivity的onPause()被调用,然后是BActivity的初始化流程(onCreate() --> onStart() --> onResume()),再然后是AActivity的onStop()被调用?
1、在 onResume() 的注释中,建议是在onResume()中打开独占设备(比如相机),与onResume()对应的是onPause(),关闭相机的操作也应该在此方法中被调用;否则,考虑一下如下场景:
如果AActivity打开了相机,我们点击某按钮要跳转到BActivity中,BActivity也想打开相机;假设AActivity的onPause() 在 BActivity启动后再被调用,那BActivity根本就无法再正常启动相机。
2、onPause() 的注释中,也明确地说了,在这个方法中执行停止动画等比较耗CPU的操作,如果不先执行这些操作,就先启动新应用,然后再来执行此操作,确实是不合逻辑;
当用户触发某事件切换到新的Activity,用户肯定是想尽快进入新的视图进行操作,上面已经说了,在onResume()一般会打开独占设备,开启动画等,当需要从AActivity切换到BActivity时,先执行AActivity中的与onResume()相对应的onPause()操作,比如关闭独占设备,关闭动画,或其它耗费cpu的操作;以防止BActivity也需要使用这些资源,关闭耗CPU的操作,也有利于BActivity运行的流畅。
底层执行AActivity的onPause()时,有一定的时间限制的,当ActivityManagerService通知应用进程暂停指定的Activity时,如果对应的onPause()在500ms内还没有执行完,ActivityManagerService就会强制关闭这个Activity。如下就是对应的onPause()执行超时常量定义:
// 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.
static final int PAUSE_TIMEOUT = 500; // 定义在ActivityStack.java中
AActivity中比较消耗资源的部分关闭后,再切换到BActivity中执行BActivity的初始化,显示BActivity中的View。
当BActivity已经执行显示出来了,用户可以交互,后台再去执行AActivity的onStop()操作,即使这里面有些比较耗时的操作,也没有关系,这是在后台执行所以也不影响用户的体验。
开发中可能会遇到个别问题,如发现onStop的调用时机受下一个页面的影响,本页面的onStop是在下一个页面onResume,onWindowFocusChanged等之后才会调用,如果在onResume和onWindowFocusChanged中进行了耗时的操作,会导致前一个页面的onStop不能被回调。这个细节会被大部分人忽略,但有时会触发意想不到的bug。
上面说了这么多,其实还没有解释为什么AActivity的onStop()方法会在BActivity的OnResume方法之后执行,下面我们需要借助源码来找下原因。
Activity启动源码分析-- onStop,onDestroy探寻
1.目的
这个是这个启动的最后一篇,讲Activity生命周期里面回调的最后几个方法。前面已经讲到了onCreate,onStart,onResume,onPause。还差onStop,onDestroy,onRestart。看一下Activity启动过程中,他们究竟藏在哪里。
这篇结束后,Activity的几个主要生命周期就都介绍了。当然,读者也可以继续拓展其他方法。
onStop还能算在这个启动过程中,onDestroy,onRestart其实就没多大关系了。下面会分开讲。
2.OnStop
还是先放图。
接着上篇,
ActivityThread
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason)
...
//onResume执行完后,会在空闲时执行Idler
Looper.myQueue().addIdleHandler(new Idler());
IdleHandler是MessageQueue类里的一个借口,messages为空的时候就会执行IdleHandler。看下Idler是怎么实现queueIdle()的。
ActivityThread
private class Idler implements MessageQueue.IdleHandler
@Override
public final boolean queueIdle()
...
mNewActivities = null;
//这就相当眼熟了,我们直接往AMS去找
IActivityManager am = ActivityManager.getService();
ActivityClientRecord prev;
do
if (localLOGV) Slog.v(
TAG, "Reporting idle of " + a +
" finished=" +
(a.activity != null && a.activity.mFinished));
if (a.activity != null && !a.activity.mFinished)
try
am.activityIdle(a.token, a.createdConfig, stopProfiling);
a.createdConfig = null;
catch (RemoteException ex)
throw ex.rethrowFromSystemServer();
prev = a;
a = a.nextIdle;
prev.nextIdle = null;
while (a != null);
...
return false;
ActivityManagerService
@Override
public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling)
final long origId = Binder.clearCallingIdentity();
synchronized (this)
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null)
ActivityRecord r =
mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */,
false /* processPausingActivities */, config);
if (stopProfiling)
if ((mProfileProc == r.app) && mProfilerInfo != null)
clearProfilerLocked();
Binder.restoreCallingIdentity(origId);
ActivityStackSupervisor
@GuardedBy("mService")
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
boolean processPausingActivities, Configuration config)
...
//找到当前不在界面上为Pause状态的Activity
// Atomically retrieve all of the other things to do.
final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r,
true /* remove */, processPausingActivities);
NS = stops != null ? stops.size() : 0;
if ((NF = mFinishingActivities.size()) > 0)
finishes = new ArrayList<>(mFinishingActivities);
mFinishingActivities.clear();
if (mStartingUsers.size() > 0)
startingUsers = new ArrayList<>(mStartingUsers);
mStartingUsers.clear();
// Stop any activities that are scheduled to do so but have been
// waiting for the next one to start.
for (int i = 0; i < NS; i++)
r = stops.get(i);
final ActivityStack stack = r.getStack();
if (stack != null)
//Pause的Activitiy正在finish状态,这里自然不是
if (r.finishing)
stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false,
"activityIdleInternalLocked");
else
//走你
stack.stopActivityLocked(r);
//onDestory会在这调用正在fininshing的Activity,是mFinishingActivities维护的。但是在前面没有看到往mFinishingActivities添加成员的地方
// Finish any activities that are scheduled to do so but have been
// waiting for the next one to start.
for (int i = 0; i < NF; i++)
r = finishes.get(i);
final ActivityStack stack = r.getStack();
if (stack != null)
activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
...
final ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity,
boolean remove, boolean processPausingActivities)
ArrayList<ActivityRecord> stops = null;
final boolean nowVisible = allResumedActivitiesVisible();
for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx)
...
if (remove)
final ActivityStack stack = s.getStack();
final boolean shouldSleepOrShutDown = stack != null
? stack.shouldSleepOrShutDownActivities()
: mService.isSleepingOrShuttingDownLocked();
//非显示状态
if (!waitingVisible || shouldSleepOrShutDown)
//处于Pause
if (!processPausingActivities && s.isState(PAUSING))
// Defer processing pausing activities in this iteration and reschedule
// a delayed idle to reprocess it again
removeTimeoutsForActivityLocked(idleActivity);
scheduleIdleTimeoutLocked(idleActivity);
continue;
if (DEBUG_STATES) Slog.v(TAG, "Ready to stop: " + s);
if (stops == null)
stops = new ArrayList<>();
//添加返回
stops.add(s);
mStoppingActivities.remove(activityNdx);
return stops;
ActivityStack
private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf)
final ActivityStack stack =
mStackSupervisor.getNextFocusableStackLocked(this, !allowFocusSelf);
final String myReason = reason + " adjustFocusToNextFocusableStack";
if (stack == null)
return false;
final ActivityRecord top = stack.topRunningActivityLocked();
if (stack.isActivityTypeHome() && (top == null || !top.visible))
// If we will be focusing on the home stack next and its current top activity isn't
// visible, then use the move the home stack task to top to make the activity visible.
return mStackSupervisor.moveHomeStackTaskToTop(reason);
stack.moveToFront(myReason);
return true;
final void stopActivityLocked(ActivityRecord r)
...
//再眼熟不过,让ClientHandler去执行StopActivityItem了。
mService.getLifecycleManager().scheduleTransaction(r.app.thread, r.appToken,
StopActivityItem.obtain(r.visible, r.configChangeFlags));
...
这里从LifecycleManager向App传递的过程前面已描述两次,这里不再赘述。
StopActivityItem
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions)
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions,
true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
ActivityThread
@Override
public void handleStopActivity(IBinder token, boolean show, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason)
...
performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
reason);
...
private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,
boolean saveState, boolean finalStateRequest, String reason)
...
if (!keepShown)
callActivityOnStop(r, saveState, reason);
...
private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason)
// Before P onSaveInstanceState was called before onStop, starting with P it's
// called after. Before Honeycomb state was always saved before onPause.
final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null
&& !r.isPreHoneycomb();
final boolean isPreP = r.isPreP();
if (shouldSaveState && isPreP)
callActivityOnSaveInstanceState(r);
try
//这里可以看到Stop方法的调用了
r.activity.performStop(false /*preserveWindow*/, reason);
catch (SuperNotCalledException e)
throw e;
catch (Exception e)
if (!mInstrumentation.onException(r.activity, e))
throw new RuntimeException(
"Unable to stop activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
r.setState(ON_STOP);
if (shouldSaveState && !isPreP)
callActivityOnSaveInstanceState(r);
可以从上面看到,onStop实在MessageQueue空闲才会调用。不像onPuase,onResume一定会被调用。
3.OnDestroy
从之前见到的方法里,唯一好像与onDestroy相关的。是ActivityStackSupervisor.mFinishingActivities。但是启动流程里没有见到正常赋值的地方啊,所以全局搜索一下,猜测一下是否有DestroyActivityItem这个类,果然是有的。再往回推调用链,可以看到正常调用有找到两处,这里单列一下System_server端的调用关系:
对应App手动调用finish方法:
->ActivityManagerService:finishActivity
->ActivityStack:requestFinishActivityLocked
->ActivityStack:finishActivityLocked
->ActivityStack:finishCurrentActivityLocked
->ActivityStack:destroyActivityLocked
还有一个是
调用处可以参考第三篇 Activity启动源码分析(3)-- 新app进程创建过程,在ActivityThread.attach有一个GcWatcher,内存占用大于3/4就会触发
->ActivityManagerService:releaseSomeActivities
->ActivityStackSupervisor:releaseSomeActivitiesLocked
->ActivityStack:releaseSomeActivitiesLocked
->ActivityStack:destroyActivityLocked
这篇的话,有看过Pause过程的话,是相当容易理解的。因为这里调用方式都一致。不会有上一篇app进程创建过程设计过程那么复杂。当然,我的文章只能描述其中一部分,还有大部分需要读者再从相关文章拓展。
其他地方看着都是异常时调用,还有系统强杀。所以onDestroy和onCreate一定会成对调用吗?并不是的,onDestroy并不一定会被调用。正常的流程下,只有在调用了finish后3/4内存占用后触发GC才调用了。所以反注册一定需要很是小心。
推荐的一种写法是
@Override
protected void onStop()
super.onStop();
if(isFinishing())
//unRegister
3.OnRestart
在上一篇,我们有把cycleToPath具体分析。其实在这个里面
TransactionExecutorHelper
public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState)
...
//如果是从大的生命周期往小的生命周期变化,如onStop到onResume
else // finish < start, can't just cycle down
if (start == ON_PAUSE && finish == ON_RESUME)
// Special case when we can just directly go to resumed state.
mLifecycleSequence.add(ON_RESUME);
else if (start <= ON_STOP && finish >= ON_START)
// Restart and go to required state.
//这里看到,最大也就是到onStop,并没有调用onDestroy方法
// Go to stopped state first.
for (int i = start + 1; i <= ON_STOP; i++)
mLifecycleSequence.add(i);
//然后调用了Restart
// Restart
mLifecycleSequence.add(ON_RESTART);
// Go to required state
for (int i = ON_START; i <= finish; i++)
mLifecycleSequence.add(i);
else
// Relaunch and go to required state
// Go to destroyed state first.
for (int i = start + 1; i <= ON_DESTROY; i++)
mLifecycleSequence.add(i);
// Go to required state
for (int i = ON_CREATE; i <= finish; i++)
mLifecycleSequence.add(i);
//移除了onResume
// Remove last transition in case we want to perform it with some specific params.
if (excludeLastState && mLifecycleSequence.size() != 0)
mLifecycleSequence.remove(mLifecycleSequence.size() - 1);
return mLifecycleSequence;
一言蔽之,就是onDestroy没有调用的时候,Activity的状态由大变小,就会走onRestart。
上面提到onStop实在MessageQueue空闲才会调用,这个MessageQueue就是onResume的时候添加的Idler。
Looper.myQueue().addIdleHandler(new Idler());
IdleHandler 详解
IdleHandler 是 MessageQueue 内定义的一个接口,一般可用于做性能优化。当消息队列内没有需要立即执行的 message 时,会主动触发 IdleHandler 的 queueIdle 方法。返回值为 false,即只会执行一次;返回值为 true,即每次当消息队列内没有需要立即执行的消息时,都会触发该方法。
public final class MessageQueue
public static interface IdleHandler
boolean queueIdle();
使用方式
通过获取 looper 对应的 MessageQueue 队列注册监听。
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler()
@Override
public boolean queueIdle()
// doSomething()
return false;
);
源码解析
IdleHandler 的执行源码很短。
Message next()
// 隐藏无关代码...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (; ; )
// 隐藏无关代码...
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when))
pendingIdleHandlerCount = mIdleHandlers.size();
if (pendingIdleHandlerCount <= 0)
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
if (mPendingIdleHandlers == null)
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++)
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try
keep = idler.queueIdle();
catch (Throwable t)
Log.wtf(TAG, "IdleHandler threw exception", t);
if (!keep)
synchronized (this)
mIdleHandlers.remove(idler);
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
在 MessageQueue 里 next 方法的 for 死循环内,获取 mIdleHandlers 的数量 pendingIdleHandlerCount;
通过 mMessages == null || now < mMessages.when 判断当前消息队列为空或者目前没有需要执行的消息时,给 pendingIdleHandlerCount 赋值;
当数量大于 0,遍历取出数组内的 IdleHandler,执行 queueIdle() ;
返回值为 false 时,主动移除监听 mIdleHandlers.remove(idler);
使用场景
如果启动的 Activity、Fragment、Dialog 内含有大量数据和视图的加载,导致首次打开时动画切换卡顿或者一瞬间白屏,可将部分加载逻辑放到 queueIdle() 内处理。例如引导图的加载和弹窗提示等;
系统源码中 ActivityThread 的 GcIdler,在某些场景等待消息队列暂时空闲时会尝试执行 GC 操作;
系统源码中 ActivityThread 的 Idler,在 handleResumeActivity() 方法内会注册 Idler(),等待 handleResumeActivity 后视图绘制完成,消息队列暂时空闲时再调用 AMS 的 activityIdle 方法,检查页面的生命周期状态,触发 activity 的 stop 生命周期等。
这也是为什么我们 BActivity 跳转 CActivity 时,BActivity 生命周期的 onStop() 会在 CActivity 的 onResume() 后。
一些第三方框架 Glide 和 LeakCanary 等也使用到 IdleHandler,感兴趣的朋友可以看看源码;
以上是关于面试题:android中A Activity 打开B Activity,为什么A Activity的onStop()方法最后被调用的主要内容,如果未能解决你的问题,请参考以下文章