Activity启动模式(一)之 launchMode

launchMode简介

Activity启动模式,即Activity启动的方式。 lauchMode可以配置在清单文件AndroidManifest.xml文件中。 也可通过java代码动态配置。

知识储备

任务栈(Task)

在讲解Activity启动模式前,先讲解下Activity的管理方式:任务栈(Task),这有利于我们理解Activity启动模式。

  • 采用任务栈任务栈(Task)的形式管理Activity
  • 任务栈采用“先进后出”的栈结构
  • 每按一次Back键或finish一个Activity,就有一个栈顶的Activity出栈

任务栈-先进后出

终端使用adb shell指令

Android还为开发者提供了adb(Android Debug Bridge),这是非常强大的调试工具。最常用的自然是logcat来显示日志记录。另外一个很强大的指令就是这里要提到的dumpsys。dumpsys还可以添加不同的参数来指示需要输出哪一类Service的信息。对于本文提到的内容,需要查看的就是activity。

adb shell dumpsys activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
 ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)    //注意一下
* PendingIntentRecord{c8486ed com.xiaomi.xmsf startService}
* PendingIntentRecord{dd7d922 com.xiaomi.xmsf startService}
* PendingIntentRecord{b5bffb3 android broadcastIntent}
// 省略 N 行 ...

ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts) //注意一下
Historical broadcasts [foreground]:
#0: BroadcastRecord{88e1b3a u-1 android.intent.action.TIME_TICK}
act=android.intent.action.TIME_TICK flg=0x50000014 (has extras)
extras: Bundle[{android.intent.extra.ALARM_COUNT=1}]
#1: BroadcastRecord{9a973eb u-1 android.intent.action.TIME_TICK}
act=android.intent.action.TIME_TICK flg=0x50000014 (has extras)
extras: Bundle[{android.intent.extra.ALARM_COUNT=1}]
// 省略 N 行 ...

ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers) //注意一下
Published single-user content providers (by class):
* ContentProviderRecord{2af5291 u0 com.android.providers.telephony/.TelephonyProvider}
proc=ProcessRecord{108d60f 4086:com.android.phone/1001}
singleton=true
authority=telephony
// 省略 N 行 ...

ACTIVITY MANAGER URI PERMISSIONS (dumpsys activity permissions) //注意一下
Granted Uri Permissions:
* UID 1000 holds:
UriPermission{e61888e 0 @ content://downloads/all_downloads/125}
UriPermission{2f698af 0 @ content://downloads/all_downloads/126}
// 省略 N 行 ...

ACTIVITY MANAGER SERVICES (dumpsys activity services) //注意一下
User 0 active services:
* ServiceRecord{2e1e671 u0 com.android.bluetooth/.hid.HidService}
app=ProcessRecord{b328630 4915:com.android.bluetooth/1002}
created=-3d15h37m1s744ms started=true connections=1
Connections:
act=android.bluetooth.IBluetoothInputDevice -> 2989:com.android.systemui/1000
// 省略 N 行 ...

ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents) //注意一下
Recent tasks:
* Recent #0: TaskRecord{f3e16fb #1558 A=com.nhtzj.appb U=0 sz=1}
* Recent #1: TaskRecord{663d296 #1405 A=com.miui.home U=0 sz=1}
* Recent #2: TaskRecord{1a325b8 #1557 A=com.nhtzj.learnapplication U=0 sz=1}
* Recent #3: TaskRecord{141234a #1427 A=.delinstalledapk U=0 sz=0}
* Recent #4: TaskRecord{6b8c2bb #1425 A=com.miui.packageinstaller U=0 sz=0}
* Recent #5: TaskRecord{fa213d8 #1406 A=android.task.stk.StkLauncherActivity U=0 sz=0}
// 省略 N 行 ...

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities) //注意一下
Display #0 (activities from top to bottom):
Stack #23:
Task id #1558
TaskRecord{f3e16fb #1558 A=com.nhtzj.appb U=0 sz=1}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.appb/.sample.MainActivity (has extras) }
Hist #0: ActivityRecord{e4adca6 u0 com.nhtzj.appb/.sample.MainActivity t1558}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.appb/.sample.MainActivity bnds=[76,625][258,807] (has extras) }
ProcessRecord{36e1c21 9302:com.nhtzj.appb/u0a267}
// 省略 N 行 ...

ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes) //注意一下
Isolated process list (sorted by uid):
Isolated # 0: ProcessRecord{284e1aa 2979:WebViewLoader-armeabi-v7a/1037}

UID states:
UID 1000: UidRecord{2d2119b 1000 P procs:13}
UID 1001: UidRecord{db0e938 1001 P procs:2}
// 省略 N 行 ...

该命令返回一大串内容,但也能清晰看出它们比较详细的分类

每一个类别都有一个括号内容,给出了更加详细的指令来查看该类别下更多具体内容。

比如本次用到的类别:ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)

括号内的dumpsys activity activities为activity的详细指令。

因此再来尝试指令:

db shell dumpsys activity activities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #12:
Task id #1302
* TaskRecord{5fb98dc #1302 A=com.nhtzj.learnapplication U=0 sz=3}
userId=0 effectiveUid=u0a238 mCallingUid=2000 mCallingPackage=null
affinity=com.nhtzj.learnapplication
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.nhtzj.learnapplication/.activity.main.MainActivity}
realActivity=com.nhtzj.learnapplication/.activity.main.MainActivity
autoRemoveRecents=false isPersistable=true numFullscreen=3 taskType=0 mTaskToReturnTo=1
rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{772a6cc u0 com.nhtzj.learnapplication/.activity.main.MainActivity t1302}, ActivityRecord{182f696 u0 com.nhtzj.learnapplication/.activity.sample.lanchmode.standard.StandardActivity t1302}, ActivityRecord{d4a426e u0 com.nhtzj.learnapplication/.activity.sample.lanchmode.standard.StandardActivity t1302}]
askedCompatMode=false inRecents=true isAvailable=true
lastThumbnail=null lastThumbnailFile=/data/system/recent_images/1302_task_thumbnail.png
stackId=12
hasBeenVisible=true mResizeable=false firstActiveTime=1524119334682 lastActiveTime=1524119334682 (inactive for 147s)
* Hist #2: ActivityRecord{d4a426e u0 com.nhtzj.learnapplication/.activity.sample.lanchmode.standard.StandardActivity t1302}
packageName=com.nhtzj.learnapplication processName=com.nhtzj.learnapplication
launchedFromUid=10238 launchedFromPackage=com.nhtzj.learnapplication userId=0
app=ProcessRecord{32912fe 28815:com.nhtzj.learnapplication/u0a238}
Intent { cmp=com.nhtzj.learnapplication/.activity.sample.lanchmode.standard.StandardActivity }
frontOfTask=false task=TaskRecord{5fb98dc #1302 A=com.nhtzj.learnapplication U=0 sz=3}
taskAffinity=com.nhtzj.learnapplication
realActivity=com.nhtzj.learnapplication/.activity.sample.lanchmode.standard.StandardActivity
baseDir=/data/app/com.nhtzj.learnapplication-2/base.apk
dataDir=/data/user/0/com.nhtzj.learnapplication
stateNotNeeded=false componentSpecified=true mActivityType=0
compat={480dpi} labelRes=0x0 icon=0x7f0b0000 theme=0x7f0d0005
config={1.0 460mcc7mnc zh_CN ldltr sw360dp w360dp h576dp 480dpi nrml port finger -keyb/v/h -nav/h s.41 themeChanged=0 themeChangedFlags=0}
stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? themeChanged=0 themeChangedFlags=0}
taskDescription: iconFilename=null label="null" color=ff3f51b5
launchFailed=false launchCount=1 lastLaunchTime=-2m27s763ms
haveState=false icicle=null
state=RESUMED stopped=false delayedResume=false finishing=false
keysPaused=false inHistory=true visible=true sleeping=false idle=true
fullscreen=true noDisplay=false immersive=false launchMode=0
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=APPLICATION_ACTIVITY_TYPE
waitingVisible=false nowVisible=true lastVisibleTime=-2m27s389ms
realComponentName=com.nhtzj.learnapplication/.activity.sample.lanchmode.standard.StandardActivity

整个log显示了当前所有在运行的任务栈,它们的id分别是什么。对于每个Task,也有Activity数量等信息,同时也列出了其中的Activity列表,并且对于每个Activity也有比较详细的描述,比如启动它的Intent的内容。

如果觉得内容过多,只想看看栈的内容,也可以直接跳到”Running activities (most recent first)”那部分,比较简洁而又明了的列出了栈中得Activity列表,就能知道当按下返回键的时候会应该会回到哪个Activity以后是要退出程序。


对于”Running activities”的内容在dumpsys activity中就有,并不需要dumpsys activity activities,也可以用下边的指令来限制仅输出”Running activities”列表:

adb shell dumpsys activity activities | sed -En -e ‘/Running activities/,/Run #0/p’

1
2
3
4
5
6
7
8
9
➜  ~ adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'
Running activities (most recent first):
TaskRecord{5fb98dc #1302 A=com.nhtzj.learnapplication U=0 sz=3}
Run #2: ActivityRecord{d4a426e u0 com.nhtzj.learnapplication/.activity.sample.lanchmode.standard.StandardActivity t1302}
Run #1: ActivityRecord{182f696 u0 com.nhtzj.learnapplication/.activity.sample.lanchmode.standard.StandardActivity t1302}
Run #0: ActivityRecord{772a6cc u0 com.nhtzj.learnapplication/.activity.main.MainActivity t1302}
Running activities (most recent first):
TaskRecord{77ff1c8 #4 A=com.miui.home U=0 sz=1}
Run #0: ActivityRecord{98a1459 u0 com.miui.home/.launcher.Launcher t4}

其中,TaskRecord中的“A=”的值,即为taskAffinity设定的值。 taskAffinity的说明,请参考下文Activity启动模式之 taskAffinity

四大启动模式

code 简介
standard 标准模式
singleTop 栈顶复用模式
singleTask 栈内复用模式
singleInstance 单例模式

具体介绍

点击查看:lanchMode测试源码

标准模式(standard)

标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。并且存在于 启动它的Activity实例所在的task之中。

标准模式为activity的默认启动方式,在清单文件中可以不写明。即默认为“standard”。

清单文件配置

1
2
3
4
   <activity
android:name=".activity.sample.lanchmode.standard.StandardActivity"
android:label="启动模式-standard"
android:launchMode="standard" />

或 不写明

1
2
3
<activity
android:name=".activity.sample.lanchmode.standard.StandardActivity"
android:label="启动模式-standard"/>

task中的形式如下: standard-task

栈顶复用模式(singleTop)

  • 如果需要新建的Activity位于任务栈栈顶,那么此Activity的实例就不会重建,而是重用栈顶的实例( 调用实例的 onNewIntent() 、不调用onCreate()和onStart())。
  • 否则就会创建该Activity新的实例,并放入栈顶

流程图如下: singleTop流程图

栈内复用模式(singleTask)

官方说明:

The system creates the activity at the root of a new task and routes the intent to it. However, if an instance of the activity already exists, the system routes the intent to existing instance through a call to its onNewIntent() method, rather than creating a new one.

翻译过来就是:

在singleTask模式下,如果是第一次创建该Activity实例时,则会新建task并将该Activity添加到该task中。否则(该Activity的实例已存在),则会将栈内该Activity实例之后加入的Activity关闭,并打开已有的Activity实例,并调用Activity的onNewIntent()方法,而不会新建Activity实例。

流程图如下: singleTask流程图

单例模式(singleInstance)

  • 栈内复用模式(singleTask)的强化,该模式下全局仅有一个实例
  • 打开该Activity时,直接创建一个新的任务栈,并创建该Activity实例放入新栈中
  • 一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例
  • 使用场景:多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。

官方介绍

android:launchMode (本地)
android:launchMode (developer.android.com,需翻墙)

总结

  • 唯一性从小到大:standard < singleTop < singleTask < singleInstance
  • standard:无限制
  • singleTop:栈顶唯一
  • singleTask:栈内唯一
  • singleInstance:全局唯一

singleTop、singleTask、singleInstance重用时不会重走整个生命周期

  • singleTop:onPause > onNewIntent > onResume
  • singleTask(位于栈顶):同singleTop一致
  • singleTask(没有位于栈顶):onNewIntent > onRestart > onStart > onResume
  • singleInstance(singleInstance实例可见,即处于前台):同singleTop一致
  • singleInstance(singleInstance实例不可见,即处于后台):同 singleTask(没有位于栈顶) 一致
坚持原创技术分享,您的支持是对我最大的鼓励!