One, foreword

The Activity lifecycle execution sequence is familiar to Android developers. But is this always the case? Does its life cycle change in certain scenarios? Let’s take a look at how familiar things are different and how we can deal with them.

Common life cycle functions

Figure 2-1 shows the Activity lifecycle process on Google’s official website:

Figure 2-1 Activity lifecycle flow chart [1] The common lifecycle functions include onCreate(), onStart(), onResume(), onPause(), onStop(), onDestory(), and so on:

  1. OnCreate () is triggered when the system creates the Activity for the first time, and this callback should only happen once during the Activity’s life cycle. After the onCreate() method completes, the Activity enters the “Started” state and the onStart() callback is triggered.

  2. OnStart () indicates the “Started” state. Once this method is called, representing the Activity to the user, the application prepares the Activity to come to the foreground and interact with the user. OnStart () completes very quickly. Once this callback is finished, the Activity enters the “Resumed” state and the onResume() method is called.

  3. OnResume () is the state in which the application interacts with the user, which we call the “recovered” state. The app stays that way until something happens that takes the focus away from the app. Such events include receiving an incoming call, the user navigating to another Activity, or the device screen shutting down. When an interrupt event occurs, the Activity enters the “paused” state and the system calls the onPause() callback.

  4. OnPause () is considered the first sign that the user is about to leave the Activity (although this does not always mean that the Activity will be destroyed). Completion of onPause() does not mean that the Activity leaves the “paused” state. Instead, the Activity remains in this state until it recovers or becomes completely invisible to the user. If the Activity resumes, the system calls the onResume() callback again. If the Activity returns from the “Paused” state to the “Resumed” state, the system keeps the Activity instance in memory and calls the instance’s onResume() method. If the Activity becomes completely invisible, the onStop() method is called;

  5. If the Activity is no longer visible to the user, it has entered the “Stopped” state, so the onStop() callback is triggered. This can happen, for example, when a newly launched Activity covers the entire screen. In the onStop() method, the application should release or adjust unwanted resources that are not visible to the user, while also performing cpu-intensive shutdown operations in onStop(). When the Activity enters the Stopped state, it either returns to interact with the user or stops running and disappears. If the Activity returns, onRestart() is called. If the Activity ends, onDestroy() is called;

  6. OnDestroy () is called before the Activity is destroyed. If the Activity is about to end, onDestroy() is the last lifecycle callback the Activity receives. If onDestroy() is called due to a configuration change, the system immediately creates a new Activity instance and then calls onCreate() for the new instance in the new configuration.

The life cycle of an Activity in common cases is described above, but it is different when we use TabActivity, so let’s take a look.

Third, TabActivity

3.1 How to Use it

The main function of TabActivity is to create a Tab corresponding to an Activity (of course, now generally use the Fragment), as shown in Figure 3-1:

Let’s now look at how to use TabActivity.

First, define a page ParentActivity that inherits from TabActivity, and then get TabHost from TabActivity, as shown in the following code:


public class ParentActivity extends TabActivity {
    private TabHost mTabHost;
    private final static String TAG = "SA.ParentActivity";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate");
        // Get TabHost in TabActivity
        mTabHost = getTabHost();
        // This is used to add a specific Tabs and Tab to the corresponding Activity
        addFirstTab();
        addSecondTab();
        addThirdTab();
        addFourthTab();
    }
 
    public void addFirstTab(){
        Intent intent = new Intent();
        intent.setClass(ParentActivity.this, FirstChildActivity.class);
        TabHost.TabSpec spec = mTabHost.newTabSpec("One");
        spec.setIndicator("one".null);
        spec.setContent(intent);
        mTabHost.addTab(spec);
    }
 
    public void addSecondTab(){
        Intent intent = new Intent();
        intent.setClass(ParentActivity.this, SecondChildActivity.class);
        TabHost.TabSpec spec = mTabHost.newTabSpec("Two");
        spec.setIndicator("two".null);
        spec.setContent(intent);
        mTabHost.addTab(spec);
    }
    public void addThirdTab(){
        Intent intent = new Intent();
        intent.setClass(ParentActivity.this, ThirdChildActivity.class);
        TabHost.TabSpec spec = mTabHost.newTabSpec("Three");
        spec.setIndicator("three".null);
        spec.setContent(intent);
        mTabHost.addTab(spec);
    }
    public void addFourthTab(){
        Intent intent = new Intent();
        intent.setClass(ParentActivity.this, FourthChildActivity.class);
        TabHost.TabSpec spec = mTabHost.newTabSpec("Four");
        spec.setIndicator("four".null); spec.setContent(intent); mTabHost.addTab(spec); }}Copy the code

Then, create a page such as FirstChildActivity, which will be created as normal.

Now to get down to business, we print logs in the lifecycle functions in ParentActivity and each ChildActivity (FirstChildActivity, SecondChildActivity, etc.).

When we switch ChildActivity, we see a different life cycle than when we normally start an Activity. Here’s why.

3.2 Life cycle details

Let’s look at the life cycles of ParentActivity and each ChildActivity in different cases. For cold startup, FirstChildActivity is selected by default, and the lifecycle process is shown in Figure 3-2:

Figure 3-2 Flowchart of the cold start life cycle

As you can see, the life cycles of the two activities are executed alternately on a cold start. Switch to SecondChildActivity, and the lifecycle process is shown in Figure 3-3:

Figure 3-3 Flowchart for switching to SecondActivity for the first time

At this point, FirstChildActivity’s onStop life cycle will not execute, and finally we will cut back from SecondChildActivity to FirstChildActivity, as shown in Figure 3-4:

The onStart life cycle for FirstChildActivity is not executed, and the onStop life cycle for SecondChildActivity is not executed. Obviously inconsistent with the normal life cycle, let’s analyze the reasons.

3.3 Source Code Analysis

Why is there a different Activity life cycle when using TabActivity? Let’s analyze the reason through the source code. First, take a look at the TabActivity source code.


public class TabActivity extends ActivityGroup {
    private TabHost mTabHost;
 
    // ...
    @Override
    public void onContentChanged() {
        super.onContentChanged();
        // 1. Initialize TabHost
        mTabHost = findViewById(com.android.internal.R.id.tabhost);
 
        if (mTabHost == null) {
            throw new RuntimeException(
                    "Your content must have a TabHost whose id attribute is " +
                    "'android.R.id.tabhost'");
        }
        // 2.TabHost binds to LocalActivityManager
        mTabHost.setup(getLocalActivityManager());
    }
 
    private void ensureTabHost() {
        if (mTabHost == null) {
            this.setContentView(com.android.internal.R.layout.tab_content);
        }
    }
 
    @Override
    protected void onPostCreate(Bundle icicle) {       
        super.onPostCreate(icicle);
 
        ensureTabHost();
 
        if (mTabHost.getCurrentTab() == -1) {
            // 3. The first Tab is selected by default
            mTabHost.setCurrentTab(0);
        }
    }
 
    public TabHost getTabHost() {
        ensureTabHost();
        returnmTabHost; }}Copy the code

There are three key points to note in TabActivity:

  1. The TabHost is initialized in the onContentChanged() method;

  2. GetLocalActivityManager () gets the LocalActivityManager initialized from the parent ActivityGroup, We also bind TabHost to LocalActivityManager in TabActivity with the TabHost#setup() method;

  3. In the onPostCreate() method, the first Tab is selected by default via mtabhost.setCurrentTab (0).

Here we still don’t see why the Activity lifecycle is different, and its parent ActivityGroup just initializes the LocalActivityManager object, Now let’s focus on what the mTabHost.setCurrentTab(0) step does.


public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
 
    / /...
    public void setCurrentTab(int index) {
        // ...
        // Close the previous page
        if(mCurrentTab ! = -1) {
            mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
        }
 
        mCurrentTab = index;
        // Get the view of the current page
        mCurrentView = spec.mContentStrategy.getContentView();
 
        if (mCurrentView.getParent() == null) {
            mTabContent
                    .addView(
                            mCurrentView,
                            new ViewGroup.LayoutParams(
                                    ViewGroup.LayoutParams.MATCH_PARENT,
                                    ViewGroup.LayoutParams.MATCH_PARENT));
        }
 
        // ...}}Copy the code

Here using TabHost. IntentContentStrategy# getContentView () obtain the View of the current page, then getContentView () and done what, let’s take a look at its source.


public View getContentView() {
    if (mLocalActivityManager == null) {
        throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
    }
    // Start the Activity with LocalActivityManagerfinal Window w = mLocalActivityManager.startActivity( mTag, mIntent); final View wd = w ! =null ? w.getDecorView() : null;
    if(mLaunchedView ! = wd && mLaunchedView ! =null) {
        if(mLaunchedView.getParent() ! =null) {
            mTabContent.removeView(mLaunchedView);
        }
    }
    mLaunchedView = wd;
    // ...
    return mLaunchedView;
}
Copy the code

After starting the Activity with LocalActivityManager, let’s look at how the startActivity() method of LocalActivityManager is implemented.


public Window startActivity(String id, Intent intent) {
    // ...
    // Initialize the LocalActivityManager variable mSingleMode in ActivityGroup
    if (mSingleMode) {
        LocalActivityRecord old = mResumed;
  
        if(old ! =null&& old ! = r && mCurState == RESUMED) {// Change the state of the FirstChildActivity pagemoveToState(old, STARTED); }}// ...
    // Change the state of the SecondChildActivity page
    moveToState(r, mCurState);
    if (mSingleMode) {
        mResumed = r;
    }
    return r.window;
}
Copy the code

Part of the startActivity source code is shown above. Where mSingleMode is the variable passed in when ActivityGroup initializes LocalActivityManager and is always true. Assuming you now switch from FirstChildActivity to SecondChildActivity, you first change the state of FirstChildActivity using the moveToState() method, Next, change the SecondChildActivity state.

The truth should be in the moveToState() method, so let’s look at the implementation of moveToState().


private void moveToState(LocalActivityRecord r, int desiredState) {
    / /...
    // Start ChildActivity for the first time
    if (r.curState == INITIALIZING) {
        // Start the current ChildActivity
        r.activity = mActivityThread.startActivityNow(
                mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance);
        if (r.activity == null) {
            return;
        }
          
        r.curState = STARTED;
        // ...
        return;
    }
    // Suppose you switch from FirstChildActivity to SecondChildActivity
    switch (r.curState) {
        / /...
        // The state of SecondChildActivity from STARTED to RESUMED
        case STARTED:
            if (desiredState == RESUMED) {
                // Need to resume it...
                if (localLOGV) Log.v(TAG, r.id + ": resuming");
                mActivityThread.performResumeActivity(r, true."moveToState-STARTED");
                r.instanceState = null;
                r.curState = RESUMED;
            }
            // ...
            return;
         // The state of FirstChildActivity, from RESUMED to STARTED
        case RESUMED:
            if (desiredState == STARTED) {
                if (localLOGV) Log.v(TAG, r.id + ": pausing");
                performPause(r, mFinishing);
                r.curState = STARTED;
            }
            // ...
            return; }}Copy the code

In the moveToState() method, the current ChildActivity is either started for the first time or not for the first time:

  1. On first startup, call the ActivityThread#startActivityNow() method to start the Activity;

  2. When not entering ChildActivity for the first time, it is assumed that you switched from FirstChildActivity to SecondChildActivity. FirstChildActivity calls performPause and triggers the onPause() life cycle, while SecondChildActivity calls performResumeActivity, Only the onResume() life cycle is triggered.

This explains why the life cycle of using TabActivity is so different. It turns out to be the “fault” of the LocalActivityManager in the ActivityGroup.

3.4 summary

Let’s summarize why Activity lifecycle functions are triggered at different stages of an Activity. From our analysis above, we can also see that the reason is that the system calls related methods that trigger the life cycle. If, in some cases, the system does not call these methods, then the lifecycle functions “fail,” which is the root cause of the inconsistent lifecycle using TabActivity.

Is there a way to tell when TabActivity is so special? The answer is yes.

We can recall how the LocalActivityManager starts the Activity: the ParentActivity parameter is passed in when the ActivityThread#startActivityNow() method is called. Instead of ContextWrapper#startActivity(), the Activity has no ParentActivity. Therefore, you can use the Activity#getParent() method to determine whether the current Activity has a Parent or not to identify this special case.

Four,

This article introduced the Activity life cycle, then used TabActivity as an example to show a different life cycle. Finally, you are welcome to discuss and study together. The so-called gentleman of learning, do not walk away from that knowledge also, will be.

References:

[1] developer.android.com/guide/compo…