📜 ⬆️ ⬇️

Life Cycle Activity Stack (Part 2)

As agreed in the first part of the article , in this we will consider tools for changing the standard behavior of the Activity Stack.

The whole theory on the current topic is present on developer.android.com/guide/topics/manifest/activity-element.html , I will refer to it in some places, and we will try to figure out how it works in practice and find out what situations it is can be used in real life.

Some parameters can be added both in AndroidManifest and the Intent flag dynamically in the code:
intent.setFlags(Intent.FLAG_ACTIVITY_*); 

android: launchMode


The parameter defines what should happen when we activate a new Intent with a call to a specific Activity.
In our example, it applies to ActivityA.

“Standard” and “singleTop” (FLAG_ACTIVITY_SINGLE_TOP)

"standard" is the default behavior. The system always creates a new Activity and adds it to the top of the stack.
Let's change our ActivityA so that instead of switching to ActivityB, it starts itself again.
')
("Standard") A-> A-> back-> back:
 ***   *** INFO/ActivityManager(249): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 28371 INFO/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=28410 uid=10060 gids={1028} DEBUG/ActivityA(28410): onCreate() DEBUG/ActivityA(28410): onStart() INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +2s64ms ***  ActivityA *** INFO/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 28410 DEBUG/ActivityA(28410): onCreate() DEBUG/ActivityA(28410): onStart() INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +739ms DEBUG/ActivityA(28410): onStop() ***  back *** DEBUG/ActivityA(28410): onStart() DEBUG/ActivityA(28410): onStop() DEBUG/ActivityA(28410): onDestroy() ***  back *** DEBUG/ActivityA(28410): onStop() DEBUG/ActivityA(28410): onDestroy() ***   Home screen*** 

We see that in the stack there were 2 identical Activities and only after two clicks back the process died.

The modifier "singleTop" protects from duplication Activity, which are located at the top of the stack, when it is called again.

("SingleTop") A-> A-> back:
 ***   *** INFO/ActivityManager(249): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 31016 INFO/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=31070 uid=10060 gids={1028} DEBUG/ActivityA(31070): onCreate() DEBUG/ActivityA(31070): onStart() INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +1s296ms ***  ActivityA *** INFO/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 31070 DEBUG/ActivityA(31070): onNewIntent() ***  back *** DEBUG/ActivityA(31070): onStop() DEBUG/ActivityA(31070): onDestroy() ***   Home screen*** 

A new Activity was not created; instead, there was an onNewIntent() call. On the first back we left the application.

"SingleTask" and "singleInstance"

The modifiers "singleTask" and "singleInstance" do not allow to have more than one entity of the same Activity. They differ in the ability to have other Activities with them in the task.

("SingleTask") A-> B-> C-> A-> back:
 ***   *** INFO/ActivityManager(249): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 1496 INFO/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=1529 uid=10060 gids={1028} DEBUG/ActivityA(1529): onCreate() DEBUG/ActivityA(1529): onStart() INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +1s769ms ***  ActivityB *** INFO/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityB u=0} from pid 1529 DEBUG/ActivityB(1529): onCreate() DEBUG/ActivityB(1529): onStart() INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityB: +524ms DEBUG/ActivityA(1529): onStop() ***  ActivityC *** INFO/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityC u=0} from pid 1529 DEBUG/ActivityC(1529): onCreate() DEBUG/ActivityC(1529): onStart() INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityC: +267ms DEBUG/ActivityB(1529): onStop() ***  ActivityA *** DEBUG/ActivityB(1529): onDestroy() DEBUG/ActivityA(1529): onNewIntent() DEBUG/ActivityA(1529): onStart() DEBUG/ActivityC(1529): onStop() DEBUG/ActivityC(1529): onDestroy() ***  back *** 11-13 00:08:00.039: DEBUG/ActivityA(1529): onStop() 11-13 00:08:00.039: DEBUG/ActivityA(1529): onDestroy() ***   Home screen*** 

When you switch to ActivityA again, the system destroyed all the Activities that were higher than it in the stack. Pressing back brought us to the Home Screen.

("SingleInstance") A-> B-> C-> A-> back-> back-> back:
 ***   *** 11-13 00:12:27.132: INFO/ActivityManager(249): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 2418 11-13 00:12:27.859: INFO/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=2438 uid=10060 gids={1028} 11-13 00:12:28.332: DEBUG/ActivityA(2438): onCreate() 11-13 00:12:28.457: DEBUG/ActivityA(2438): onStart() 11-13 00:12:29.254: INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +1s606ms ***  ActivityB *** 11-13 00:12:32.195: INFO/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityB u=0} from pid 2438 11-13 00:12:32.679: DEBUG/ActivityB(2438): onCreate() 11-13 00:12:32.824: DEBUG/ActivityB(2438): onStart() 11-13 00:12:33.394: INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityB: +897ms 11-13 00:12:33.547: DEBUG/ActivityA(2438): onStop() ***  ActivityC *** 11-13 00:12:36.257: INFO/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityC u=0} from pid 2438 11-13 00:12:36.507: DEBUG/ActivityC(2438): onCreate() 11-13 00:12:36.582: DEBUG/ActivityC(2438): onStart() 11-13 00:12:37.343: INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityC: +989ms 11-13 00:12:37.695: DEBUG/ActivityB(2438): onStop() ***  ActivityA *** 11-13 00:12:38.660: INFO/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 2438 11-13 00:12:38.734: DEBUG/ActivityA(2438): onNewIntent() 11-13 00:12:38.734: DEBUG/ActivityA(2438): onStart() 11-13 00:12:39.789: DEBUG/ActivityC(2438): onStop() ***  back *** 11-13 00:12:41.425: DEBUG/ActivityC(2438): onStart() 11-13 00:12:42.250: DEBUG/ActivityA(2438): onStop() 11-13 00:12:42.250: DEBUG/ActivityA(2438): onDestroy() ***  back *** 11-13 00:12:52.332: DEBUG/ActivityB(2438): onStart() 11-13 00:12:52.894: DEBUG/ActivityC(2438): onStop() 11-13 00:12:52.898: DEBUG/ActivityC(2438): onDestroy() ***  back *** 11-13 00:12:55.617: DEBUG/ActivityB(2438): onStop() 11-13 00:12:55.617: DEBUG/ActivityB(2438): onDestroy() ***   Home screen*** 

Repeated transition to ActivityA did not cause chain reactions, but opened a separate task with a single ActivityA. It was completed by first pressing back. Two more taps were enough to go to the Home Screen, because the only essence of ActivityA was destroyed above and there was no return to it. Externally, the transition from ActivityA to ActivityB and from ActivityC to ActivityA (that is, the transition between different tasks within one process) looked like a change of application, i.e. folding one Activity and “jumping out” a new one instead of a smoother transition.

android: noHistory (FLAG_ACTIVITY_NO_HISTORY)


The default is false . If true , then it will be impossible to return to the stopped Activity.
The parameter is applied to ActivityA with a value of true :
 <activity android:name=".ActivityA" android:noHistory="true"> 

A-> B-> back:
 ***   *** INFO/ActivityManager(249): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 4875 INFO/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=4915 uid=10060 gids={1028} DEBUG/ActivityA(4915): onCreate() DEBUG/ActivityA(4915): onStart() INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +1s383ms ***  ActivityB *** INFO/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityB u=0} from pid 4915 DEBUG/ActivityB(4915): onCreate() DEBUG/ActivityB(4915): onStart() INFO/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityB: +877ms DEBUG/ActivityA(4915): onStop() ***  back *** DEBUG/ActivityA(4915): onDestroy() DEBUG/ActivityB(4915): onStop() DEBUG/ActivityB(4915): onDestroy() ***   Home screen*** 

Judging by the launch of onDestroy() from ActivityA, it remained in memory even after ActivityA was called. onStop() , although a return to it was no longer possible.

The parameter is convenient to use if you need to show the logo when the application starts and never return to it.

android: clearTaskOnLaunch and android: finishOnTaskLaunch


The clearTaskOnLaunch parameter clearTaskOnLaunch if true will force the system to destroy all non-root Activities on the stack (or rather on a specific task) when the application is restarted. It makes sense to apply only to the root Activity, so in the example from which the log was filmed, I added it to ActivityA:
 <activity android:name=".ActivityA" android:clearTaskOnLaunch="true"> 

App start-> A-> B-> C-> Home-> App start:
 ***   *** INFO/ActivityManager(250): START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 476 DEBUG/ActivityA(3412): onCreate() DEBUG/ActivityA(3412): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +295ms ***  ActivityB *** INFO/ActivityManager(250): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityB u=0} from pid 3412 DEBUG/ActivityB(3412): onCreate() DEBUG/ActivityB(3412): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityB: +140ms DEBUG/ActivityA(3412): onStop() ***  ActivityC *** INFO/ActivityManager(250): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityC u=0} from pid 3412 DEBUG/ActivityC(3412): onCreate() DEBUG/ActivityC(3412): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityC: +131ms DEBUG/ActivityB(3412): onStop() ***  Home *** INFO/ActivityManager(250): START {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10200000 cmp=com.android.launcher/com.android.launcher2.Launcher u=0} from pid 250 DEBUG/ActivityC(3412): onStop() ***       *** INFO/ActivityManager(250): START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 476 DEBUG/ActivityC(3412): onDestroy() DEBUG/ActivityB(3412): onDestroy() DEBUG/ActivityA(3412): onStart() 

We see that when you restart the application, Android destroyed the child ActivityB and ActivityC from memory. We also have in mind that a return to the application through the Recents menu (a long tap on the Home button) does not initiate an Intent LAUNCHER, and therefore a return to ActivityC will happen.

You can achieve exactly the same behavior with the finishTaskOnLaunch parameter. Android will destroy those Activities when you restart the application, in which the value of this parameter will be true . Those. for our example, it is enough to register it in ActivityB and ActivityC to see the same log:
 <activity android:name=".ActivityB" android:finishOnTaskLaunch="true"/> <activity android:name=".ActivityC" android:finishOnTaskLaunch="true"/> 

Both parameters are false by default.

One possible use case is to implement the impossibility of returning to a stopped Activity in combination with the excludeFromRecents parameter (not including the Activity in the Recents menu). Although, I believe, there are more specific or, conversely, simple cases.

android: parentActivityName


This parameter can be made the parent of a specific Activity any we need. But there is some reservation that the return to it will not happen by the back button, but by Navigation Up (http://developer.android.com/training/implementing-navigation/ancestral.html), as, for example, in Action Bar'e . But we, in order not to bother, redefine onBackPressed() in ActivityC and make ActivityA the parent of ActivityC:
For example:
 @Override public void onBackPressed() { onNavigateUp(); } 

 <activity android:name=".ActivityC" android:parentActivityName=".ActivityA"> <!-- Parent activity meta-data to support 4.0 and lower --> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".ActivityA" /> </activity> 

A-> B-> C-> back:
 ***   *** INFO/ActivityManager(250): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 6620 INFO/ActivityManager(250): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=6634 uid=10060 gids={1028} DEBUG/ActivityA(6634): onCreate() DEBUG/ActivityA(6634): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +895ms ***  ActivityB *** INFO/ActivityManager(250): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityB u=0} from pid 6634 DEBUG/ActivityB(6634): onCreate() DEBUG/ActivityB(6634): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityB: +179ms DEBUG/ActivityA(6634): onStop() ***  ActivityC *** INFO/ActivityManager(250): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityC u=0} from pid 6634 DEBUG/ActivityC(6634): onCreate() DEBUG/ActivityC(6634): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityC: +144ms DEBUG/ActivityB(6634): onStop() ***  back *** INFO/ActivityManager(250): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 6634 DEBUG/ActivityB(6634): onDestroy() DEBUG/ActivityA(6634): onDestroy() DEBUG/ActivityA(6634): onCreate() DEBUG/ActivityA(6634): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +182ms DEBUG/ActivityC(6634): onStop() DEBUG/ActivityC(6634): onDestroy() 

We see that after clicking back there was even more of what was expected. Not only those Activities that stood above the parent were destroyed, she herself was also re-created. But overall behavior is expected.

It is reasonable to use it in order to allow the user to exit, for example, to the main menu after long wanderings by the child Activities without multiple returns on the back button (in the case of implementation, as expected, with the Action Bar).

android: allowTaskReparenting and android: taskAffinity


The parameter allowTaskReparenting allows allowTaskReparenting to bind the Activity that was called from task No. 1 and previously created in task No. 2 (that is, linked to it) to task No. 1.
Training:
 <activity android:name=".ActivityA" android:launchMode="singleInstance" > <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".ActivityB" android:launchMode="singleTask" /> <activity android:name=".ActivityC" android:launchMode="singleTask" android:allowTaskReparenting="true" android:taskAffinity=".ActivityA" /> 

Add another button to the form ActivityA, which will start ActivityC.

In the manifest file, we allowed the ActivityC to change the parent if it is claimed by ActivityA, which is a separate task for the android:launchMode="singleInstance" reason android:launchMode="singleInstance" .

App start-> A-> B-> C-> Home-> App start-> A-> C-> back:
 ***   *** INFO/ActivityManager(250): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 10495 INFO/ActivityManager(250): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=10524 uid=10060 gids={1028} DEBUG/ActivityA(10524): onCreate() DEBUG/ActivityA(10524): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +761ms ***  ActivityB *** INFO/ActivityManager(250): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityB u=0} from pid 10524 DEBUG/ActivityB(10524): onCreate() DEBUG/ActivityB(10524): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityB: +225ms DEBUG/ActivityA(10524): onStop() ***  ActivityC *** INFO/ActivityManager(250): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityC u=0} from pid 10524 DEBUG/ActivityC(10524): onCreate() DEBUG/ActivityC(10524): onStart() INFO/ActivityManager(250): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityC: +204ms DEBUG/ActivityB(10524): onStop() ***  Home*** INFO/ActivityManager(250): START {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10200000 cmp=com.android.launcher/com.android.launcher2.Launcher u=0} from pid 250 DEBUG/ActivityC(10524): onStop() ***       *** INFO/ActivityManager(250): START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 476 DEBUG/ActivityA(10524): onNewIntent() DEBUG/ActivityA(10524): onStart() ***  ActivityC *** INFO/ActivityManager(250): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityC u=0} from pid 10524 DEBUG/ActivityC(10524): onNewIntent() DEBUG/ActivityC(10524): onStart() DEBUG/ActivityA(10524): onStop() ***  back *** DEBUG/ActivityA(10524): onStart() DEBUG/ActivityC(10524): onStop() DEBUG/ActivityC(10524): onDestroy() 

Before pressing Home, we had two entities: Task1 [A], Task2 [B, C]. After restarting the application, we from ActivityA turned to ActivityC, i.e. to Task2, which is further, without allowTaskReparenting and taskAffinity , would behave as a separate application and, by pressing back, would return us to its root ActivityB. Thanks to the parameters, the back button led us back to Task1.

In real life, it is rarely necessary to build such complex work patterns within one application, so it is more logical to present separate applications on the site of Task1 and Task2, one of which triggers the Activity of the other to perform a short task and after pressing back it takes control of the device back.

android: alwaysRetainTaskState


By default, the system destroys the task along with its Activity after some time (“such as 30 minutes” © developer.android.com) if the user has not accessed it. They can be made to live forever (except in cases of lack of memory), by setting the alwaysRetainTaskState parameter to true for such an Activity. This is described in theory, and it is difficult to imagine a trick here, so I did not test it.
Added : It turned out that there is still a catch, thanks ara89 for his comment . Starting with Android 4.0 (API level 14), the Activity stopped being destroyed and this parameter became useless. The diff between 2.3.7 and 4.0.1 can be viewed here (pay attention to the ACTIVITY_INACTIVE_RESET_TIME field). However, this was a bug in the Android tracker, but it is still in the New status without Owner.

Life Cycle Activity Stack (Part 1)

Source: https://habr.com/ru/post/201886/


All Articles