一、Activity任务栈的存在形态
通过命令adb shell dumpsys activity activities查看存在的任务栈信息如下:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #30:
Task id #338
* TaskRecord{57d640 #338 A=com.didi.taskstack U=0 sz=4}
userId=0 effectiveUid=u0a113 mCallingUid=2000 mCallingPackage=null
affinity=com.didi.taskstack
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.didi.taskstack/.MainActivity}
realActivity=com.didi.taskstack/.MainActivity
autoRemoveRecents=false isPersistable=true numFullscreen=4 taskType=0 mTaskToReturnTo=0
rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{50bedc3 u0 com.didi.taskstack/.MainActivity t338}, ActivityRecord{489b3da u0 com.didi.taskstack/.StandardActivity t338}, ActivityRecord{8b6c160 u0 com.didi.taskstack/.MainActivity t338}, ActivityRecord{3d149db u0 com.android.contacts/.activities.ContactSelectionActivity t338}]
askedCompatMode=false inRecents=true isAvailable=true
lastThumbnail=null lastThumbnailFile=/data/system/recent_images/338_task_thumbnail.png
stackId=30
hasBeenVisible=true mResizeable=false firstActiveTime=1472561978634 lastActiveTime=1472561978634 (inactive for 5s)
otherMwTaskId=0
* Hist #3: ActivityRecord{3d149db u0 com.android.contacts/.activities.ContactSelectionActivity t338}
packageName=com.android.contacts processName=com.android.contacts
launchedFromUid=10113 launchedFromPackage=com.didi.taskstack userId=0
app=ProcessRecord{8f9ee9f 27008:com.android.contacts/u0a3}
Intent { act=android.intent.action.PICK dat=content://contacts/people cmp=com.android.contacts/.activities.ContactSelectionActivity }
frontOfTask=false task=TaskRecord{57d640 #338 A=com.didi.taskstack U=0 sz=4}
taskAffinity=android.task.contacts
realActivity=com.android.contacts/.activities.ContactSelectionActivity
baseDir=/system/priv-app/Contacts/Contacts.apk
dataDir=/data/user/0/com.android.contacts
stateNotNeeded=false componentSpecified=false mActivityType=0
compat={480dpi} labelRes=0x7f0b0107 icon=0x7f030000 theme=0x2060008
config={1.0 ?mcc?mnc zh_CN ldltr sw360dp w360dp h580dp 480dpi nrml port finger -keyb/v/h -nav/h suim:1 s.10}
stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}
taskDescription: iconFilename=null label=”null” color=fff0f0f0
launchFailed=false launchCount=1 lastLaunchTime=-5s422ms
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=1
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=APPLICATION_ACTIVITY_TYPE
waitingVisible=false nowVisible=true lastVisibleTime=-4s890ms
* Hist #2: ActivityRecord{8b6c160 u0 com.didi.taskstack/.MainActivity t338}
packageName=com.didi.taskstack processName=com.didi.taskstack
launchedFromUid=10113 launchedFromPackage=com.didi.taskstack userId=0
app=ProcessRecord{362d717 29425:com.didi.taskstack/u0a113}
Intent { flg=0x10400000 cmp=com.didi.taskstack/.MainActivity }
frontOfTask=false task=TaskRecord{57d640 #338 A=com.didi.taskstack U=0 sz=4}
taskAffinity=com.didi.taskstack
realActivity=com.didi.taskstack/.MainActivity
baseDir=/data/app/com.didi.taskstack-1/base.apk
dataDir=/data/user/0/com.didi.taskstack
stateNotNeeded=false componentSpecified=true mActivityType=0
compat={480dpi} labelRes=0x7f060015 icon=0x7f030000 theme=0x7f090037
config={1.0 ?mcc?mnc zh_CN ldltr sw360dp w360dp h580dp 480dpi nrml port finger -keyb/v/h -nav/h suim:1 s.10}
stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}
taskDescription: iconFilename=null label=”null” color=ff3f51b5
launchFailed=false launchCount=0 lastLaunchTime=-8s367ms
haveState=true icicle=Bundle[mParcelledData.dataSize=620]
state=STOPPED stopped=true delayedResume=false finishing=false
keysPaused=false inHistory=true visible=false sleeping=false idle=true
fullscreen=true noDisplay=false immersive=false launchMode=0
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=APPLICATION_ACTIVITY_TYPE
waitingVisible=false nowVisible=false lastVisibleTime=-7s999ms
* Hist #1: ActivityRecord{489b3da u0 com.didi.taskstack/.StandardActivity t338}
packageName=com.didi.taskstack processName=com.didi.taskstack
launchedFromUid=10113 launchedFromPackage=com.didi.taskstack userId=0
app=ProcessRecord{362d717 29425:com.didi.taskstack/u0a113}
Intent { cmp=com.didi.taskstack/.StandardActivity }
frontOfTask=false task=TaskRecord{57d640 #338 A=com.didi.taskstack U=0 sz=4}
taskAffinity=com.didi.taskstack
realActivity=com.didi.taskstack/.StandardActivity
baseDir=/data/app/com.didi.taskstack-1/base.apk
dataDir=/data/user/0/com.didi.taskstack
stateNotNeeded=false componentSpecified=true mActivityType=0
compat={480dpi} labelRes=0x7f060015 icon=0x7f030000 theme=0x7f09008d
config={1.0 ?mcc?mnc zh_CN ldltr sw360dp w360dp h580dp 480dpi nrml port finger -keyb/v/h -nav/h suim:1 s.10}
stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}
taskDescription: iconFilename=null label=”null” color=ff3f51b5
launchFailed=false launchCount=0 lastLaunchTime=-16s449ms
haveState=true icicle=Bundle[mParcelledData.dataSize=620]
state=STOPPED stopped=true delayedResume=false finishing=false
keysPaused=false inHistory=true visible=false sleeping=false idle=true
fullscreen=true noDisplay=false immersive=false launchMode=0
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=APPLICATION_ACTIVITY_TYPE
waitingVisible=false nowVisible=false lastVisibleTime=-15s961ms
* Hist #0: ActivityRecord{50bedc3 u0 com.didi.taskstack/.MainActivity t338}
packageName=com.didi.taskstack processName=com.didi.taskstack
launchedFromUid=2000 launchedFromPackage=null userId=0
app=ProcessRecord{362d717 29425:com.didi.taskstack/u0a113}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.didi.taskstack/.MainActivity }
frontOfTask=true task=TaskRecord{57d640 #338 A=com.didi.taskstack U=0 sz=4}
taskAffinity=com.didi.taskstack
realActivity=com.didi.taskstack/.MainActivity
baseDir=/data/app/com.didi.taskstack-1/base.apk
dataDir=/data/user/0/com.didi.taskstack
stateNotNeeded=false componentSpecified=true mActivityType=0
compat={480dpi} labelRes=0x7f060015 icon=0x7f030000 theme=0x7f090037
config={1.0 ?mcc?mnc zh_CN ldltr sw360dp w360dp h580dp 480dpi nrml port finger -keyb/v/h -nav/h suim:1 s.10}
stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}
taskDescription: iconFilename=null label=”null” color=ff3f51b5
launchFailed=false launchCount=0 lastLaunchTime=-26s659ms
haveState=true icicle=Bundle[mParcelledData.dataSize=620]
state=STOPPED stopped=true delayedResume=false finishing=false
keysPaused=false inHistory=true visible=false sleeping=false idle=true
fullscreen=true noDisplay=false immersive=false launchMode=0
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=APPLICATION_ACTIVITY_TYPE
waitingVisible=false nowVisible=false lastVisibleTime=-26s336ms
Task id #340
* TaskRecord{5a660a9 #340 A=com.didi.taskstack U=0 sz=1}
userId=0 effectiveUid=u0a113 mCallingUid=u0a113 mCallingPackage=com.didi.taskstack
affinity=com.didi.taskstack
intent={flg=0x10000000 cmp=com.didi.taskstack/.SingleInstanceActivity}
realActivity=com.didi.taskstack/.SingleInstanceActivity
autoRemoveRecents=false isPersistable=true numFullscreen=1 taskType=0 mTaskToReturnTo=0
rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{9f7ec30 u0 com.didi.taskstack/.SingleInstanceActivity t340}]
askedCompatMode=false inRecents=false isAvailable=true
lastThumbnail=null lastThumbnailFile=/data/system/recent_images/340_task_thumbnail.png
stackId=30
hasBeenVisible=true mResizeable=false firstActiveTime=1472561975670 lastActiveTime=1472561975670 (inactive for 8s)
otherMwTaskId=0
* Hist #0: ActivityRecord{9f7ec30 u0 com.didi.taskstack/.SingleInstanceActivity t340}
packageName=com.didi.taskstack processName=com.didi.taskstack
launchedFromUid=10113 launchedFromPackage=com.didi.taskstack userId=0
app=ProcessRecord{362d717 29425:com.didi.taskstack/u0a113}
Intent { flg=0x10000000 cmp=com.didi.taskstack/.SingleInstanceActivity }
frontOfTask=true task=TaskRecord{5a660a9 #340 A=com.didi.taskstack U=0 sz=1}
taskAffinity=com.didi.taskstack
realActivity=com.didi.taskstack/.SingleInstanceActivity
baseDir=/data/app/com.didi.taskstack-1/base.apk
dataDir=/data/user/0/com.didi.taskstack
stateNotNeeded=false componentSpecified=true mActivityType=0
compat={480dpi} labelRes=0x7f060015 icon=0x7f030000 theme=0x7f09008d
config={1.0 ?mcc?mnc zh_CN ldltr sw360dp w360dp h580dp 480dpi nrml port finger -keyb/v/h -nav/h suim:1 s.10}
stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}
taskDescription: iconFilename=null label=”null” color=ff3f51b5
launchFailed=false launchCount=0 lastLaunchTime=-10s384ms
haveState=true icicle=Bundle[mParcelledData.dataSize=620]
state=STOPPED stopped=true delayedResume=false finishing=false
keysPaused=false inHistory=true visible=false sleeping=false idle=true
fullscreen=true noDisplay=false immersive=false launchMode=3
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=APPLICATION_ACTIVITY_TYPE
waitingVisible=false nowVisible=false lastVisibleTime=-10s57ms
Task id #339
* TaskRecord{4c69583 #339 A=com.didi.qiaoyf U=0 sz=1}
userId=0 effectiveUid=u0a113 mCallingUid=u0a113 mCallingPackage=com.didi.taskstack
affinity=com.didi.qiaoyf
intent={flg=0x10000000 cmp=com.didi.taskstack/.TaskAffinityActivity}
realActivity=com.didi.taskstack/.TaskAffinityActivity
autoRemoveRecents=false isPersistable=true numFullscreen=1 taskType=0 mTaskToReturnTo=0
rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{fbae432 u0 com.didi.taskstack/.TaskAffinityActivity t339}]
askedCompatMode=false inRecents=true isAvailable=true
lastThumbnail=android.graphics.Bitmap@17d1953 lastThumbnailFile=/data/system/recent_images/339_task_thumbnail.png
stackId=30
hasBeenVisible=true mResizeable=false firstActiveTime=1472561973651 lastActiveTime=1472561973651 (inactive for 10s)
otherMwTaskId=0
* Hist #0: ActivityRecord{fbae432 u0 com.didi.taskstack/.TaskAffinityActivity t339}
packageName=com.didi.taskstack processName=com.didi.taskstack
launchedFromUid=10113 launchedFromPackage=com.didi.taskstack userId=0
app=ProcessRecord{362d717 29425:com.didi.taskstack/u0a113}
Intent { flg=0x10000000 cmp=com.didi.taskstack/.TaskAffinityActivity }
frontOfTask=true task=TaskRecord{4c69583 #339 A=com.didi.qiaoyf U=0 sz=1}
taskAffinity=com.didi.qiaoyf
realActivity=com.didi.taskstack/.TaskAffinityActivity
baseDir=/data/app/com.didi.taskstack-1/base.apk
dataDir=/data/user/0/com.didi.taskstack
stateNotNeeded=false componentSpecified=true mActivityType=0
compat={480dpi} labelRes=0x7f060015 icon=0x7f030000 theme=0x7f09008d
config={1.0 ?mcc?mnc zh_CN ldltr sw360dp w360dp h580dp 480dpi nrml port finger -keyb/v/h -nav/h suim:1 s.10}
stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}
taskDescription: iconFilename=null label=”null” color=ff3f51b5
launchFailed=false launchCount=0 lastLaunchTime=-13s481ms
haveState=true icicle=Bundle[mParcelledData.dataSize=620]
state=STOPPED stopped=true delayedResume=false finishing=false
keysPaused=false inHistory=true visible=false sleeping=false idle=true
fullscreen=true noDisplay=false immersive=false launchMode=0
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=APPLICATION_ACTIVITY_TYPE
waitingVisible=false nowVisible=false lastVisibleTime=-13s149ms
从上面可以看到任务栈和Activity启动配置的很多信息,这里我们重点看一下任务栈的Activity实例和压入顺序,taskAffinity,launchMode(0:Standard,1:singleTop,2:singleTask,3:singleInstance),processName.
应用程序为com.didi.taskstack的app运行过程中,创建了三个任务栈Task id #338,Task id #340,Task id #339。
Task id #338:Activities=[ActivityRecord{50bedc3 u0 com.didi.taskstack/.MainActivity t338}, ActivityRecord{489b3da u0 com.didi.taskstack/.StandardActivity t338}, ActivityRecord{8b6c160 u0 com.didi.taskstack/.MainActivity t338}, ActivityRecord{3d149db u0 com.android.contacts/.activities.ContactSelectionActivity t338}]
其中ContactSelectionActivity为系统选择联系人页面,launchMode为singleTop,taskAffinity为android.task.contacts,进程processName=com.android.contacts,其他三个均为应用自身的Activity,taskAffinity为应用自身默认的包名。
Task id #340:Activities=[ActivityRecord{9f7ec30 u0 com.didi.taskstack/.SingleInstanceActivity t340}]
SingleInstanceActivity launch mode为singleIntance,单独为其创建一个task,但是taskAffinity为应用自身默认的包名。
Task id #339:Activities=[ActivityRecord{fbae432 u0 com.didi.taskstack/.TaskAffinityActivity t339}]
TaskAffinityActivity为应用自身的Activity,修改TaskAffinityActivity的taskAffinity为com.didi.qiaoyf,创建了一个taskAffinity为com.didi.qiaoyf的任务栈。
由以上分析可知,一个应用至少有一个默认taskAffinity的任务栈,可以通过singleInstance或者修改Activity的taskAffinity来创建不同的任务栈,同属一个任务栈的Actvity也可能运行在不同的进程。从存在形态来讲,任务是指在执行特定作业时与用户交互的一系列 Activity, 这些 Activity 按照各自的打开顺序排列在堆栈(即“返回栈”)中。
二、Activity任务栈的算法分析
Activity任务栈,从数据结构上来讲,可以理解为满足特定push算法的,存放Activity信息的栈。主要算法描述如下:
假定由Context A startActivity到 Activity B。
当Context A 是Activity:
每个Activity都有个taskAffinity,默认为应用程序的包名,也可以通过android:taskAffinity设定,故分为以下两种情况:
1. Activity A 和 Activity B taskAffinity相同
情形1:A的launchMode 不为singleInstance时
(1)standard,标准模式,每次startActivity都会创建一个新的实例,压入A所在任务栈中;
(2)singleTop,栈顶复用模式,每次startActivity都会先检查A所在任务栈栈顶是否存在一个该Activity一个的实例,如果有则不会创建新实例,如果没有才会创建一个新的实例压入栈中;
(3)singleTask,每次startactivity都会先检查A所在任务栈栈中是否存在一个该Activity一个的实例,如果有则弹出栈中在该activity实例之上的所有activity实例,复用栈中的该Activity实例,如果没有才会创建一个新的实例压入栈中;
(4)singleInstance,与 “singleTask” 相同,如果存在则复用该任务栈,如果没有,系统会为他单独创建一个任务栈,且任何其他 Activity 实例都不会压入该栈中,该 Activity 始终是其任务唯一仅有的成员,该栈的affinity与A的任务栈一致。
情形2:A的launchMode 为singleInstance时
压入算法与上述保持一致,但由于A的任务栈有且只能由Activity A,因此Activity B只能寻找affinity与A一致且不是独占的任务栈。
2. Activity A 和 Activity B taskAffinity不相同
I. A的launchMode 不为singleInstance时
情形3:启动 Activity B的 Intent 没有包含 FLAG_ACTIVITY_NEW_TASK 标志并且Activity B的launchMode 不是singleTask,singleInstance。
(1)standard,标准模式,每次startActivity都会创建一个新的实例,压入A所在任务栈中。
(2)singleTop,栈顶复用模式,每次startActivity都会先检查A所在任务栈栈顶是否存在一个该Activity一个的实例,如果有则不会创建新实例,如果没有才会创建一个新的实例压入栈中
情形4:启动 Activity B的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志或者Activity B的launchMode 是singleTask或者singleInstance。
(1)standard,标准模式,每次startActivity都会创建一个新的实例,
然后寻找affinity与B一致且不是独占的任务栈,如果没有则创建新任务栈并压入该栈中。
(2)singleTop,栈顶复用模式,每次startActivity都会先寻找affinity与B一致且不是独占的任务栈,如果没有则创建新任务栈,然后检查该任务栈栈顶是否存在一个该Activity的实例,如果有则不会创建新实例,如果没有才会创建一个新的实例压入栈中
(3)singleTask,每次startActivity都会先寻找affinity与B一致且不是独占的任务栈,如果没有则创建新任务栈,然后检查该任务栈栈中是否存在一个该Activity一个的实例,如果有则弹出栈中在该Activity实例之上的所有Activity实例,复用栈中的该Activity实例,如果没有才会创建一个新的实例压入栈中。
(4)singleInstance,与 “singleTask” 相同,如果存在则复用该任务栈,如果没有,则系统会为他单独创建一个affinity与B一致的任务栈,且任何其他 Activity 实例都不会压入该栈中。
II. A的launchMode 为singleInstance时
压入栈策略与情形4一致。
当Context A 不是Activity:
只能通过包含 FLAG_ACTIVITY_NEW_TASK 标志的Intent来启动Activity B,压入栈策略与情形4一致。