当通过“不保留活动”杀死活动时,不会触发 ActivityLifecycleCallbacks
Posted
技术标签:
【中文标题】当通过“不保留活动”杀死活动时,不会触发 ActivityLifecycleCallbacks【英文标题】:ActivityLifecycleCallbacks are not triggered when activity is killed through "Don't keep activities" 【发布时间】:2017-02-15 05:19:16 【问题描述】:在我的 android 应用中,我有两个活动:
DemoActivity
带有一个按钮以启动 SearchActivity
和 Intent
SearchActivity
按钮是一个自定义的ViewGroup:
SearchButton
一旦SearchButton
生效,它就会注册生命周期事件(对应的SearchActivity
):
public class SearchButton extends CardView implements
Application.ActivityLifecycleCallbacks
@Override
protected void onAttachedToWindow()
super.onAttachedToWindow();
Context applicationContext = getContext().getApplicationContext();
if (applicationContext instanceof Application)
((Application) applicationContext)
.registerActivityLifecycleCallbacks(this);
// ...
事件的消费方式如下:
// ...
@Override
public void onActivityStarted(Activity activity)
if (activity instanceof SearchActivity)
SearchActivity searchActivity = (SearchActivity) activity;
searchActivity.addSomeListener(someListener);
@Override
public void onActivityStopped(Activity activity)
if (activity instanceof SearchActivity)
SearchActivity searchActivity = (SearchActivity) activity;
searchActivity.removeSomeListener(someListener);
SearchActivity
启动后,我将应用程序置于后台,然后将其重新置于前台。可以看到如下调用栈:
1. SearchButton.onActivityStarted // triggered by DemoActivity
2. DemoActivity.onStart
3. SearchButton.onActivityStarted // triggered by SearchActivity
4. SearchActivity.addSomeListener
5. SearchActivity.onStart
如您所见,添加了侦听器。这很好用。
问题
一旦我在开发者选项中启用Don't keep activities
,当我再次进入应用程序前台时,调用堆栈将如下所示:
1. DemoActivity.onCreate
2. SearchButton.init // Constructor
3. DemoActivity.onStart
4. SearchActivity.onStart
5. SearchButton.onAttachedToWindow
6. DemoApplication.registerActivityLifecycleCallbacks
这里的监听器没有添加。由SearchActivity.onStart
触发的所需onActivityStarted
回调缺失。
【问题讨论】:
显然因为DemoActivity
中的SearchButton
从未附加到窗口,因为(如果我确实了解发生了什么)DemoActivity
位于“下方”SearchActivity
(就活动堆栈而言) ……还有你为什么要为此烦恼?在这种情况下,DemoActivity
无论如何都处于停止状态
永远不会错。在这种情况下,对onAttachToWindow
的调用只是在onStart
之后发生,这会导致问题。
老实说,您无法理解您发布的代码会发生什么。甚至调用堆栈对我来说也不够清楚(什么是 DemoApplication?)。您应该发布更多代码和您执行的步骤以重现此行为:当您启用“不保留活动”标志时,您将应用程序从最近的应用程序列表中移到前台,还是单击应用程序图标?跨度>
【参考方案1】:
简答
只有当活动在后台一段时间后被带到前台时,您才会从视图中看到 onStart
调用。目前无法从您的 Activity 的视图中看到较早的 Activity 事件,因为视图层次结构仍在创建中,并且视图尚未附加到窗口。
当一个活动从头开始初始化时,视图层次结构直到onResume
之后才完全附加。这意味着一旦你的视图的onAttachedToWindow
被调用,onStart
就已经被执行了。如果您退出问题中提到的活动,您应该仍会看到 onPause
等的事件。
通常,如果您通过按下主页按钮将 Activity 置于后台,则该 Activity 会停止但不会被销毁。如果有足够的系统资源这样做,它会与视图层次结构一起留在内存中。当 Activity 恢复到前台时,它不会从头开始创建它,而是调用 onStart
并从它停止的地方恢复,而不重新创建视图层次结构。
“不保留活动”选项可确保每个活动在离开前台后立即被销毁,确保您的视图的 onAttachedToWindow
始终在 onResume
之后调用,因为需要重新创建视图层次结构每次。
你可以做什么
如果不共享更多代码,我们并不清楚为什么需要在视图中设置监听器。看来无论如何都需要监听activity的生命周期方法。
如果监听器只与 Activity 的生命周期相关联,您也许可以将其完全从视图中提取出来并放入 Activity。
如果它同时绑定到视图和活动的生命周期,您可以尝试在视图的构造函数中注册活动生命周期回调,因为此时上下文已经可用。
或者,您可以选择 Google 地图目前拥有的解决方案,例如在地图视图中。它要求活动将所有生命周期方法代理到视图。如果您的视图与活动的生命周期紧密结合,这可能很有用。 You can see the documentation here.
第四个选项是使用片段而不是视图,因为它有自己的一组生命周期方法。就我个人而言,我对片段不太满意,因为它们的生命周期可能更加复杂。
更长的答案
为了解释为什么会发生这种情况,我们需要深入研究 Android 的源代码。我在这里解释的内容是特定于这个实现的,并且可能会因制造商的变化而在 SDK 版本之间甚至在 Android 设备之间有所不同。你不应该在你的代码中依赖这些细节。我将使用 Android Studio 附带的 SDK 23 源代码和带有 build MTC19T 的 Nexus 6P。
最容易开始调查的地方是onAttachedToWindow
方法。它实际上是什么时候调用的? Its documentation says 它是在创建视图的表面以进行绘图后调用的,但我们对此并不满意。
为了找出答案,我们为视图设置了一个断点,重新启动应用程序以重新创建 Activity,并检查 Android Studio 中的前几帧:
"main@4092" prio=5 runnable
java.lang.Thread.State: RUNNABLE
at com.lnikkila.callbacktest.TestView.onAttachedToWindow(TestView.java:18)
at android.view.View.dispatchAttachedToWindow(View.java:14520)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2843)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1372)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1115)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6023)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5422)
...
我们可以看到,第一帧来自视图的内部逻辑,来自父 ViewGroup,来自称为 ViewRootImpl 的东西,然后来自 Choreographer 和 Handler 的一些回调。
我们不确定是什么创建了这些回调,但最接近的回调实现名为 ViewRootImpl$TraversalRunnable,因此我们将对其进行检查:
final class TraversalRunnable implements Runnable
@Override
public void run()
doTraversal();
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
定义好了,下面就是这个方法中给Choreographer的回调实例:
void scheduleTraversals()
if (!mTraversalScheduled)
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch)
scheduleConsumeBatchedInput();
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
Choreographer 是在 Android 上的每个 UI 线程上运行的东西。它用于将事件与显示器的帧速率同步。使用它的一个原因是避免浪费处理能力,因为绘图速度比显示器显示的速度快。
由于 Choreographer 使用线程的消息队列,我们无法在之前的帧中看到此调用,因为直到 Looper 处理了消息后才进行调用。我们可以给这个方法设置一个断点,看看这个调用是从哪里来的:
"main@4091" prio=5 runnable
java.lang.Thread.State: RUNNABLE
at android.view.ViewRootImpl.scheduleTraversals(ViewRootImpl.java:1084)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:913)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:526)
- locked <0x100a> (a android.view.ViewRootImpl)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:310)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3169)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2481)
at android.app.ActivityThread.-wrap11(ActivityThread.java:-1)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5422)
...
如果我们查看 ActivityThread 的 handleLaunchActivity
,就会看到对 handleResumeActivity
的调用。在此之前是对performLaunchActivity
的调用,在该方法中是对Instrumentation#callActivityOnCreate
、Activity#performStart
等的调用。
所以我们有证据证明视图直到 onResume
之后才附加。
【讨论】:
以上是关于当通过“不保留活动”杀死活动时,不会触发 ActivityLifecycleCallbacks的主要内容,如果未能解决你的问题,请参考以下文章
当应用程序不在前台时,特定活动不会从通知单击打开(应用程序是最近的,没有被杀死!)