一次搞定Process和Task
关于进程-Process
影响process的属性
控制组件运行进程的有两个个属性:android:process
和android:multiprocess
关于android:process
- 可分别指定Application和四大组件运行的进程名
- 不指定时,使用包名作为进程名
- 如果进程名是以冒号开头的,则这个进程是应用的私有进程
- 如果进程名是以字符开头的,且符合包名规范,则这个进程是全局的
只有provider和activity定义了android:multiprocess
,但不要在activity中使用。
如果android:multiprocess
为true,则每个访问provider的应用都会自己创建一个ContentProvider实例
- 优势:避免跨进程通信,提高数据访问效率
- 弊端:多个实例导致系统内存消耗变大,且难以处理多个进程之间的数据同步问题
如果需要两个不同APK中的组件运行于同一个进程,需要以下三个条件:
- 两个应用程序使用相同的
android:sharedUserId
- 两个应用使用相同的keystore进行签名
- 为组件设置相同的
android:process
不同Process带来的影响
跨进程通信
由于不同的进程使用不同的内存空间,所以不同进程之间的通信本质上只依赖以下四种方式
- Binder:包括Messenger、AIDL,只能传递基本数据类型或实现了Parcelable的对象,本质是数据的序列化和反序列化
- Intent:跨进程的组件调用,本质也是数据的序列化和反序列化
- ASHMEM:匿名共享内存,需要依赖Binder传递共享内存文件描述,使用系统签名的APK才能直接使用
- 其他通用方式:共享文件、网络接口等
ContentProvider的实现就是使用ASHMEM
- ContentProvider保存的数据,都是以私有文件存储的,其他进程无法访问
- 其他程序创建CursorWindow时,同时创建了一块匿名共享内存,并实现了Parcelable
- 通过Binder将CursorWindow和共享内存文件描述传递给ContentProvider
- ContentProvider创建自己的共享内存文件描述,并指向共享内存
进程优先级
内存不足时,系统可能杀掉进程,这时进程中的Application和应用组件也会随之销毁。系统如何选择停止的进程,就涉及到进程优先级了。
- 前台进程 Activte process
- 前台响应用户事件的Activity以及与之绑定的Service
- startForeground的Service
- 正在执行onStart,onCreate,OnDestroy的Service
- 正在执行onReceive的BroadcastReceiver
- 可见进程 Visible Process
- onPause但未onStop的Activity
- 绑定到可见Activity的Service
- 服务进程 Service process
- 普通Service
- 背景进程 Background process
- onStop的Activity
- 空进程 Empty process
- 不包含任何组件的进程
Process的实际使用
- 子进程可以分担主进程的内存压力
- 在主进程Crash时,子进程中的功能不受影响
- 子进程的
Application#onCreate
中可以跳过一些不必要的初始化
关于任务-Task
关于Task,有太多的参数与之相关,而且他们相互影响,感觉无法穷尽。下面仅从实际的使用出发,明确和Task相关的一些原则。
Task和startActivityForResult
使用startActivityForResult的必要条件是被启动的Activity和原Activity要在同一个Task中。因此,使用startActivityForResult时,会强制将新启动的Activity放在原来的Task中,不论activiy的xml属性和Intent#Flag_XXX
。
只有一个例外FLAG_ACTIVITY_NEW_TASK
:如果使用这个标签,原Activity会立刻收到onActivityResult,并执行和startActivity相同的逻辑。在后面的分析中可以看到,仅凭这个FLAG是无法正真启动新Task的。
多Task有关的参数
可以下载多Task相关的演示代码,修改MultiTaskActivity启动方法的Intent和Manifest中的属性,就可演示这小结的大部分例子。
launchMode
standard
和singleTop
:可以包含多个Activity实例singleInstance
和singleTask
:只有一个Activity实例singleInstance
是一个Task中唯一的ActivitysingleTask
不一定是Task root(google文档有问题,但使用中建议作为Task root使用)
taskAffinity
- 仅有
singleInstance
启动新Task不依赖taskAffinity
singleTask
的Activity只有其taskAffinity
和原Activity不一样时才会启动新Taskstandard
和singleTop
启动新Task,不仅要求新的taskAffinity
,而且需要FLAG_ACTIVITY_NEW_TASK
- 如果有后台Task和要启动的Activity具有相同的
taskAffinity
,则不会启动新的Task,而是将后台Task切换到前台,并根据其他属性和标签重新安排后台Task中的Activity和新Activity - taskAffinity的值应该是以‘.’开头的字符串
其他多Task属性和标签
只有standard
和singleTop
类型的Activity才可能出现多个实例。因此,只有这两类Activity才可能出现多个实例,并处于不同的Task中。下面的讨论,也仅限于这两类。
- FLAG_ACTIVITY_NEW_DOCUMENT
- 如果已经有Activity符合Intent,则切换到该Activity
- 否则启动新的Activity
- FLAG_ACTIVITY_MULTIPLE_TASK
- 配合FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_NEW_DOCUMENT使用,在启动时不再寻找匹配的Activity,而是直接启动新任务。
- documentLaunchMode:与前面两个标签相互限制。
- always:每次启动时都会启动新的Task。等同于FLAG_ACTIVITY_NEW_DOCUMENT和FLAG_ACTIVITY_MULTIPLE_TASK
- intoExisting:如果有符合Intent的Activity存在,则不再启动新Task,否则启动新Task。等同于FLAG_ACTIVITY_NEW_DOCUMENT,且不带FLAG_ACTIVITY_MULTIPLE_TASK
- none:默认值。由其他标签和属性决定是否启动新Task。
- never:使FLAG_ACTIVITY_NEW_DOCUMENT和FLAG_ACTIVITY_MULTIPLE_TASK失效。
FLAG_ACTIVITY_NEW_DOCUMENT和documentLaunchMode都是5.0的新特性。通过FLAG_ACTIVITY_NEW_DOCUMENT启动一个新的Task,不需要指定taskAffinity
。因此,最好不要将taskAffinity与FLAG_ACTIVITY_NEW_DOCUMENT混用。
在实测中发现,never无法使FLAG_ACTIVITY_NEW_DOCUMENT失效,只是使FLAG_ACTIVITY_MULTIPLE_TASK失效。
Task相关的其他参数
这部分的参数主要处理以下两个问题,演示代码不包括这部分的例子。
- Task在最近任务中的表现
- 一个新Activity进入已有的Task时,如何重新安排Task中的老Activity和新Activity
其它xml元素
alllowTaskReparenting
:默认false,只有standard和singleTop有效,表示Activity实例可以换到其他Task中。(通过应用图标启动程序时会生效,在程序内通过FLAG_ACTIVITY_NEW_TASK切换任务时,不生效)例子:浏览器。finishOnTaskLaunch
:默认false,通过应用图标重新启动程序时,这个Activity会被销毁。clearTaskOnLaunch
:用于Task root,默认false,启动时会清空Task上的其它Activity,只保留root。(通过应用图标启动程序时会生效,通过最近任务启动时不生效)alwaysRetainTaskState
:用于Task root,默认false,在应用切换到后台30分钟后会被系统清理。如果为true则不会被系统清理noHistory
:页面不可见时被自动销毁,不会保存在mHistory
中
其他Intent.FLAG
- FLAG_ACTIVITY_CLEAR_TOP:启动的Activity存在,则不创建新实例,而是使用原有实例,并清空上面的其它Activity。在Activity是singleTask,或者Intent中有FLAG_ACTIVITY_SINGLE_TOP,这个Activity不会重新创建。
- FLAG_ACTIVITY_REORDER_TO_FRONT:Activity会重新排序,任何Activity都不会被销毁。
- FLAG_ACTIVITY_NO_HISTORY:和xml中的
noHistory
效果相同 - FLAG_ACTIVITY_TASK_ON_HOME:后退时直接回Home,而不会回到之前的Task
Task和Process的关系
之前文章的《怎么处理SaveState》的末尾也提到的Task和Process的关系。这里重复一遍。
我们先看下再ActivityManagerService中进程Process和Task的关系
ActivityManagerService
通过一个列表mHistory
来管理所有ActivityRecord
- 相同
TaskRecord
中的ActivityRecord
在列表中处于连续位置 - 同一个
TaskRecord
中的ActivityRecord
可能处于不同的ProcessRecord
中
由于以下两个因素,使得很难找到Task和进程之间关联的清晰线索。
- 同一Task中的Activity可能属于不同进程
- 进程中不仅有Activity,还有Service和BroadcastReceiver
先看Task中Activity销毁
- 处理的问题:一个进程内部,前后台Task的资源协调
- 触发时机:进程使用的内存接近上限时(根据机型不同,大约在64M~256M之间)
- 会调用Activity#onDestroy,后台Task回到前台时会触发Activity#onRestoreInstanceState
再看进程被杀
- 处理的问题:系统控制中,多进程之间的资源协调
- 触发时机:整个系统使用的内存接近机器配置的内存上限时
- 不会调用Activity#onDestroy,后台Task回到前台时不一定会触发Activity#onRestoreInstanceState,和Task的启动方式和启动时间有关。
我们以一个简化的例子讨论两者的关系。假设:
- 单进程最大可使用内存为100M,进程使用内存超过90M时会触发后台Task销毁。
- 系统总可用内存为200M,系统使用内存超过190M时会触发后台进程被杀。
- 系统中运行着3个进程,他们在三个Task中的分布和内存使用如下
- Task1处于前台运行
Momory Usage | Process1 | Process2 | Process 3 |
---|---|---|---|
Task1 | 60M | 20M | - |
Task2 | 20M | 20M | - |
Task3 | - | - | 40M |
如果T1 P1部分消耗的内存由60M上升到75M,由于P1的总内存消耗达到95M,所以会导致P1 T2中的Activity被销毁。
如果T1 P2部分消耗的内存由20M上升到50M,会导致系统总内存消耗达到190M。此时三个Process中,P1和P2和前台Task关联,优先级较高,所以系统会杀掉P3。
这个例子,只是对两者关系的一个简要说明。系统对进程的实际处理方式要复杂得多!