oynix

于无声处听惊雷,于无色处见繁花

Android基础之singleTask

时间做的久了,基础的知识忘的越来越多,正所谓:基础不牢,地动山摇。最近有需要,所以在慢慢重来,今天说一说Activity的几种启动模式。

前言

启动模式有4种:standardsingleTopsingleTasksingleInstance,这次主要说一说singleTask,相比于其他,这个算是比较复杂一些的。

利用adb提供的命令adb shell dumpsys activity activities可以查看设备当前的activity栈信息。此外,adb shell dumpsys可以查看各种信息,直接输入就会列出它所支持的所有服务。

下面贴了一段我用的测试设备的activity信息,乍一看上去内容有些冗杂,其实是因为里面包含了很详细的信息和关系,从头按行读,读懂的话不是问题。从这些数据中,可以对ProcessTask之间的关系有个大致了解。

为了不影响阅读体验,我把它折叠起来了。

view detail
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
$ adb shell dumpsys activity activities
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):

Stack #7: type=standard mode=fullscreen
isSleeping=false
mBounds=Rect(0, 0 - 0, 0)
Task id #25
mBounds=Rect(0, 0 - 0, 0)
mMinWidth=-1
mMinHeight=-1
mLastNonFullscreenBounds=null
* TaskRecord{3e1780c #25 A=com.oynix.launch.mode.sample U=0 StackId=7 sz=2}
userId=0 effectiveUid=u0a208 mCallingUid=2000 mUserSetupComplete=true mCallingPackage=null
affinity=com.oynix.launch.mode.sample
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.oynix.launch.mode.sample/.MainActivity}
realActivity=com.oynix.launch.mode.sample/.MainActivity
autoRemoveRecents=false isPersistable=true numFullscreen=2 activityType=1
rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{a1dcebd u0 com.oynix.launch.mode.sample/.MainActivity t25}, ActivityRecord{907f0e u0 com.oynix.launch.mode.sample/.AActivity t25}]
askedCompatMode=false inRecents=true isAvailable=true
mRootProcess=ProcessRecord{26080b8d0 21734:com.oynix.launch.mode.sample/u0a208}
stackId=7
hasBeenVisible=true mResizeMode=RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION mSupportsPictureInPicture=false isResizeable=true lastActiveTime=6697777 (inactive for 3s)
isLaunchedPairApp=false
mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mWindowingMode=undefined mActivityType=undefined} mkbd/? desktop/?showBtnShape = -1 themeSeq=0 0 0 ?dc}
dexBoundsPolicy:
mLastNonFullBoundsDisplayId=-1
* Hist #1: ActivityRecord{907f0e u0 com.oynix.launch.mode.sample/.AActivity t25}
packageName=com.oynix.launch.mode.sample processName=com.oynix.launch.mode.sample
launchedFromUid=10208 launchedFromPackage=com.oynix.launch.mode.sample userId=0
app=ProcessRecord{26080b8d0 21734:com.oynix.launch.mode.sample/u0a208}
Intent { cmp=com.oynix.launch.mode.sample/.AActivity }
frontOfTask=false task=TaskRecord{3e1780c #25 A=com.oynix.launch.mode.sample U=0 StackId=7 sz=2}
taskAffinity=com.oynix.launch.mode.sample
realActivity=com.oynix.launch.mode.sample/.AActivity
baseDir=/data/app/com.oynix.launch.mode.sample-3DMFUnGXrp7XpBKiFV3-Tg==/base.apk
dataDir=/data/user/0/com.oynix.launch.mode.sample
stateNotNeeded=false componentSpecified=true mActivityType=standard
compat={480dpi} labelRes=0x7f0e001b icon=0x7f0c0000 theme=0x7f0f01a1
mLastReportedConfigurations:
mGlobalConfig={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=undefined} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
mOverrideConfig={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2076) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=standard} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
CurrentConfiguration={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2076) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=standard} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
taskDescription: label="null" icon=null iconResource=0 iconFilename=null primaryColor=ff6200ee
backgroundColor=ffffffff
statusBarColor=ff3700b3
navigationBarColor=fff2f2f2
launchFailed=false launchCount=1 lastLaunchTime=-5s311ms
haveState=false icicle=null
state=RESUMED stopped=false delayedResume=false finishing=false
keysPaused=false inHistory=true visible=true sleeping=false idle=true mStartingWindowState=STARTING_WINDOW_NOT_SHOWN
fullscreen=true noDisplay=false immersive=false launchMode=0
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=standard
vrActivityType=0
waitingVisible=false nowVisible=true lastVisibleTime=-4s856ms
resizeMode=RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION
mLastReportedMultiWindowMode=false mLastReportedPictureInPictureMode=false
mUseDeviceDefaultTheme=false
* Hist #0: ActivityRecord{a1dcebd u0 com.oynix.launch.mode.sample/.MainActivity t25}
packageName=com.oynix.launch.mode.sample processName=com.oynix.launch.mode.sample
launchedFromUid=2000 launchedFromPackage=null userId=0
app=ProcessRecord{26080b8d0 21734:com.oynix.launch.mode.sample/u0a208}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.oynix.launch.mode.sample/.MainActivity }
frontOfTask=true task=TaskRecord{3e1780c #25 A=com.oynix.launch.mode.sample U=0 StackId=7 sz=2}
taskAffinity=com.oynix.launch.mode.sample
realActivity=com.oynix.launch.mode.sample/.MainActivity
baseDir=/data/app/com.oynix.launch.mode.sample-3DMFUnGXrp7XpBKiFV3-Tg==/base.apk
dataDir=/data/user/0/com.oynix.launch.mode.sample
stateNotNeeded=false componentSpecified=true mActivityType=standard
compat={480dpi} labelRes=0x7f0e001b icon=0x7f0c0000 theme=0x7f0f01a1
mLastReportedConfigurations:
mGlobalConfig={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=undefined} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
mOverrideConfig={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2076) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=standard} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
CurrentConfiguration={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2076) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=standard} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
taskDescription: label="null" icon=null iconResource=0 iconFilename=null primaryColor=ff6200ee
backgroundColor=ffffffff
statusBarColor=ff3700b3
navigationBarColor=fff2f2f2
launchFailed=false launchCount=0 lastLaunchTime=-24s316ms
haveState=true icicle=Bundle[mParcelledData.dataSize=2448]
state=STOPPED stopped=true delayedResume=false finishing=false
keysPaused=false inHistory=true visible=false sleeping=false idle=true mStartingWindowState=STARTING_WINDOW_REMOVED
fullscreen=true noDisplay=false immersive=false launchMode=0
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=standard
vrActivityType=0
waitingVisible=false nowVisible=false lastVisibleTime=-6s410ms
resizeMode=RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION
mLastReportedMultiWindowMode=false mLastReportedPictureInPictureMode=false
mUseDeviceDefaultTheme=false

Running activities (most recent first):
TaskRecord{3e1780c #25 A=com.oynix.launch.mode.sample U=0 StackId=7 sz=2}
Run #1: ActivityRecord{907f0e u0 com.oynix.launch.mode.sample/.AActivity t25}
Run #0: ActivityRecord{a1dcebd u0 com.oynix.launch.mode.sample/.MainActivity t25}

mResumedActivity: ActivityRecord{907f0e u0 com.oynix.launch.mode.sample/.AActivity t25}
mLastPausedActivity: ActivityRecord{a1dcebd u0 com.oynix.launch.mode.sample/.MainActivity t25}

Stack #0: type=home mode=fullscreen
isSleeping=false
mBounds=Rect(0, 0 - 0, 0)

Task id #18
mBounds=Rect(0, 0 - 0, 0)
mMinWidth=-1
mMinHeight=-1
mLastNonFullscreenBounds=null
* TaskRecord{13181bb #18 I=com.sec.android.app.launcher/com.android.launcher3.infra.activity.Launcher U=0 StackId=0 sz=1}
userId=0 effectiveUid=u0a96 mCallingUid=1000 mUserSetupComplete=true mCallingPackage=android
intent={act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10800100 cmp=com.sec.android.app.launcher/com.android.launcher3.infra.activity.Launcher}
origActivity=com.sec.android.app.launcher/.activities.LauncherActivity
realActivity=com.sec.android.app.launcher/com.android.launcher3.infra.activity.Launcher
autoRemoveRecents=false isPersistable=false numFullscreen=1 activityType=2
rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{ad01979 u0 com.sec.android.app.launcher/.activities.LauncherActivity t18}]
askedCompatMode=false inRecents=true isAvailable=true
stackId=0
hasBeenVisible=true mResizeMode=RESIZE_MODE_RESIZEABLE mSupportsPictureInPicture=false isResizeable=true lastActiveTime=6676345 (inactive for 24s)
isLaunchedPairApp=false
mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mWindowingMode=undefined mActivityType=undefined} mkbd/? desktop/?showBtnShape = -1 themeSeq=0 0 0 ?dc}
dexBoundsPolicy:
mLastNonFullBoundsDisplayId=-1
* Hist #0: ActivityRecord{ad01979 u0 com.sec.android.app.launcher/.activities.LauncherActivity t18}
packageName=com.sec.android.app.launcher processName=com.sec.android.app.launcher
launchedFromUid=0 launchedFromPackage=null userId=0
app=ProcessRecord{95a823d0 5418:com.sec.android.app.launcher/u0a96}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10800100 cmp=com.sec.android.app.launcher/.activities.LauncherActivity }
frontOfTask=true task=TaskRecord{13181bb #18 I=com.sec.android.app.launcher/com.android.launcher3.infra.activity.Launcher U=0 StackId=0 sz=1}
taskAffinity=null
realActivity=com.sec.android.app.launcher/com.android.launcher3.infra.activity.Launcher
baseDir=/system/priv-app/TouchWizHome_2017/TouchWizHome_2017.apk
dataDir=/data/user/0/com.sec.android.app.launcher
stateNotNeeded=true componentSpecified=false mActivityType=home
compat={480dpi} labelRes=0x7f090042 icon=0x7f020127 theme=0x7f0e019c
mLastReportedConfigurations:
mGlobalConfig={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=undefined} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
mOverrideConfig={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2076) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=home} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
CurrentConfiguration={0.9 ?mcc?mnc [zh_CN_#Hans] ldltr sw360dp w360dp h668dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2076) mAppBounds=Rect(0, 0 - 1080, 2076) mWindowingMode=fullscreen mActivityType=home} s.9 mkbd/h desktop/dshowBtnShape = 0 themeSeq=0 0 0 ?dc}
OverrideConfiguration={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mWindowingMode=undefined mActivityType=home} mkbd/? desktop/?showBtnShape = -1 themeSeq=0 0 0 ?dc}
taskDescription: label="null" icon=null iconResource=0 iconFilename=null primaryColor=ff0074d4
backgroundColor=fffafafa
statusBarColor=0
navigationBarColor=0
launchFailed=false launchCount=0 lastLaunchTime=-1h51m21s986ms
haveState=true icicle=Bundle[mParcelledData.dataSize=14056]
state=STOPPED stopped=true delayedResume=false finishing=false
keysPaused=false inHistory=true visible=false sleeping=false idle=true mStartingWindowState=STARTING_WINDOW_NOT_SHOWN
fullscreen=true noDisplay=false immersive=false launchMode=2
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=home
vrActivityType=0
waitingVisible=false nowVisible=false lastVisibleTime=-1m33s752ms
connections=[ConnectionRecord{108b50e u0 CR com.samsung.android.app.spage/.service.overlay.PageOverlayService:@e38ef09}]
resizeMode=RESIZE_MODE_RESIZEABLE
mLastReportedMultiWindowMode=false mLastReportedPictureInPictureMode=false
mUseDeviceDefaultTheme=true

Running activities (most recent first):
TaskRecord{13181bb #18 I=com.sec.android.app.launcher/com.android.launcher3.infra.activity.Launcher U=0 StackId=0 sz=1}
Run #0: ActivityRecord{ad01979 u0 com.sec.android.app.launcher/.activities.LauncherActivity t18}

mLastPausedActivity: ActivityRecord{ad01979 u0 com.sec.android.app.launcher/.activities.LauncherActivity t18}

ResumedActivity: ActivityRecord{907f0e u0 com.oynix.launch.mode.sample/.AActivity t25}
mFocusedStack=ActivityStack{9f0e91 stackId=7 type=standard mode=fullscreen visible=true translucent=false, 1 tasks} mLastFocusedStack=ActivityStack{9f0e91 stackId=7 type=standard mode=fullscreen visible=true translucent=false, 1 tasks}
mCurTaskIdForUser={0=25}
mUserStackInFront={}
displayId=0 stacks=2
mHomeStack=ActivityStack{364f85f stackId=0 type=home mode=fullscreen visible=false translucent=true, 1 tasks}
mDefaultMinSizeOfResizeableTask=660
isHomeRecentsComponent=true mDefaultMinSizeOfResizeableTask=660 KeyguardController:
mKeyguardShowing=false
mAodShowing=false
mKeyguardGoingAway=true
mOccluded=false
mDismissingKeyguardActivity=null
mDismissalRequested=false
mVisibilityTransactionDepth=0
mDexOccluded=false
mDexDisplaySleepToken=null
LockTaskController
mLockTaskModeState=NONE
mLockTaskModeTasks=
mLockTaskPackages (userId:packages)=
u0:[]

几个概念

在此之前,做一些准备工作,有几个名词需要先解释。

  • Process 进程
    如果没有特殊的配置,启动一个application就会启动一个进程,进程名称默认为包名。进程之间相互独立,内存互不共享。一个进程内可以有多个线程,这些线程的内存是共享的,所以一个变量可以同时被多个线程访问,从而导致的线程不安全问题。
    AndroidManifest.xml文件中声明四大组件时,如果设置了android:process属性,这个就不会运行在applicaton的所在的进程,而是另起一个新的进程。进程的名字就是android:process属性的值。

  • Task 任务
    可以简单的把Task理解成一个存放Activity的栈,一个Activity必须依附于一个Task,启动Activity就是将Activity实例压入栈顶,销毁Activity就是将Activity实例出栈的过程。Activity虽然依附于Task,但并不是随便一个Task就可以,每个Task都有一个name,Activity就是根据这个名字找到自己的归宿。意思就是说,会同时存在多个Task,从上面dumpsys的结果也可以看出这一点,其中的TaskRecord便是一个Task实例。Task的名字是可以重复的,这与其中包含的Activity的模式有关,下面再说。

  • android:taskAffinity
    这是AndroidManifest.xml文件中,activity标签的一个属性,字符串类型。affinity,直译过来就是密切关系、类同的意思,在这里就是指定Task的名字。application也有这个属性,当activity不设置这个属性时,默认会使用application这个属性的值;如果application也没有设置这个属性的值,那就默认使用包名。所以,不设置这个属性时,activity启动后都会依附在一个名字为包名的Task中,从dumpsys结果也可以看出来。

模式:standard

这个是默认的模式,不主动设置的Activity,都是这个模式。这个模式很好理解,只要设置成这个模式,它从生到死都会存在包名为名称的Task中,即便是设置了taskAffinify也不会生效。每次启动,都会创建新的实例压入Task的栈顶。

模式:singleTop

从名字来看,就是单独的顶部。意思就是,在Task栈顶只会有一个实例。启动时,如果发现栈顶就是这个activity的实例,那么就会通过重用这个实例,这个时候会掉用Activity中的onNewIntent方法。这个模式也对taskAffinify属性无效,只会存在于包名的Task中。常用于内容详情,如新闻详情,单独占一个页面,浏览完详情,底部会显示几个其他新闻的链接,点击之后,会再次启动新闻详情,自己跳自己,这样不管跳转了多少次,按一次返回,就可以回到新闻列表页面。

模式:singleTask

如果上面解释的几个概念都能明白,那这个属性就很好说了。顾名思义,在Task中只会存在一个实例。这个模式下,taskAnffinity属性会生效,如果不设置,那就是包名。启动Activity时,首先会按照taskAnffinity查找对应的Task,如果没找到,那么就新建一个,然后将Activity实例压入栈;如果找到了该名字的Task,那么会在该Task中查找Activity的实例,若找到,如果不在Task栈顶,那么就将上面的Activity实例全部出栈,使其位于栈顶;如果栈中无此Activity的实例,则新建实例并压入栈。

模式:singleInstance

这个也很好理解,凡是以这个模式启动的Activity,都会自己独占一个Task。如果不存在则创建新Task然后实例压入栈,如果存在则onNewIntent复用其中的Activity实例。如果不设置taskAffinity,那么Task的名字就是包名,这时,就会有两个名字都是包名的Task,所以Task的名字是可能会相同的。这里强调一下独占这个词,如果一个以singleTask启动的Activity,找到了它目标名字的Task,但是这个Task里包含singleInstance启动的Activity实例,那么它会将实例压入该栈吗?不会的,因为是独占,所以不允许其他实例。

总结

其中3个都很好理解,standard是默认模式,只要启动就会创建新实例并压入默认Task;singleTop是栈顶存在就复用,不存在就创建;singleInstance是独占一个Task;singleTask是先找到目标名字、且没有被独占的Task,找不到则新建,Task中若存在实例则通过弹出上面的实例使其处于栈顶并onNewIntent复用,已在栈顶则直接复用,若Task栈中不存在实例,则创建并压入栈。

如果能自己把每个模式验证,那么理解的就会深刻些。我便是跑了一个Application,写了几个简单的Activity,通过不断调整AndroidManifest.xml中Activity的launchModetaskAnffinity,然后配合adb shell dumpsys activity activities命令,从头到尾走了一遍。

附上Git地址:https://github.com/oynix/launch-mode-sample

------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2021/08/765699b0dec3/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道