Android后台保活套路分析

原文作者:D_clock爱吃葱花
链接:https://www.jianshu.com/p/63aafe3c12af
來源:简书
基于个人理解进行了部分删减补充

保活手段

当前业界的Android进程保活手段主要分为 黑、白、灰 三种,其大致的实现思路如下:

黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)

白色保活:启动前台Service

灰色保活:利用系统的漏洞启动前台Service

黑色保活

所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:

场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app

场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3

场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)

没错,我们的Android手机就是一步一步的被上面这些场景给拖卡机的。

针对场景1,估计Google已经开始意识到这些问题,所以在最新的Android N取消了 ACTION_NEW_PICTURE(拍照),ACTION_NEW_VIDEO(拍视频),CONNECTIVITY_ACTION(网络切换)等三种广播。

白色保活

白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。这样的目的也很容易理解,就是提高进程的优先级。让Linux在内存不足的时候不会优先被杀掉,从某种意义上来说这算是最不流氓的一种保活方式了。

灰色保活

灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:

  • 思路一:API < 18,启动前台Service时直接传入new Notification();
  • 思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理;
class KLiveService : Service() {    companion object {        const val SERVICE_ID = 0x11        class InnerKLiveService : Service() {            override fun onBind(intent: Intent?): IBinder? {                return null            }            override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {                val channelId = "keep"                val notification = NotificationCompat.Builder(this, channelId)                        .setPriority(NotificationCompat.PRIORITY_DEFAULT)                        .build()                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {                    val importance = NotificationManager.IMPORTANCE_DEFAULT                    val channel = NotificationChannel(channelId, "name", importance)                    // Register the channel with the system                    val notificationManager: NotificationManager =                            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager                    notificationManager.createNotificationChannel(channel)                }                startForeground(SERVICE_ID, notification)                stopSelf()                Log.i("TAG","stop")                return super.onStartCommand(intent, flags, startId)            }        }    }    override fun onBind(intent: Intent?): IBinder? {        return null    }    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {        val channelId = "keep"        val notification = NotificationCompat.Builder(this, channelId)                .setPriority(NotificationCompat.PRIORITY_DEFAULT)                .build()        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {            val importance = NotificationManager.IMPORTANCE_DEFAULT            val channel = NotificationChannel(channelId, "name", importance)            // Register the channel with the system            val notificationManager: NotificationManager =                    getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager            notificationManager.createNotificationChannel(channel)        }        startService(Intent(this, InnerKLiveService::class.java))        startForeground(SERVICE_ID, notification)        Log.i("TAG","start")        return super.onStartCommand(intent, flags, startId)    }    }

代码大致就是这样,能让你神不知鬼不觉的启动着一个前台Service。其实市面上很多app都用着这种灰色保活的手段。

但是,在Android O以后,传入的notification需要强制加入Channel机制,不知道是不是其中有个功能就是Google为了去除这个bug,总之在次之后的版本上,亲测该方法已经失效。站在一个Android用户的立场上,对此表示:干得漂亮(゜-゜)つロ干杯~!

进程回收机制

熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 **OOM Killer(Out-Of-Memory killer)**机制诞生。

了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:

  • 进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收
  • 普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0

那么我们如何查看进程的oom_adj值呢,需要用到下面的两个shell命令

ps | grep PackageName //获取你指定的进程信息

这里是以我写的demo代码为例子,红色圈中部分别为下面三个进程的ID

UI进程:com.clock.daemon
普通后台进程:com.clock.daemon:bg
灰色保活进程:com.clock.daemon:gray

当然,这些进程的id也可以通过AndroidStudio获得

接着我们来再来获取三个进程的oom_adj

cat /proc/进程ID/oom_adj

从上图可以看到UI进程和灰色保活Service进程的oom_adj=0,而普通后台进程oom_adj=15。到这里估计你也能明白,**为什么普通的后台进程容易被回收,而前台进程则不容易被回收了吧。**但明白这个还不够,接着看下图

上面是我把app切换到后台,再进行一次oom_adj的检验,你会发现UI进程的值从0变成了6,而灰色保活的Service进程则从0变成了1。这里可以观察到,app退到后台时,其所有的进程优先级都会降低。但是UI进程是降低最为明显的,因为它占用的内存资源最多,系统内存不足的时候肯定优先杀这些占用内存高的进程来腾出资源。所以,为了尽量避免后台UI进程被杀,需要尽可能的释放一些不用的资源,尤其是图片、音视频之类的

从Android官方文档中,我们也能看到优先级从高到低列出了这些不同类型的进程:Foreground processVisible processService processBackground processEmpty process。而这些进程的oom_adj分别是多少,又是如何挂钩起来的呢?推荐大家阅读下面这篇文章:

http://www.cnblogs.com/angeldevil/archive/2013/05/21/3090872.html

总结

絮絮叨叨写完了这么多,最后来做个小小的总结。回归到开篇提到QQ进程不死的问题,我也曾认为存在这样一种技术。可惜我把手机root后,杀掉QQ进程之后就再也起不来了。有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。

更多相关文章

  1. Android中LOG机制详解(上)
  2. Android基础和运行机制
  3. 重看android(二) android启动
  4. Android(安卓)low memory killer 详解
  5. 最新历史版本 :浅析Android线程模型
  6. Android(安卓)系统基础
  7. Android基础和运行机制
  8. Android实现进程之间通信
  9. Android应用程序的生命周期

随机推荐

  1. android jni方法模拟高频按键点击
  2. Android面试题(四)——动画
  3. android setTag的妙用和The key must be
  4. Android(安卓)Camera数据流分析全程记录(o
  5. Material Design: NavigationView Flaotin
  6. Android(安卓)Studio工程导入,仅需三步便
  7. EventBus在Android中的简单使用
  8. 转:android:Adapter用法总结
  9. Android(安卓)Window的添加和显示过程
  10. ADB命令行 使用总结(持续更新中)