通过"不要保持活动"杀死活动时不会触发ActivityLifecycleCallbacks

JJD*_*JJD 10 android callback activity-lifecycle android-activity

在我的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);
        }
    }

// ...
Run Code Online (Sandbox Code Playgroud)

事件消耗如下:

// ...

    @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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

一旦SearchActivity已经启动,我把应用程序到后台并把它放回前台.可以看到以下调用堆栈:

1. SearchButton.onActivityStarted // triggered by DemoActivity
2. DemoActivity.onStart
3. SearchButton.onActivityStarted // triggered by SearchActivity
4. SearchActivity.addSomeListener
5. SearchActivity.onStart
Run Code Online (Sandbox Code Playgroud)

如您所见,添加了侦听器.这很好用.


问题

一旦我Don't keep activities在开发人员选项中启用,当我再次获得应用前景时,调用堆栈就像这样:

1. DemoActivity.onCreate
2. SearchButton.init // Constructor
3. DemoActivity.onStart
4. SearchActivity.onStart
5. SearchButton.onAttachedToWindow
6. DemoApplication.registerActivityLifecycleCallbacks
Run Code Online (Sandbox Code Playgroud)

这里没有添加监听器.期望onActivityStarted通过触发回调SearchActivity.onStart失踪.

Leo*_*ilä 9

简短的回答

onStart只有当活动在后台运行一段时间后才能进入前台,您才会看到来自视图的调用.目前,由于仍在创建视图层次结构并且视图尚未附加到窗口,因此无法从活动的视图中查看先前的活动事件.

从头开始初始化活动时,视图层次结构直到之后才会完全附加onResume.这意味着一旦onAttachedToWindow调用了视图,onStart就已经执行了.如果您退出问题中提到的活动,您仍应该看到事件onPause等等.

通常,如果您通过按主页按钮将活动放到后台,则活动会停止但不会被销毁.如果有足够的系统资源,它将以其视图层次结构保留在内存中.当活动恢复到前台时,它不是从头开始创建它,而是onStart从它停止的地方调用和恢复,而不重新创建视图层次结构.

"不要保留活动"选项可确保每个活动在离开前台后立即销毁,确保onAttachedToWindow始终调用onResume视图,因为每次都需要重新创建视图层次结构.

你可以做什么呢

如果不共享更多代码,则无法立即清楚为什么需要在视图中设置侦听器.在任何情况下,您似乎都需要监听活动的生命周期方法.

如果侦听器仅与活动的生命周期相关联,则可以将其从视图中完全提取出来并放入活动中.

如果它与视图和活动的生命周期相关联,则可以尝试在视图的构造函数中注册活动生命周期回调,因为此时上下文已经可用.

或者,您可以选择Google地图当前具有的解决方案,例如MapView.它要求活动将所有生命周期方法代理到视图.如果您的视图与活动的生命周期紧密结合,这可能很有用.您可以在此处查看文档.

第四种选择是使用片段而不是视图,因为它有自己的一组生命周期方法.就个人而言,我对片段感觉不太舒服,因为它们的生命周期可能更复杂.

更长的答案

为了解释为什么会这样,我们需要深入研究Android的源代码.我在这里解释的内容特定于此实现,并且由于制造商的更改,SDK版本之间甚至Android设备之间可能会有所不同.您不应该依赖代码中的这些细节.我将使用Android Studio附带的SDK 23源代码和构建MTC19T的Nexus 6P.

最容易开始调查的地方是onAttachedToWindow方法.什么时候实际调用?它的文档说它是在为绘图创建视图的表面之后调用的,但我们对此并不满意.

为了找到答案,我们为视图设置了一个断点,重新启动应用程序以重新创建活动,并调查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)
      ...
Run Code Online (Sandbox Code Playgroud)

我们可以看到第一帧来自视图的内部逻辑,来自父ViewGroup,来自ViewRootImpl,然后来自Choreographer和Handler的一些回调.

我们不确定是什么创建了这些回调,但最接近的回调实现名为ViewRootImpl $ TraversalRunnable,所以我们将检查出来:

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
Run Code Online (Sandbox Code Playgroud)

有定义,下面是在此方法中为Choreographer提供的回调实例:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
Run Code Online (Sandbox Code Playgroud)

编排器是在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)
      ...
Run Code Online (Sandbox Code Playgroud)

如果我们查看ActivityThread handleLaunchActivity,那就是调用handleResumeActivity.在此之前是一个调用performLaunchActivity,并在该方法中调用Instrumentation#callActivityOnCreate,Activity#performStart等等.

因此,我们有证据证明这些观点直到之后才会附加onResume.