Android开发——Android多进程以及使用场景介绍
0. 前言
在Android中,默认情况下,同一应用的所有组件均运行在同一进程中,且大多数应用都不会改变这一点。不过,单进程开发并不是Android应用的全部,今天我们就来说说Android中的多进程开发以及多进程的使用场景。
1. 进程
我们都知道Android系统是基于Linux改造而来的,进程系统也是一脉相承,进程其实就是程序的具体实现。当程序第一次启动,Android会启动一个Linux进程(具体由Zygote fork出来)以及一个主线程,默认的情况下,所有组件都将运行在该进程内。同一个应用由系统分配一个独立的Linux账户,该应用产生的所有进程,都会是这同一个Linux账户。
2. 使用多进程
在开发中,我们通常会使用修改清单文件的android:process来达到多进程的目的。如果android:process的value值以冒号开头的话,那么该进程就是私有进程,如果是以其他字符开头,那么就是公有进程,这样拥有相同 ShareUID 的不同应用可以跑在同一进程里。至于创建进程的具体源码分析,网上有一篇很详细的文章,在这就不重复造轮子了,有需要的朋友可以前往理解Android进程创建流程。
还有一种方法开启进程,是通过JNI利用C/C++,调用fork()方法来生成子进程,一般开发者会利用这种方法来做一些daemon进程,来实现防杀保活等效果。
3. 进程优先级
Android利用重要性层次结构,就是将最重要的保留,杀掉不重要的进程。
Android将重要性层次结构分为5个层级,具体可以查看Android开发——Android进程保活招式大全中1.1部分的内容,这里就不赘述了。
根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。
此外,一个进程的级别可能会因其他进程对它的依赖而有所提高。例如进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。
由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity仍可在后台继续执行上传操作。使用服务可以保证无论 Activity 发生什么情况该操作至少具备“服务进程”优先级。同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。
4. Low Memory Killer
Android的Low Memory Killer基于Linux的OOM机制,Low Memory Killer会根据进程的adj级别以及所占的内存,来决定是否杀掉该进程,adj越大,占用内存越多,进程越容易被杀掉。
关于adj的分级,我们可以参考ProcessList.java,这里的常量定义了ADJ的分级。
UNKNOWN_ADJ = 16
//级别最低级的进程,通常是被缓存的进程,但是系统也不清楚缓存的内容 CACHED_APP_MAX_ADJ = 15
//这是一个只托管不可见的活动的进程,因此可以在没有任何中断的情况下被杀死 CACHED_APP_MIN_ADJ = 9
//缓存进程,没有英文解释 SERVICE_B_ADJ = 8
//不活跃的服务,不像adj=5的服务那么活跃
//在root以后,有的系统优化大师会把所有服务都调成adj=8来达到内存优化的目的
//因为当所有人adj都比较高时,这样才能保证名正言顺的杀进程 PREVIOUS_APP_ADJ = 7
//被切换的进程,一般是用户前一个使用的进程。两个应用来回切换,那么前一个应用一般adj设置为7 HOME_APP_ADJ = 6
//与主应用程序有交互的进程 SERVICE_ADJ = 5
//活跃的服务进程 HEAVY_WEIGHT_APP_ADJ = 4
//高权重进程 BACKUP_APP_ADJ = 3
//正在备份的进程 PERCEPTIBLE_APP_ADJ = 2
//可感知进程,通常是前台Service进程 VISIBLE_APP_ADJ = 1
//可见进程 FOREGROUND_APP_ADJ = 0
//前台进程
剩下的就是adj值为负数的进程,基本上都是系统集成,不在本文的讨论范围内。负数进程是不会被lmk杀掉的。
5. 如何查看进程优先级和设备的内存临界值
首先通过 adb shell ps 指令查找对应进程的pid。
然后通过 adb shell cat /proc/${pid}/oom_adj(设备需要root)返回对应进程的adj值。
我们可以通过adb shell cat 查看下面两个文件,cat之前请先用chmod赋予权限,adj 代表的是oom_score_adj的值,对应的minfree则代表内存临界值。
/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree
比如我的测试机小米4C测试机对应的值就是:
adj: 0,58,117,176,529,1000
这个值其实是oom_score_adj的值,用这个值*17再除1000四舍五入取整数,就是对应的adj的值,例如第二个值58即为 58*17/1000 = 1,对应的adj也就是1,1000默认就是15。所以这6个值对应的adj是0,1,2,3,9,15。
minfree: 18432,23040,27648,32256,56250,81250
这个值是页值,一页等于4KB,换算成MB大概是72,90,108,126,220,318
当可用内存小于318MB的时候,系统开始杀adj=15的进程,以此类推。
6. 什么情况需要使用多进程
举个例子,现在要做一款音乐播放器,现在有以下几种方案:
A. 在Activity中直接播放音乐。
B. 启动后台Service,播放音乐。
C. 启动前台Service,播放音乐。
D. 在新的进程中,启动后台Service,播放音乐。
E. 在新的进程中,启动前台Service,播放音乐。
A方案
我们的播放器是直接在activity中启动的。首先这么做肯定是不对的,我们需要在后台播放音乐,所以当activity退出后就播不了了,之所以给出这个例子是为了控制变量作对比。
音乐播放器无非是打开app,选歌,播放,退到桌面,切其他应用。我们选取了三个场景,打开、按home、按back退回桌面。让我们看一下A的相对应的oom_adj、oom_score、oom_score_adj的值。


从上述三个场景的结果来看,当我们应用在前台的时候,无论adj还是score还是score_adj,他们的值都非常的小,基本不会被LMK所杀掉,但是当我们按了Home之后,进程的adj就会急剧增大,变为7,相应的score和score_adj也会增大。在上篇文章中我们得知,adj=7即为被切换的进程,两个进程来回切换,上一个进程就会被设为7。当我们按Back键的时候,adj就会被设为9,也就是缓存进程,优先级比较低,有很大的几率被杀掉。
B方案
B直接启动一个后台service并播放音乐,让我们来看下B的对应的打开、按下Home切换、按下Back退出相应的adj、score、score_adj的值。



三种状态的adj、score_adj的值和A都是一样的,只有score有一点出入,其实分析源码得知,LMK杀进程的时候,score的影响其实并不大,所以我们暂时忽略它。所以adj和score_adj的值都相同却内存不足的情况下,这两个应用谁占得内存更大,谁就会被杀掉。不过鉴于A实在activity中播放音乐,所以B还是比A略好的方案。
这里有朋友肯定要问了,为什么切到后台后,adj的值是7而不是5,后台不是还有service在跑吗?
我们通过查看源码可以找出来,当切换Home的时候,会调用ActivityStack.java的finishCurrentActivityLocked函数,然后调用到了ActivityManagerService.java的computeOomAdjLocked函数,在这里对进程的ADJ值进行重新计算。当进程为PreviousProcess情况,则ADJ=7。具体的计算流程请看computeOomAdjLocked计算流程。
if (app == mPreviousProcess && app.activities.size() > 0) {
if (adj > ProcessList.PREVIOUS_APP_ADJ) {
adj = ProcessList.PREVIOUS_APP_ADJ;
schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.cached = false;
app.adjType = "previous";
}
if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
}
}
C方案
C的话是启动一个前台Service来播放音乐。让我们来看一下对应的值。



在前台的时候,和AB是一样的,adj都是0,当切到后台,或者back结束时,C对应的adj都是2,也就是可感知进程。adj=2可以说是很高优先级了。一般adj<5的应用不会被杀掉。因此总的来说,C方案相对于B来说更稳定,用户体验更好。不过有一点不足是必须启动一个前台service。不过现在大部分的音乐类软件都会提供一个前台service,也就不是什么缺点了。
D方案
D把应用进行了拆分,把用于播放音乐的service放到了新的进程内,让我们看一下对应的值。



上面三张图对应的是D应用主进程的ADJ相关值,我们可以看出来,跟A类似,adj都是0,7,9。由于少了service部分,内存使用变少,最后计算出的oom_score_adj也更低了,意味着主进程部分也更不容易被杀死。下面我们看下拆分出的service的相关值。

因为是service进程,所以不受打开,关闭,切换所影响,我们可以看到service的adj值一直会是5,也就是活跃的服务进程,相比于B来说,优先级高了不少。不过对于C来说,其实这个方案反倒不如C的adj=2的前台进程更稳定。但是D可以自主释放主进程,使D实际所占用的内存很小,从而不容易被杀掉。C、D各有利弊。
E方案
E也是使用了多进程,并且在新进程中,使用了前台service,先来看下对应的值。



这个不多解释,和ABD基本差不多,都是0,7,9。我们看下拆分出来的进程的值。
我们可以看到,这个进程的值是2,像C方案,非常小,非常稳定,而且我们还可以在系统进入后台后,手动杀掉主进程,使整个应用的内存消耗降到最低。内存低,优先级又高,E获得了今天的“最稳定方案奖”。
7. 使用多进程的其他场景补充
多进程还有一种非常有用的场景,就是多模块应用。比如我做的应用大而全,里面肯定会有很多模块,假如有地图模块、大图浏览、自定义WebView等等(这些都是吃内存大户),一个成熟的应用一定是多模块化的。首先多进程开发能为应用解决了OOM问题,因为Android对内存的限制是针对于进程的,所以,当我们需要加载大图之类的操作,可以在新的进程中去执行,避免主进程OOM。而且假如图片浏览进程打开了一个过大的图片,java heap 申请内存失败,该进程崩溃并不影响我主进程的使用。
Android开发——Android多进程以及使用场景介绍的更多相关文章
- Android开发——Android M(6.0) 权限解决方案
Android开发--Android M(6.0) 权限解决方案 自从Android M(6.0)发布以来,权限管理相比以前有了很大的改变,很多程序员发现之前运行的好好的Android应用在Andro ...
- Android开发-Android Studio问题以及解决记录
[Android开发] Android Studio问题以及解决记录 http://blog.csdn.net/niubitianping/article/details/51400721 1.真 ...
- Android开发 - 掌握ConstraintLayout(二)介绍
介绍 发布时间 ConstraintLayout是在2016的Google I/O大会上发布的,经过这么长时间的更新,现在已经非常稳定. 支持Android 2.3(API 9)+ 目前的Androi ...
- Android开发——Android手机屏幕适配方案总结
)密度无关像素,单位为dp,是Android特有的单位 Android开发时通常使用dp而不是px单位设置图片大小,因为它可以保证在不同屏幕像素密度的设备上显示相同的效果. /** * dp与px的转 ...
- Android开发——Android的消息机制详解
)子线程默认是没有Looper的,Handler创建前,必须手动创建,否则会报错.通过Looper.prepare()即可为当前线程创建一个Looper,并通过Looper.loop()来开启消息循环 ...
- Android开发 Android Studio2.0 教程从入门到精通Windows版 - 入门篇
第一篇 介绍了Android Studio开发环境以及Genymotion虚拟机安装方法,本节将给大家介绍如何使用Android Studio开发应用. 开发第一应用 可以开发属于自己的应用,是否有点 ...
- (转载)所有分类 > 开发语言与工具 > 移动开发 > Android开发 Android中的Service:默默的奉献者 (1)
前言 这段时间在看一些IPC相关的东西,这里面就不可避免的要涉及到service,进程线程这些知识点,而且在研究的过程中我惊觉自己对这些东西的记忆已经开始有些模糊了——这可要不得.于是我就干脆花了点心 ...
- Qt for Android开发Android应用时的各种错误汇总(此片博文不成熟,请大家略过)
“Qt for Android真的很脆弱,项目能跑起来靠的是奇迹,跑不起来,各种报错才是正常...” 问题一:Qt for Android编译不过:make (e=2): 系统找不到指定的文件. 之前 ...
- android开发——Android开发中的47个小知识
1.判断sd卡是否存在 boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environme ...
随机推荐
- [Objective-C] 从NSInteger说开去
在iOS开发过程中,我一直习惯于使用C语法里的基本类型,而很少用(除非必须使用)Foundation的数据类型.最近看了一些资料,发现自己这样写可能有风险,虽然目前没遇到过相关的问题,但这是非常需要注 ...
- 使用angular-cli脚手架快速搭建项目
第一步 安装全局的angular-cli, npm install -g @angular/cli 或者 cnpm install -g @angular/cli@v1.0.0-rc.2 – 国内淘宝 ...
- PHP用正则匹配字符串中的特殊字符防SQL注入
本文出至:新太潮流网络博客 /** * [用正则匹配字符串中的特殊字符] * @E-mial wuliqiang_aa@163.com * @TIME 2017-04-07 * @WEB http:/ ...
- svn其它
参考地址: http://www.cnblogs.com/mymelon/p/5483215.html
- SQL Server 合并复制如何把备份的发布端或订阅端BAK文件还原为数据库
SQL Server的合并复制,是可以备份发布端和订阅端数据库为BAK文件的,但是问题是合并复制在数据库中自动创建的系统表.触发器.表中的RowGuid列等也会被一起备份. 这里我们举个例子,下面图中 ...
- SQL Server复制入门(一)----复制简介 (转载)
简介SQL Server中的复制(Replication)是SQL Server高可用性的核心功能之一,在我看来,复制指的并不仅仅是一项技术,而是一些列技术的集合,包括从存储转发数据到同步数据到维护数 ...
- asp.net获取当前网址url
asp.net获取当前网址url 设当前页完整地址是:http://www.jb51.net/aaa/bbb.aspx?id=5&name=kelli "http://" ...
- 如何在linux centos 环境下运行.exe文件
linux是不能运行window下的可执行文件的,必须借助于wine.百度了以下wine如下: Wine (“Wine Is Not an Emulator” 的递归缩写)是一个能够在多种 POS ...
- python基础学习24----使用pymysql连接mysql
使用pymysql连接mysql 安装pymysql pymysql安装可以通过两种方式 使用pip安装 首先简单说一下pip的使用方法 获取帮助 pip --help 升级 pip pip inst ...
- 实现一个协程版mysql连接池
实现一个协程版的mysql连接池,该连接池支持自动创建最小连接数,自动检测mysql健康:基于swoole的chanel. 最近事情忙,心态也有点不积极.技术倒是没有落下,只是越来越不想写博客了.想到 ...