进程介绍
先强化两个进程相关的概念
-
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作系统动态执行的基本单元,是系统进行资源分配的基本单位,在面向线程设计的计算机结构中是线程的容器。
-
fork:fork函数UNIX及类UNIX系统中的分叉函数,一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
Android进程机制
下面介绍一下android的进程机制,Android进程实际上是Linux(类UNIX系统)进程,在Linux中所有的进程都是init进程的子孙进程,也就是说,所有的进程都是直接或者间接地由init进程fork出来的。
在android系统中也有一个初始进程:Zygote进程,是在系统启动的过程,由init进程创建的,启动Zygote之后, init进程会启动runtime进程。Zygote进程负责后续Android应用程序框架层的其它进程的创建和启动工作。 Zygote进程初始化了第一个VM, 并且预加载了framework和App所需要的通用资源。它会创建一个超级管理进程SystemServer进程,SystemServer进程会启动所有系统的核心服务,如包管理服务PackageManagerService和应用程序组件管理服务ActivityManagerService,硬件相关的Service等。然后它开启一个Socket接口来监听请求, 根据请求fork新的VM来管理新的App进程. 一旦收到新的请求, Zygote会基于自身预先加载的VM来fork一个新的VM创建一个新的进程。 需要启动一个Android应用程序时,ActivityManagerService会通过Socket进程间通信机制,通知Zygote进程为这个应用程序创建一个新的进程。
具体启动细节:每一个App应用都是由ActivityManagerService通过Socket与Zygote进程进行通信,ActivityManagerService调用startProcessLocked()方法来创建新的进程, 该方法会通过前面讲到的socket通道传递参数给Zygote进程。Zygote会fork一个子进程出来作为这个即将要启动的应用程序的进程, 并调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid。 接下来要做的就是将进程和指定的Application绑定起来. 这个是通过ActivityThread对象中调用bindApplication()方法完成的. 该方法发送一个BINDAPPLICATION的消息到消息队列中, 最终通过handleBindApplication()方法处理该消息. 然后调用makeApplication()方法来加载App的classes到内存中。 经过前两个步骤之后, 系统已经拥有了该application的进程。后面的调用顺序就是普通的从一个已经存在的进程中启动一个新进程的Activity了。ActivityManagerService会通过Binder机制通知ActivityThread去创建需要的Activity,实际调用方法是realStartActivity(), 它会调用application线程对象中的sheduleLaunchActivity()发送一个LAUNCHACTIVITY消息到消息队列中, 通过handleLaunchActivity()来处理该消息。最后会辗转到Instrumentation来创建Activity。
影响应用进程的因素
正常情况下一个android应用只有一个进程,进程名就是应用包名,android的application和四大组件都是在这个进程。但可分别指定Application和四大组件运行的进程,通过以下方式创建独立进程:
-
android:process=":name" 创建私有进程,进程名包名+":name"
-
android:process="com.cxzl.name" 创建公共进程,进程名com.cxzl.name
组件创建独立进程通常用来需要长期运行不被系统回收的功能,比如push功能。
除了android:process外,android:multiprocess也能够影响进程创建,multiprocess顾名思义就是多进程的意思,这个参数只能在Activity和ContentProvider中定义。默认是false,组件只会创建在申明它的app所在的进程。multiprocess="true"允许组件创建多个实例,在哪个进程中调用组件就会使用该进程中的组件实例,并不会共用。
除了上述两个直接影响进程创建的配置外,还有一个配置能够间接的影响到进程的创建,它就是android:launchMode="singleInstance",Android的Activity有四种启动方式,除了singleInstance以外的三种全部都是在同一个Activity栈里面,唯独singeleInstance是创建独立的栈,而且只有一份实例,这就导致它能够影响到进程创建:因为它与android:multiprocess="true"这个属性相冲突。既然它是在独立的栈,那么两个应用打开它,它属于哪个应用的栈?哪个应用的进程?如果组件指定了自己的进程,又会是什么样的情况?
带着这些疑问我在网上搜了一下,没有找到完整的资料,有的文章会根据某个点有一些分析,但不全面,而且不同文章观点有冲突,对android:process,android:multiprocess,android:launchMode三个配置对一个组件所处的进程的影响并没有清晰结论。查看官方文档,也只是单独解释这个三个配置的功能,并没有说这些情况交叉在一起的影响。
进程测试
所以我写了两个Demo来测试应用中多进程的情况. 一个主应用(github地址https://github.com/cxzl/ProcessTestMain),包名com.cxzl.processtest,包含了以下内容:
-
MainActivity类,启动各个具体配置的Activity;
-
要启动的Activity基类,用来打印进程的pid,进程名和Activity的hashcode;
-
在AndroidManifest配置出来的各个Activity,全部继承第二条的基类。
一个辅助应用(github地址https://github.com/cxzl/ProcessTestAssist),包名com.cxzl.processthirdstart,只包含一个类,就是MainActivity,用来启动主应用AndroidManifest配置出来的各个Activity。 用这两个应用单独和交叉启动各种配置的具体Activity,来比较各种因素会对进程创建的影响。测试界面选取了几张截图:
下面的表格是具体的测试结果:
先解释一下这个表格:
最左边一行是对Activity三种配置的数字编码,因为用文字列举这12种情况太复杂了,所以改成数字编码。
-
第一个数字是android:launchMode,0是默认情况,也就是不设置launchMode(Android默认为standard),1是设置launchMode为singleInstance。
-
第二数字是android:multiprocess,0是默认情况,也就是false,1是true。
-
第三个数字是android:process,0是默认情况,也就是不设置process,默认process就是应用的包名com.cxzl.processtest,1是设置私有进程,进程名com.cxzl.processtest:private,2是设置公共进程,进程名com.cxzl.public。
举个例子101就是1(launchMode="singleInstance")+0(multiprocess="false")+1(process:private)
最上面一行是启动Activity的方式,分别是:
-
辅助应用未运行,主应用启动自己的Activity;
-
辅助应用已经打开了主应用的某个Activity,按home键,打开主应用再去启动这个Activity;
-
主应用未运行,辅助应用启动主应用的Activity;
-
主应用已经打开了自己的某个Activity,按home键,打开辅助应用再去启动这个Activity。
解释完了下面来总结结论:
-
辅助应用的进程com.cxzl.processthirdstart出现次数为0,打破网上一些文章说配置了multiprocess="true",哪个应用打开组件,组件就在哪个进程的流言,实际上不论怎么配置:组件只存在于本应用进程或者本应用process指定的进程。
-
所有的情况下,设置了私有进程或者公有进程的表现都同步,都是要么是主应用进程,要么是各自的进程,也就是说私有进程和公有进程在进程创建上表现是相同的,只是进程名不同。
-
所有编码0结尾的行,启动组件都是在主进程,也就是说只要process没有指定进程,一定在主进程。
-
对比B列和C列(都是启动主应用,C列多了第三方应用干扰),只有111和112不同,也就是设置launchmodle为singInstance,multiprocess为true的情况下启动组件,私有进程和公有进程会因为第三方应用先打开了这个进程,直接打开相同进程,否则在主进程中创建组件。
-
对比B列和D列(主应用启动和第三方应用启动),011,012和111,112不同,也就是multiprocess为true的情况,本应用和第三方应用打开同一个组件所在进程不同,本应用在主进程,第三方应用在组件自己声明的进程。
-
对比D列和E列(都是第三方应用启动,E列多了本应用干扰),只有111和112不同,也就是设置launchmodle为singInstance,multiprocess为true的情况下启动组件,私有进程和公有进程会因为本应用先打开了这个进程,直接打开相同进程,否则在组件指定的进程中创建组件。
-
对比001,002,101,102和011,012,111,112,会发现multiprocess为true会导致本应用打开指定公有或者私有进程的组件失效,还是出现在主进程,但是第三方应用打开不受影响。
-
对比000,001,002和100,101,102,发现是一模一样,也就是说multiprocess为false的情况下,singleInstance不会影响组件所在进程。
-
对比011,012和111,112,发现multiprocess为true的情况下,组件指定了私有或公有进程,singInstance模式下,后运行的应用会用先运行的应用创建的进程。
除了结论以外,还发现了两个问题:
-
第三方应用启动multiprocess为true的组件,不开启singleInstance的Activity连续打开了两个页面,进程号相同但是hashcode不同。这个我还没找出来原因,也没发现网上有人讨论这个问题,如果谁知道原因可以交流一下。
-
第三方应用启动singleInstance的activity,按android多任务键会出现两个应用,非singleInstance虽然创建了进程,但是不会出现在任务列表。任务列表和进程的关系是一个值得深入了解的事。
到这里这篇文章就结束了,希望能帮助大家理解android进程机制,和多应用多进程之间的进程关系。本系列第二篇文章正在准备中:android进程详解(二):进程与应用生命周期的关系和进程销毁,敬请期待。
我的微信公众号:程序之路,持续发布技术和成长文章,欢迎长按或者扫描二维码关注