The background,

Some time ago, I dealt with the feedback of a lost draft in the App. Many users reported that there was only one draft in the draft box after consecutively saving multiple drafts, which was obviously a loss of drafts. According to the data from the users, the system versions of the feedback users are all below Android 7.0.

After a period of investigation, it was found that the draft was overwritten. The direct reason was that the shooting link page (recording page, editing page and publishing page) was not closed after saving the draft, and the recording page was reused (the state was reused) again after shooting, resulting in the replacement of the draft when saving the draft.

So the question is, why does this happen? It started when we saved the draft.

Why doesn’t Clear Top work?

Normally, after saving the draft, we close the shooting link page, shutting down all the activities on the shooting link. Can be seen in the code, we are using Intent# FLAG_ACTIVITY_CLEAR_TOP | Intent# FLAG_ACTIVITY_NEW_TASK to achieve this purpose:


Logic is straightforward, is to add a clear_top flag, and then setClass assigned to jump to the home page, here getPublishContainerActivityClass returns the MainActivity, not appear abnormal.

I tried to make a package on Mi 10 Pro and saved the draft, but there was no recurrence problem. After returning to the home page, I pressed Return and directly quit the App, indicating that the page for shooting link was closed. Fortunately, after changing several mobile phones, I finally reproduced this case on an Android 5.1, and found that if I returned to the home page after saving the draft, I would return to the release page with the shooting link still in place.

So the core question is: why is the clear_top flag not in effect on Android 5.1?

FLAG_ACTIVITY_CLEAR_TOP does FLAG_ACTIVITY_CLEAR_TOP clear top?

A quick summary of Intent#FLAG_ACTIVITY_CLEAR_TOP is this: After setting this flag, if the target Activity already exists, it moves the Task of the target Activity to the foreground, finishes all activities above the target Activity, and finally opens the target Activity. How to determine if the target Activity already exists is not mentioned in the comments.


Back to the Aweme project. Typically, the home page, record page, edit page, and publish page are on the same task stack, as can be seen from the activity stack information printed by adb.

adb shell dumpsys activity activities | grep 'com.zhiliaoapp.musically'


See this a few Activity under the startup mode, and found nothing different places: VideoRecordNewActivity: singleTask VideoPublishEditActivity: standard(default) VideoPublishActivity: singleTask

Take a wild guess, could it be that different versions of ROM treat Intent#FLAG_ACTIVITY_CLEAR_TOP differently and this flag doesn’t work?

Try to reproduce the problem on Demo

Built A Demo project, created A, B, C three empty page, corresponding to the home page, shooting page, release page respectively, jump path is: A -> B -> C -> A. The startup modes of A, B and C are singleTop, singleTask and singleTask respectively, which are consistent with the shooting link in the App.

The test on Android 5.1, where the problem reappears online, found that after C returned to A with clear_top, the whole stack was cleared and the App was directly exited at point A without any exception.

The Aweme project does something with an Activity or Intent. Blind guess a hand. Print the task stack of the Android 5.1 App after saving the draft, and find something different:


The bottom of the task stack is SplashActivity. What about our home page? Where is MainActivity? We obviously take photos from the home page and then go to…… of the release page If you hit a breakpoint in a MainActivity file, you can see that the breakpoint works. If you hit a breakpoint in a MainActivity file, you can see that the breakpoint works. Yes, this is activity-alias, a mechanism supported since Android 1.0.

Check the declarations of SplashActivity and MainActivity in the AndroidManifest. You can see that SplashActivity is declared as activity-alias. Its targetActivity points to MainActivity.


For the details of activity-alias, I will change the Demo to Splash and declare it as activity-alias. Now the Demo startup process is Splash –> A –> B –> C –> A.


C opens A with clear top, but does not close B and C. So could it be the ROM? Try running on the emulator and find that it also reappears. Is it an Android Bug? Won’t, won’t, won’t……

5. Solve problems

Talk is cheap. Show me the code.

Since running the Demo App in the emulator also recur, you can try to trace the problem back to the AOSP source code.

5.1 Let’s review a few concepts in the startup process


5.1.1 ActivityRecord

An entry in the history stack, representing an activity.

The Activity is stored in the task stack as an ActivityRecord object. An ActivityRecord is created during the startup of an Activity, which represents the Activity to be started.

  1. ActivityRecord resultTo
    • who started this entry, so will get our reply

5.1.2 ActivityInfo

Information you can retrieve about a particular application activity or receiver. This corresponds to information collected from the AndroidManifest.xml’s and tags.

It holds a bunch of Activity configurations that we specify when we declare the Activity in the AndroidManifest.

  1. taskAffinity
      1. The affinity this activity has for another task in the system. The string here is the name of the task, often the package name of the overall package.

5.1.3 TaskRecord

Represents a stack of tasks that may have a bunch of activities on the same stack.

  1. Intent intent
    • The original intent that started the task
  2. ArrayList mActivities
    • List of all activities in the task arranged in history order
  3. ActivityStack stack
    • The ActivityStack it belongs to.

5.1.4 ensuring ActivityStack

State and management of a single stack of activities.

The task stack is held by ActivityStack.

  1. ArrayList mTaskHistory
    • The back history of all previous (and possibly still running) activities. It contains #TaskRecord objects.
    • 0 ~ size – 1, the last is the top task.

The relationship between ActivityRecord, TaskRecord, and ActivityStack can be easily represented by the following diagram:


5.1.5 activity – alias

Activity alias, which is a tag supported in AndroidManifest and used in a similar way to the Activity tag to indicate that an Activity is an alias for another Activity (targetActivity). When an activity of the activity-alias type is launched with an Intent, only targetActivity is eventually launched, without any lifecycle of the activity-alias activity itself.

5.2 See the processing logic of clear Top when the Activity starts from the source code

5.2.1 the Android 6.0

By referring to Android 6.0 source code, we can sort out the following Activity start link call sequence diagram.

Note:

  1. Because only the clear top logic is analyzed, the call link here only considers the scenario with the Clear Top Flag set

TaskRecord

As you can see from the sequence diagram above, an Activity is launched to find a task stack. Once the stack is found, the task stack is processed based on the Intent’s flag. If Intent#FLAG_ACTIVITY_CLEAR_TASK is set, existing activities in the task stack will be cleared.

If Intent#FLAG_ACTIVITY_CLEAR_TOP is set, clear top is performed to finish all activities above the target Activity in the task stack. The specific processing logic is as follows:

TaskRecord#performClearTaskLocked


When performClearTaskLocked executes, the activities in the task stack are iterated, If the realActivity property of an Activity is determined to be the same as that of the Activity to be started, clear Top is performed to clear all activities above that Activity in the task stack.

ActivityRecord

So where is ActivityRecord#realActivity set up? Moving on to the ActivityRecord source code, you can see that the realActivity is a final member variable in ActivityRecord, which makes things seem easier.


During the Activity launch process, an ActivityRecord object is created when it is executed in activitystackcontainer #startActivityLocked, This is the ActivityRecord object where the Activity is to be started. When the ActivityRecord is initialized, its realActivity is assigned a value:

ActivityRecord.java


When assigning a value to a realActivity, it assigns the component of the incoming Intent to one of three conditions:

  1. TargetActivity is empty. That is notactivity-alias
    • TargetActivity is specified only when activity-alias is specified in the AndroidManifest
  2. LaunchMode LAUNCH_MULTIPLE. Note The startup mode is Standard
  3. LaunchMode LAUNCH_SINGLE_TOP. The startup mode is singleTop

So far, we’ve seen how Android 6.0 handles Clear Top. The assignment logic of the realActivity here is key and will be reviewed below.

5.2.2 Android 7.0

Android 7.0 and Android 6.0 have some changes in the call link, mainly by removing the startup logic from the original ActivityStackSupervisor into a new ActivityStarter class.


Clear top is still handled in TaskRecord#performClearTaskLocked.

TaskRecord

The logic for executing clear Top remains the same, as it is for realActivity.

TaskRecord#performClearTaskLocked


ActivityRecord


As you can see, realActivity is still final.

ActivityRecord.java


But! Android 7.0 tweaks the assignment logic for ActivityRecord#realActivity and adds a new judgment: aInfo.targetActivity.equals(_intent.getComponent().getClassName()

This new difference in Android 7.0 can be illustrated with this example:

  1. The first step: Start the ActivityRecord directly and insert the ActivityRecord into the task stack. The realActivity property of the ActivityRecord points to the targetActivity corresponding to the ActivityRecord – Alias
  2. Step 2: Open several more intermediate activities in the same stack, and then directly launch targetActivity with clear_TOP (specifying the class as targetActivity). Clear Top will take effect and the stack will be cleared

Activities declared as activity-alias do not support launchMode, so the default launchMode is standard, which is easy to understand. After all, it is just a placeholder.

Why clear Top works is that the realActivity of the ActivityRecord created when targetActivity is started is also the targetActivity pointed to, So when TaskRecord#performClearTaskLocked is executed, it will find that it is equal to the realActivity of the ActivityRecord created when activity-alias is started. Clear Top works because both are targetActivities to point to.

Prior to Android 7.0, performing the same two steps above does not take effect and the task stack is not cleaned. Because the realActivity of the ActivityRecord object created when activity-alias is started directly points to the activity-alias itself, The ActivityRecord#realActivity created when targetActivity is directly started again points to targetActivity.

5.2.3 requires Android 10.0

The startup process for Android 10.0 changes a lot, but the core logic executed by Clear_top is the same as Android 7.0.


TaskRecord

TaskRecord#performClearTaskLocked


ActivityRecord

So where is the ActivityRecord#mActivityComponent set?

ActivityRecord.java


5.2.4 To summarize

If clear_top is set in an Intent, Android will traverse the entire task stack while handling clear_TOP. If clear_top is set in an Intent, Android will traverse the entire task stack. Find an existing Activity instance in the task stack by judging ActivityRecord#realActivity. If the target Activity is found, all activities on top of the target Activity finish.

ActivityRecord#realActivity is initialized in the ActivityRecord constructor, and the initialization logic is different between Android 6.0 and Android 7.0+.

When initializing an ActivityRecord, Android 5.0/6.0 (including the following) does not determine the ActivityRecord alias. RealActivity refers to the ActivityRecord itself. In Android 7.0+, activity-alias is checked. RealActivity points to targetActivity of activity-alias.

This difference leads to: If the Activity to be started is a targetActivity of an activity-alias, Android 6.0 and Android 7.0+ may behave differently when handling clear_top Flag in an Intent. The former clear Top does not take effect, while the latter Clear Top takes effect.

5.3 The truth comes out

Back to our original question: why does MainActivity fail to clear the task stack on the release page of our App with clear Top Flag on Android 7.0 or lower?

Let’s see how we jump to the front page again. Jump page from below you can see is to specify the class is AVEnv APPLICATION_SERVICE. GetPublishContainerActivityClass (), see the specific implementation that this class is MainActivity, So it does go straight to the front page.


As mentioned earlier, the Launch Activity of our App is SplashActivity, but it is just an activity-alias with targetActivity pointing to MainActivity. The launchMode of MainActivity is singleTop. After the analysis of Android 6.0, Android 7.0, Android 10.0 startup process, I think the answer has been relatively obvious.

When launching the App on Android 5.1, launching the Launch Activity actually launches the MainActivity. But the realActivity of the ActivityRecord inserted into the task stack points to SplashActivity. After completing the publishing process, click save Draft on the publishing page and start MainActivity again. The realActivity of the ActivityRecord created points to MainActivity. Clear Top will not take effect because realActivity = MainActivity is not found in the task stack.

Clear Top works on Android 7.0+ for reasons that will not be explained here.

To avoid the problem that clear_top flag does not take effect due to the above differences, specify clear_TOP’s Intent to the activity-alias activity. To fix the problem, you can set the intent class to SplashActivity for the intent that opens the front page after saving the draft. Running the intent class on Android 5.1 did fix the problem.

Of course, since AOSP is a logical change from Android 7.0+, it also explains why the feedback users are under Android 7.0.

5.4 Verifying this Operation on Demo

Launch the Demo App on Android 5.1 and print the task stack with ADB. Print out ActivityRecord#realActivity in the task stack pointing to Splash

adb shell dumpsys activity activities | grep 'com.yongf.android.myapplication'


The same package prints the following information on Android 7.0. You can see that the ActivityRecord#realActivity in the task stack no longer points to Splash, but to A.

adb shell dumpsys activity activities | grep 'com.yongf.android.myapplication'


Change the startup link on Demo to Splash –> A –> B –> C –> Splash. You can see that B and C are both dead on the test machine and emulator of Android 5.1. Clear Top takes effect.

At the end of the article, another interesting point, it is estimated that 6.0 was found on this problem, so in Android 7.0 fixed this problem, because in the ActivityRecord constructor realActivity assignment logic source 7.0 added such a comment:


Finally, back to the title of this article, Intent#FLAG_ACTIVITY_CLEAR_TOP Does it really clear top, do you know? ^_^

6. Demo Project

To upload.

7. Recommend several tools

  1. Carbon
    • Carbon is the easiest way to create and share beautiful images of your source code.
  2. Android Source Code Viewer
    • Android Code Search

Viii. This article updates the history

  1. 2020/10/25: Updated image source, fixed article image display issues

Ix. About the author

  1. Github
  2. zhihu
  3. The Denver nuggets
  4. Personal website

Wechat Official Account:


This article is formatted using MDNICE