Short answer
You’re seeing the onStart
calls from the view only when the activity has been brought to the foreground after being in the background for some time. Currently it’s impossible to see earlier activity events from your activity’s views since the view hierarchy is still being created and the views are not attached to the window yet.
When an activity is initialised from scratch, the view hierarchy isn’t fully attached until after onResume
. This means that once your view’s onAttachedToWindow
is called, onStart
has already been executed. If you exit the activity you mentioned in the question, you should still see the events for onPause
and so on.
Normally if you put an activity to the background by pressing the home button for example, the activity is stopped but not destroyed. It stays in memory with its view hierarchy if there are adequate system resources to do so. When the activity is restored to the foreground, instead of creating it from scratch it calls onStart
and resumes from where it left off, without recreating the view hierarchy.
The “Don’t keep activities” option makes sure that each activity is destroyed right away after it leaves the foreground, making sure that your view’s onAttachedToWindow
is always called after onResume
since the view hierarchy needs to be recreated every time.
What you could do instead
Without sharing more code it’s not immediately clear why you need to set the listener within the view. It seems that you need to listen to the activity’s lifecycle method in any case.
If the listener is only tied to the activity’s lifecycle, you might be able to extract it completely out from the view and into the activity.
If it’s tied both to view’s and the activity’s lifecycle, you could try registering the activity lifecycle callbacks in the constructor of the view since a context is already available at that point.
Alternatively you could go for the solution that Google Maps currently has e.g. in MapView. It requires the activity to proxy all lifecycle methods to the view. This might be useful in case your view is very tightly knit with the activity’s lifecycle. You can see the documentation here.
A fourth option is to use a fragment instead of a view since it has its own set of lifecycle methods. Personally I don’t feel quite comfortable with fragments since their lifecycles are potentially even more complicated.
Longer answer
To explain why this is happening, we need to delve into Android’s source code. The things I’m explaining here are specific to this implementation and might differ between SDK versions and even between Android devices due to the manufacturer’s changes. You should not rely on these details in your code. I’ll be using the SDK 23 source code that ships with Android Studio and a Nexus 6P with build MTC19T.
The easiest place to start investigating is the onAttachedToWindow
method. When is it actually called? Its documentation says that it’s called after the view’s surface is created for drawing, but we’re not satisfied with that.
To find out, we set a breakpoint to a view, restart the app so that the activity is recreated, and investigate the first few frames in 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)
...
We can see that the first frames are from the view’s internal logic, from the parent ViewGroup, from something called a ViewRootImpl, and then from some callbacks from Choreographer and Handler.
We’re not sure what created those callbacks, but the closest callback implementation is named ViewRootImpl$TraversalRunnable so we’ll check that out:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
There’s the definition, and right below is the callback instance that’s given to Choreographer in this method:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
Choreographer is something that runs on every UI thread on Android. It’s used to synchronise events with the display’s frame rate. One reason to use it is to avoid wasting processing power by drawing things faster than what the display can show.
Since Choreographer uses the thread’s message queue, we couldn’t see this call in the previous frames because the call wasn’t made until Looper handled the message. We can set a breakpoint to this method to see where this call is coming from:
"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)
...
If we look into ActivityThread’s handleLaunchActivity
, there’s the call to handleResumeActivity
. Before that is a call to performLaunchActivity
, and in that method are calls to Instrumentation#callActivityOnCreate
, Activity#performStart
and so on.
So there we have our proof that the views aren’t attached until after onResume
.
SearchButton
inDemoActivity
was never attached to window as (if i did understand what is going on)DemoActivity
is "under"SearchActivity
(in terms of activities stack) ... also why you are bother about it? in this caseDemoActivity
is stopped state anyway – MansononAttachToWindow
just happens afteronStart
in this case which causes the problem. – Partner