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 | <activity |
或 不写明1
2
3<activity
android:name=".activity.sample.lanchmode.standard.StandardActivity"
android:label="启动模式-standard"/>
task中的形式如下:
栈顶复用模式(singleTop)
- 如果需要新建的Activity位于任务栈栈顶,那么此Activity的实例就不会重建,而是重用栈顶的实例( 调用实例的 onNewIntent() 、不调用onCreate()和onStart())。
- 否则就会创建该Activity新的实例,并放入栈顶
流程图如下:
栈内复用模式(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实例。
流程图如下:
单例模式(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(没有位于栈顶) 一致