在我的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
此处听众未添加。 onActivityStarted
触发的所需SearchActivity.onStart
回调缺失。
答案 0 :(得分:8)
只有当活动在后台播放一段时间后才会显示在前景中时,您才会看到onStart
来自视图的调用。目前,由于仍在创建视图层次结构且视图尚未附加到窗口,因此无法从活动视图中查看先前的活动事件。
从头开始初始化活动时,视图层次结构在onResume
之后才会完全附加。这意味着,一旦调用了视图onAttachedToWindow
,onStart
就已经执行了。如果您退出问题中提到的活动,您仍应看到onPause
的事件,依此类推。
通常,如果您通过按主页按钮将活动放到后台,则活动会停止但不会被销毁。如果有足够的系统资源,它将以其视图层次结构保留在内存中。当活动恢复到前台时,它不是从头开始创建它,而是调用onStart
并从它停止的地方继续,而不重新创建视图层次结构。
“不要保留活动”选项可确保每个活动在离开前景后立即销毁,确保在onAttachedToWindow
之后始终调用视图的onResume
,因为视图层次结构需要每次都重新创建。
如果不共享更多代码,则无法立即清楚为什么需要在视图中设置侦听器。在任何情况下,您似乎都需要听取活动的生命周期方法。
如果监听器仅与活动的生命周期相关联,您可以将其从视图中完全提取出来并进入活动。
如果它与视图和活动的生命周期相关联,您可以尝试在视图的构造函数中注册活动生命周期回调,因为此时上下文已经可用。
或者您可以选择Google地图目前拥有的解决方案,例如在MapView中。它要求活动将所有生命周期方法代理到视图。如果您的视图与活动的生命周期紧密结合,这可能很有用。 You can see the documentation here.
第四个选项是使用片段而不是视图,因为它有自己的一组生命周期方法。就个人而言,我对片段感觉不太舒服,因为它们的生命周期可能更复杂。
为了解释为什么会这样,我们需要深入研究Android的源代码。我在这里解释的内容特定于此实现,并且由于制造商的更改,SDK版本之间甚至Android设备之间可能会有所不同。您不应该依赖代码中的这些细节。我将使用Android Studio附带的SDK 23源代码和构建MTC19T的Nexus 6P。
开始调查的最简单方法是onAttachedToWindow
方法。什么时候实际调用? Its documentation says在为绘图创建视图表面之后调用它,但我们对此不满意。
为了找到答案,我们为视图设置了断点,重新启动应用以重新创建活动,并调查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
之后才会附加观点。