当通过“不保留活动”杀死活动时,不会触发 ActivityLifecycleCallbacks

Posted

技术标签:

【中文标题】当通过“不保留活动”杀死活动时,不会触发 ActivityLifecycleCallbacks【英文标题】:ActivityLifecycleCallbacks are not triggered when activity is killed through "Don't keep activities" 【发布时间】:2017-02-15 05:19:16 【问题描述】:

在我的 android 应用中,我有两个活动:

DemoActivity 带有一个按钮以启动 SearchActivityIntent 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#callActivityOnCreateActivity#performStart 等的调用。

所以我们有证据证明视图直到 onResume 之后才附加。

【讨论】:

以上是关于当通过“不保留活动”杀死活动时,不会触发 ActivityLifecycleCallbacks的主要内容,如果未能解决你的问题,请参考以下文章

当应用程序被杀死/关闭时,世博会通知不会触发方法

当应用程序不在前台时,特定活动不会从通知单击打开(应用程序是最近的,没有被杀死!)

Android 当打开“开发人员模式”中的“不保留活动”后,程序应当怎么保持正常执行

小米手机设置里“开发人员选项里不保留活动”是啥意思?

当活动崩溃时,进程保持活动状态,我无法杀死它

请问安卓系统,开发人员选项里的“不保留活动”以及“后台进程限制”有啥区别?“后台进程限制”里的“