android系统进程
❶ Android 进程管理篇(四)-cpu限制
梳理Process进程相关知识点,再继续补充点内容。
linux系统中对进程的管理无非是从调度策略、优先级以及CPU限制三个角度进行配置与管理,那么Android中主要是通过AMS来管理应用程序进程的,是不是也是从这三个方面进行管理的呢?答案是肯定的,那么本篇文章先来看看cpuset负载均衡在AMS中是如何应用的。
cpuset是Linux cgroup子系统,它为cgroup任务分配单独的CPU和内存。单独分配CPU即表明进程可调度cpu范围。cpu按不同的芯片,大小核数目和频率都有差别,大核频率高处理速度相对比小核快,而Android系统实际上还是响应优先于吞吐的交互型系统,因此Android AMS对进程管理于不同优先级的进程在调度cpu限制上会做有一些策略,以保证更好的交互响应。
还是回到AMS中与adj相关的有三个方法,这三个方法值得看一万遍,每一遍都会有新收获:
聚焦到computeOomAdjLocked方法,该方法主要是根据进程的四大组件状态决定当前进程的adj优先级。
以TOP_APP为例,这里ProcessRecord 的curSchedGroup属性对应的是cup调度组,而在后续applyOomAdjLocked中会执行Process的setProcessGroup方法。
调用Process的setProcessGroup方法
setProcessGroup是个native方法,并且这里分了若干类型的group,这里看top app优先级是最高的。接着jni到native
这里直接调用sched_policy.cpp的set_cpuset_policy,并传入对应的pid和SchedPolicy
这里主要就是通过policy对应具体的fd句柄,然后通过add_tid_to_cgroup()写cpuset对应节点。这里要注意,如果cpusets_enabled为false的话,会走set_sched_policy,这部分下篇会讲到。
看看对应的fd是什么:
那我们来看看对应节点是什么内容:
然后看看对应的cpuset配置:
显然,top app 满核随便跑,foreground跑在除了3这个核以外的所有核上, 而background只能跑在小核上。
不同芯片平台配置会有差别。
❷ Android 进程管理篇(五)-调度策略与优先级
接上篇cpuset,这篇来看看进程优先级与调度策略管理。
Linux中,优先级号一共有0-139,其中0-99的是RT(实时)进程,100-139的是非实时进程。
数字越低优先级越高。
SCHED_IDLE idle状态低优先级进程调度
先看Process中调度策略的划分,与上面介绍的一样。
首先在AMS中封装了FIFO和NORMAL的两个策略,NORMAL好说,看看FIFO在哪用到
这里Process.setThreadScheler并没有太多的应用,我们直接来看优先级设置吧。else中将top app的UI线程与render线程都设置为TOP_APP_PRIORITY_BOOST优先级,nice值为-10,非常高。
这里主要调用androidSetThreadPriority方法
这里通过set_sched_policy来调整调度策略,并通过setpriority设置进程优先级。这里不特意区分进程与线程了,反正在linux中都是进程。
这里与前面的cpuset非常相似,依然是写节点,节点前面也提了就是:
那么这里又引入了一个schedtune子系统,简单介绍下:
schedtune是ARM/Linaro为了EAS新增的一个子系统,主要用来控制进程调度选择CPU以及boost触发。通过权重来分配CPU负载能力来实现快速运行。高权重意味着会享受到更好的cpu负载来处理对应的任务,换句话说你能享受相对更好的cpu运行性能。
简单梳理下schedtune和不同类型SchedPolicy之间的对应关系:
看下具体文件夹内容:
系统配置:
这里/dev/stune相关配置只做了这么一个
❸ Android 守护进程的实现方式
在我们进行应用开发时,会遇到上级的各种需求,其中有一条 刚需: 后台保活 ,更有甚者:
我要我们的应用永远活在用户的手机后台不被杀死 —— 这都 TM 的扯淡
除了系统级别的应用能持续运行,所有三方程序都有被杀死的那一天!当然 QQ/微信/陌陌 等会好一些,因为他们已经深入设备的 心 ;
我们能做的只是通过各种手段尽量让我们的程序在后台运行的时间长一些,或者在被干掉的时候,能够重新站起来,而且这个也不是每次都有效的,也是不能在所有的设备的上都有效的;要做到后台进程保活,我们需要做到两方便:
要实现实现上边所说,通过下边几点来实现,首先我们需要了解下进程的优先级划分:
Process Importance 记录在 ActivityManager.java 类中:
了解进程优先级之后,我们还需要知道一个进程回收机制的东西;这里参考 AngelDevil 在博客园上的一篇文章:
Android 的 Low Memory Killer 基于 Linux 的 OOM 机制,在 Linux 中,内存是以页面为单位分配的,当申请页面分配时如果内存不足会通过以下流程选择bad进程来杀掉从而释放内存:
在 Low Memory Killer 中通过进程的 oom_adj 与占用内存的大小决定要杀死的进程, oom_adj 越小越不容易被杀死;
Low Memory Killer Driver 在用户空间指定了一组内存临界值及与之一一对应的一组 oom_adj 值,当系统剩余内存位于内存临界值中的一个范围内时,如果一个进程的 oom_adj 值大于或等于这个临界值对应的 oom_adj 值就会被杀掉。
下边是表示 Process State (即老版本里的 OOM_ADJ )数值对照表,数值越大,重要性越低,在新版SDK中已经在 android 层去除了小于0的进程状态
Process State (即老版本的 OOM_ADJ )与 Process Importance 对应关系,这个方法也是在 ActivityManager.java 类中,有了这个关系,就知道可以知道我们的应用处于哪个级别,对于我们后边优化有个很好地参考
一般情况下,设备端进程被干掉有一下几种情况
由以上分析,我们可以可以总结出,如果想提高我们应用后台运行时间,就需要提高当前应用进程优先级,来减少被杀死的概率
分析了那么多,现在对Android自身后台进程管理,以及进程的回收也有了一个大致的了解,后边我们要做的就是想尽一切办法去提高应用进程优先级,降低进程被杀的概率;或者是在被杀死后能够重新启动后台守护进程
第一种方式就是利用系统漏洞,使用 startForeground() 将当前进程伪装成前台进程,将进程优先级提高到最高(这里所说的最高是服务所能达到的最高,即1);
这种方式在 7.x 之前都是很好用的,QQ、微信、IReader、Keep 等好多应用都是用的这种方式实现;因为在7.x 以后的设备上,这种伪装前台进程的方式也会显示出来通知栏提醒,这个是取消不掉的,虽然 Google 现在还没有对这种方式加以限制,不过这个已经能够被用户感知到了,这种方式估计也用不了多久了
下边看下实现方式,这边这个 VMDaemonService 就是一个守护进程服务,其中在服务的 onStartCommand() 方法中调用 startForeground() 将服务进程设置为前台进程,当运行在 API18 以下的设备是可以直接设置,API18 以上需要实现一个内部的 Service ,这个内部类实现和外部类同样的操作,然后结束自己;当这个服务启动后就会创建一个定时器去发送广播,当我们的核心服务被干掉后,就由另外的广播接收器去接收我们守护进程发出的广播,然后唤醒我们的核心服务;
当我们启动这个守护进程的时候,就可以使用以下 adb 命令查看当前程序的进程情况(需要 adb shell 进去设备),
为了等下区分进程优先级,我启动了一个普通的后台进程,两外两个一个是我们启动的守护进程,一个是当前程序的核心进程,可以看到除了后台进程外,另外两个进程都带有 isForeground=true 的属性:
然后我们可以用下边的命令查看 ProcessID
有了 ProcessID 之后,我们可以根据这个 ProcessID 获取到当前进程的优先级状态 Process State ,对应 Linux 层的 oom_adj
可以看到当前核心进程的级别为 0 ,因为这个表示当前程序运行在前台 UI 界面,守护进程级别为 1 ,因为我们利用漏洞设置成了前台进程,虽然不可见,但是他的级别也是比较高的,仅次于前台 UI 进程,然后普通后台进程级别为 4 ;当我们退到后台时,可以看到核心进程的级别变为 1 了,这就是因为我们利用 startForeground() 将进程设置成前台进程的原因,这样就降低了进程被系统回收的概率了;
可以看到这种方式确实能够提高进程优先级,但是在一些国产的设备上还是会被杀死的,比我我测试的时候小米点击清空最近运行的应用进程就别干掉了;当把应用加入到设备白名单里就不会被杀死了,微信就是这样,人家直接装上之后就已经在白名单里了,我们要做的就是在用户使用中引导他们将我们的程序设置进白名单,将守护进程和白名单结合起来,这样才能保证我们的应用持续或者
Android系统在5.x以上版本提供了一个 JobSchele 接口,系统会根据自己实现定时去调用改接口传递的进程去实现一些操作,而且这个接口在被强制停止后依然能够正常的启动;不过在一些国产设备上可能无效,比如小米;
下边是 JobServcie 的实现:
我们要做的就是在需要的时候调用 JobSchele 的 schele 来启动任务;剩下的就不需要关心了, JobSchele 会帮我们做好,下边就是我这边实现的启动任务的方法:
在实现 Service 类时,将 onStartCommand() 返回值设置为 START_STICKY ,利用系统机制在 Service 挂掉后自动拉活;不过这种方式只适合比较原生一些的系统,像小米,华为等这些定制化比较高的第三方厂商,他们都已经把这些给限制掉了;
这种方式在以下两种情况无效:
事事没有绝对,万物总有一些漏洞,就算上边的那些方式不可用了,后边肯定还会出现其他的方式;我们不能保证我们的应用不死,但我们可以提高存活率;
其实最好的方式还是把程序做好,让程序本身深入人心,别人喜欢你了,就算你被干掉了,他们也会主动的把你拉起来,然后把你加入他们的白名单,然后我们的目的就实现了不是 😁 ~
❹ Android 如何进行进程保活
每一个 Android 应用启动后至少对应一个进程,有的是多个进程,而且主流应用中多个
进程的应用比例较大
对于任何一个进程,我们都可以通过 adb shell ps|grep <package_name>的方式来查看
它的基本信息
Android 中的进程跟封建社会一样,分了三流九等,Android 系统把进程的划为了如下
几种(重要性从高到低),网上多位大神都详细总结过(备注:严格来说是划分了 6 种)。
场景:
1.某个进程持有一个正在与用户交互的 Activity 并且该 Activity 正处于 resume 的
状态。
2.某个进程持有一个 Service,并且该 Service 与用户正在交互的 Activity 绑定。
3.某个进程持有一个 Service,并且该 Service 调用 startForeground()方法使之位于前台运行。
4.某个进程持有一个 Service,并且该 Service 正在执行它的某个生命周期回调方法,比如 onCreate()、 onStart()或 onDestroy()。
5.某个进程持有一个 BroadcastReceiver,并且该 BroadcastReceiver 正在执行其onReceive()方法。用户正在使用的程序,一般系统是不会杀死前台进程的,除非用户强制停止应用或者系统内存不足等极端情况会杀死。
场景:
1.拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())。
2.拥有绑定到可见(或前台)Activity 的 Service
用户正在使用,看得到,但是摸不着,没有覆盖到整个屏幕,只有屏幕的一部分可见进程
不包含任何前台组件,一般系统也是不会杀死可见进程的,除非要在资源吃紧的情况下,
要保持某个或多个前台进程存活
场景
1.某个进程中运行着一个 Service 且该 Service 是通过 startService()启动的,与用户看见的界面没有直接关联。
在内存不足以维持所有前台进程和可见进程同时运行的情况下,服务进程会被杀死
场景:
在用户按了"back"或者"home"后,程序本身看不到了,但是其实还在运行的程序,
比如 Activity 调用了 onPause 方法系统可能随时终止它们,回收内存
场景:
某个进程不包含任何活跃的组件时该进程就会被置为空进程,完全没用,杀了它只有好处没坏处,第一个干它!
上面是进程的分类,进程是怎么被杀的呢?系统出于体验和性能上的考虑,app 在退到
后台时系统并不会真正的 kill 掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制
来判断要 kill 掉哪些进程,以腾出内存来供给需要的 app, 这套杀进程回收内存的机制
就叫 Low Memory Killer。那这个不足怎么来规定呢,那就是内存阈值,我们可以使用
cat /sys/mole/lowmemorykiller/parameters/minfree 来查看某个手机的内存阈值。
其实系统在进程回收跟内存回收类似也是有一套严格的策略,可以
自己去了解,大概是这个样子的,oom_adj 越大,占用物理内存越多会被最先 kill 掉,OK,那么现在对于进程如何保活这个问题就转化成,如何降低 oom_adj 的值,以及如
何使得我们应用占的内存最少。
据说这个是手 Q 的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要
使得进程常驻,我们只需要在锁屏的时候在本进程开启一个 Activity,为了欺骗用户,
让这个 Activity 的大小是 1 像素,并且透明无切换动画,在开屏幕的时候,把这个 Activity
关闭掉,所以这个就需要监听系统锁屏广播,我试过了,的确好使,如下。
如果直接启动一个 Activity,当我们按下 back 键返回桌面的时候,oom_adj 的值是 8,
上面已经提到过,这个进程在资源不够的情况下是容易被回收的。现在造一个一个像素
的 Activity。
为了做的更隐藏,最好设置一下这个 Activity 的主题,当然也无所谓了
在屏幕关闭的时候把 LiveActivity 启动起来,在开屏的时候把 LiveActivity 关闭掉,所以
要监听系统锁屏广播,以接口的形式通知 MainActivity 启动或者关闭 LiveActivity。
现在 MainActivity 改成如下
按下 back 之后,进行锁屏,现在测试一下 oom_adj 的值
果然将进程的优先级提高了。
但是还有一个问题,内存也是一个考虑的因素,内存越多会被最先 kill 掉,所以把上面
的业务逻辑放到 Service 中,而 Service 是在另外一个 进程中,在 MainActivity 开启这
个服务就行了,这样这个进程就更加的轻量,
OK,通过上面的操作,我们的应用就始终和前台进程是一样的优先级了,为了省电,
系统检测到锁屏事件后一段时间内会杀死后台进程,如果采取这种方案,就可以避免了
这个问题。但是还是有被杀掉的可能,所以我们还需要做双进程守护,关于双进程守护,
比较适合的就是 aidl 的那种方式,但是这个不是完全的靠谱,原理是 A 进程死的时候,
B 还在活着,B 可以将 A 进程拉起来,反之,B 进程死的时候,A 还活着,A 可以将 B
拉起来。所以双进程守护的前提是,系统杀进程只能一个个的去杀,如果一次性杀两个,
这种方法也是不 OK 的。
事实上
那么我们先来看看 Android5.0 以下的源码,ActivityManagerService 是如何关闭在应用
退出后清理内存的
在应用退出后,ActivityManagerService 不仅把主进程给杀死,另外把主进程所属的进
程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也
就停止了。所以在 Android5.0 以后的手机应用在进程被杀死后,要采用其他方案。
这种大部分人都了解,据说这个微信也用过的进程保活方案,移步微信 Android 客户端
后台保活经验分享,这方案实际利用了 Android 前台 service 的漏洞。
原理如下
对于 API level < 18 :调用 startForeground(ID, new Notification()),发送空的
Notification ,图标则不会显示。
对于 API level >= 18:在需要提优先级的 service A 启动一个 InnerService,两个服务
同时 startForeground,且绑定同样的 ID。Stop 掉 InnerService ,这样通知栏图标即
被移除。
public class KeepLiveService extends Service{
public static final int NOTIFICATION_ID=0x11;
public KeepLiveService() {
}
@Override
public IBinder onBind(Intent intent){
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate(); //API 18 以下,直 接发 送 Notification 并 将 其 置 为 前 台
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN_MR2){
startForeground(NOTIFICATION_ID,new Notification());
} else { //API 18 以上,发送 Notification 并将其置为前台后,启动 InnerService
Notification.Builder builder=new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
startService(new Intent(this, InnerService.class));
}
}
public class InnerService extends Service{
@Override public IBinder onBind(Intent intent) {
return null;
}
@Override public void onCreate() {
super.onCreate(); //发送与 KeepLiveService中 ID 相同的 Notification,然后将其取消并取消自己的前台显示
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);startForeground(NOTIFICATION_ID,
builder.build());
new Handler().postDelayed(new Runnable() {
@Override public void run() {
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
stopSelf();
}
},
100);
}
}
}
在没有采取前台服务之前,启动应用,oom_adj 值是 0,按下返回键之后,变成 9(不
同 ROM 可能不一样)
相互唤醒的意思就是,假如你手机里装了支付宝、淘宝、天猫、UC 等阿里系的 app,
那么你打开任意一个阿里系的 app 后,有可能就顺便把其他阿里系的 app 给唤醒了。
这个完全有可能的。此外,开机,网络切换、拍照、拍视频时候,利用系统产生的广播
也能唤醒 app,不过 Android N 已经将这三种广播取消了。
如果应用想保活,要是 QQ,微信愿意救你也行,有多少手机上没有 QQ,微信呢?或
者像友盟,信鸽这种推送 SDK,也存在唤醒 app 的功能。
拉活方法
JobSheler是作为进程死后复活的一种手段,
native进程方式最大缺点是费电,Native
进程费电的原因是感知主进程是否存活有两种实现方式,在 Native 进程中通过死循环
或定时器,轮训判断主进程是否存活,当主进程不存活时进行拉活。其次 5.0 以上系统
不支持。 但是 JobSheler 可以替代在 Android5.0 以上 native 进程方式,这种方式即
使用户强制关闭,也能被拉起来,亲测可行。
JobSheler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
@Override
public void onCreate() {
super.onCreate();
startJobSheler();
}
public void startJobSheler() {
try {
JobInfo.Builder builder = new JobInfo.Builder(1,new ComponentName(getPackageName(), MyJobService.class.getName()));
builder.setPeriodic(5); builder.setPersisted(true); JobScheler jobScheler =(JobScheler)
this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheler.schele(builder.build());
}
catch
(Exception ex)
{ ex.printStackTrace(); } }
@Override
public boolean onStartJob(JobParameters jobParameters) {
return false;
} @Override public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
这个是系统自带的,onStartCommand 方法必须具有一个整形的返回值,这个整形的返
回值用来告诉系统在服务启动完毕后,如果被 Kill,系统将如何操作,这种方案虽然可
以,但是在某些情况 or 某些定制 ROM 上可能失效,我认为可以多做一种保保守方案。
1.START_STICKY
如果系统在 onStartCommand 返回后被销毁,系统将会重新创建服务并依次调用
onCreate 和 onStartCommand(注意:根据测试 Android2.3.3 以下版本只会调用
onCreate 根本不会调用 onStartCommand,Android4.0 可以办到),这种相当于服务
又重新启动恢复到之前的状态了)。
2.START_NOT_STICKY
如果系统在 onStartCommand 返回后被销毁,如果返回该值,则在执行完
onStartCommand 方法后如果 Service 被杀掉系统将不会重启该服务
3.START_REDELIVER_INTENT
START_STICKY 的兼容版本,不同的是其不保证服务被杀后一定能重启。
4.相比与粘性服务与系统服务捆绑更厉害一点,这个来自爱哥的研究,这里说的系统服务
很好理解,比如 NotificationListenerService,NotificationListenerService 就是一个监听
通知的服务,只要手机收到了通知,NotificationListenerService 都能监听到,即时用户
把进程杀死,也能重启,所以说要是把这个服务放到我们的进程之中,那么就可以呵呵
了
所以你的应用要是有消息推送的话,那么可以用这种方式去欺骗用户。
❺ 033 Android多进程-共享内存
要使用一块共享内存
还是先看共享内存的使用方法,我主要介绍两个函数:
通过 shmget() 函数申请共享内存,它的入参如下
通过 shmat() 函数将我们申请到的共享内存映射到自己的用户空间,映射成功会返回地址,有了这个地址,我们就可以随意的读写数据了,我们继续看一下这个函数的入参
共享内存的原理是在内存中单独开辟的一段内存空间,这段内存空间其实就是一个tempfs(临时虚拟文件),tempfs是VFS的一种文件系统,挂载在/dev/shm上,前面提到的管道pipefs也是VFS的一种文件系统。
由于共享的内存空间对使用和接收进程来讲,完全无感知,就像是在自己的内存上读写数据一样,所以也是 效率最高 的一种IPC方式。
上面提到的IPC的方式都是 在内核空扰灶间中开辟内存来存储数据 ,写数据时,需要将数据从用户空间拷贝到内核空间,读数据时,需要从内核空间拷贝到自己的用户空间,
共享内存就只需要一次拷贝 ,而且共享内存不是在内核开辟空间,所以可以 传输的数据量大 。
但是 共享内存最大的缺点就是没有并发的控制,我们一般通过信号量配合共享内存使用,进行同步和并发的控制 。
共享内存在Android系统中主要的使用场景是 用来传输大数据 ,并且 Android并没有直接使用Linux原生的共享内存方式,而是设计了Ashmem匿名共享内存 。
之前说到有名管道和匿名管道的区别在于有名管道可以在vfs目录树中查看到这个管道的文件,但是匿名管道不行, 所以匿名共享内存同样也是无法在vfs目录中查看到 的, Android之所以要设计匿名共享内存 ,我觉得主要是为了安全性的考虑吧。
我们来看看共享内存的一个使用场景,在Android中,如果蠢卜我们想要将当前的界面显示出来,需要将当前界面的图元数据传递Surfaceflinger去做图层混合,图层混合之后的数据会直接送入帧缓存,送入帧缓存后,显卡就会直接取出帧缓存里的图元数据显示了。
那么我们如何将应用的Activity的图元数据传递给SurfaceFlinger呢?想要将图像数据这样比较大的数据跨进程传输,靠binder是不行的,所以这儿便用到匿名共享内存。
从谷歌官方提供的架构图可以看到,图元数据是通过BufferQueue传递到SurfaceFlinger去的,当我们想要绘制图像的时候, 需要从BufferQueue中申请一个Buffer,Buffer会调用Gralloc模块来分配共享内存 当作图元缓冲区存放我们的图元数据。
可以看到Android的匿名共享内存是通过 ashmem_create_region() 函数来申请共享内存的,它会在/dev/ashmem下创建一个虚拟文件,Linux原生共享内存是通过shmget()函数,并会在/dev/shm下创建虚拟文件。
匿名共享内存是通过 mmap() 函数将申请到的内存映射到自己的进程空间,而Linux是通过*shmat()函数。
虽然函数不一样,但是带李穗Android的匿名共享内存和Linux的共享内存在本质上是大同小异的。
。