Android Performance

Android App 链式唤醒分析

Word count: 4.3kReading time: 15 min
2020/05/07
loading

MIUI 12 的发布, 将之前一直是应用开发者和 Rom 开发者斗争最激烈的部分展示给了普通消费者, 让普通消费者也知道了这场斗争的细节, 正所谓 “魔高一尺道高一丈” , Rom 开发者由于有更高的代码修改权限, 始终占据着上风 ; App 开发者当然也不甘示弱, 各种保活拉起黑科技层出不穷,甚至 Google 都参与到了这部分斗争中, 居中调和, 制定各种规则来规范双方. 当然斗争对双方来说都算是好事, 毕竟任何一方完全的胜利都会导致 “狡兔死走狗烹,飞鸟尽良弓藏”

不过双方斗争的受害者无疑还是使用手机的消费者 , App 如果斗争成功, 那么手机上各种后台进程乱跑, 杀不掉, 占用 CPU 和内存 , 这不是消费者想看到的 ; 如果 Rom 开发者斗争成功 , App 的体验必定会大打折扣 , 各位 App 开发者应该深有体会.

从文章最后一段可以看到, 其实各个手机厂商对付这一套都有自己的策略, 基本上都可以搞定自启动和关联启动. 至于隐私 , 李彦宏曾经说过 “中国人对隐私问题的态度更加开放,也相对来说没那么敏感。如果他们可以用隐私换取便利、安全或者效率。在很多情况下,他们就愿意这么做“ . 大家想想在微信里面复制一段话打开到淘宝就可以自动跳转到这个物品, 方不方便? 好不好用? 还想不想用? 剪贴板再借我看一看?

希望大家在隐私问题上不要打哈哈, 技术是把双刃剑, 如果隐私落到别有用心的人手上, 后果是很严重的, 就算不是为了自己, 为了下一代. 欧盟为什么要搞《通用数据保护条例》(General Data Protection Regulation,简称 GDPR), 就是为了隐私. 举个例子 , 国内很多厂商的产品现在要区分是否在欧盟买, 如果是在欧盟卖的话, 就得把里面那些收集用户数据的功能都关掉 , 否则抓住了就能罚你罚到吐血 . 至于中国和印度, 随便收集.

本篇文章不涉及到隐私部分, 我是对隐私保护无条件支持的 . 这里只从技术的角度 , 来讲一下 MIUI 12 爆出来的应用自启动和关联唤醒的问题.

PS: 大家在自己的手机上可能看不到我列的一些例子, 是因为我是用的 Android 10 的 AOSP 代码, 大部分的国产 Rom 都已经阻断了应用的这些行为.

技术名词解释

首先解释几个技术名词, 方便大家对号入座

进程启动

在 Android 中 , 一个 App 包含六部分, 进程(必选) + Activity (可选) + BroadcastReceiver (可选)+Service (可选)+ContentProvider (可选) + 子进程(可选)

一个必选项加五个可选项, 组成了一个 App , 其中 Activity(可选) + BroadcastReceiver(可选)+Service(可选)+ContentProvider(可选) 这四个又称为 Android 的四大组件, 之所以这四兄弟这么特殊, 是因为这四个组件都可以单独启动,

但是这四兄弟启动之前, 系统都会检查对应的进程是否存在, 如果进程不存在 , 那么就需要先启动进程, 再启动这个组件. 我们在桌面上点击一个应用图标, 其实启动的就是他的 Activity , 系统会先创建进程, 然后再启动 Activity , 我们才可以看到对应的界面

一般自启动和关联启动, 一般不会直接启动 Activity , 因为 Activity 是用户可感知的 , 你在后台莫名其妙起了一个界面到前台, 用户分分钟卸了你 . 所以一般自启动和关联启动都是在 BroadcastReceiver (可选) + Service (可选) + ContentProvider (可选) 三个上面做文章.

自启动

自启动指的是不借助其他的应用, 通过监听系统的一些事件, 或者文件变化, 通过系统的机制, 把自己的进程拉起来处理事情.

关联启动

关联启动指的是借助其他应用来启动自己, 比如大家列出来的起点读书启动作家助手\电信营业厅\百词斩这种.

启动阻断

启动阻断也叫切断唤醒, Rom 开发人员在四大组件启动的地方加入逻辑判断, 符合条件的组件才能拉起自己的进程 , 不符合条件的组件直接返回 , 这样就达到了启动阻断的目的.

当然这里面还有很多工作要做, 比如工作状态判断, 拉起合理性判断 , 一旦错误的阻断必然会引起用户的使用逻辑的断裂, 比如用户在一个 App 里面要拉起支付宝进行支付 , 结果启动支付宝的支付组件的时候被你给阻断了, 可以想象用户的愤怒

有了上面几个简单的概念, 下面我们就简单说一下自启动和关联启动的技术分析 .

分析手段

Monkey

要分析应用启动,首先需要安装大量应用,然后执行 Monkey,让大部分进程都跑起来。我使用的 Monkey 命令如下,跑完就自己去睡觉了

1
adb shell monkey --kill-process-after-error --ignore-security-exceptions --ignore-crashes --pct-appswitch 90 --pct-touch 10 --throttle 10000 --ignore-timeouts --ignore-native-crashes 100000000

EventLog

首先可以用 EventLog 来查看进程的启动信息,EventLog 会如实记录每个进程的启动、死亡信息。我使用下面的命令来进行进程启动和死亡的过滤

1
adb logcat -b events | egrep "am_proc_died|am_proc_start"

Dumpsys

这里主要是使用了 Dumpsys activity ,主要是用来分析进程的各个组件的信息

1
adb shell dumpsys activity

自启动的技术分析

自启动指的是不借助其他的应用, 通过监听系统的一些事件, 或者文件变化, 通过系统的机制, 把自己的进程拉起来处理事情. 这些系统的事件就包括开机广播 / 网络变化 / 媒体库扫描等(这里只列了一部分) .

开机广播

用户重启手机后, 系统会向注册了开机广播的应用发广播, 收到广播的应用就可以把自己拉起来, 开始处理对应的逻辑(拉起更多的进程) , 对应的广播如下:

1
android.intent.action.BOOT_COMPLETED

应用可以监听这个广播, 在用户重启手机后, 将自己唤醒, 处理自己的逻辑 , 比如说继续图片备份/ 继续同步联系人 / 检查是否有固件更新 / 推送最新的新闻等操作

当然监听这个广播也是应用自启动的一个手段

案例: 腾讯新闻监听开机广播拉起后台进程

典型的广播接受处理记录 : com.tencent.news 的 com.tencent.news.system.BootBroadcastReceiver 组件接收了 android.intent.action.BOOT_COMPLETED 广播 ,处理了 7s ,至于怎么处理, 当然是先把 com.tencent.news 这个进程拉起来, 然后执行 BootBroadcastReceiver  的 onReceive 方法 . 这是一个典型的自启动的例子

网络变化

网络变化包括网络连接 / 断开 / wifi 移动网络切换等操作 , 一旦发生这些事件, 系统会向对应注册了这个事件的应用发送广播 . 对应的广播如下:

1
android.net.conn.CONNECTIVITY_CHANGE

应用就可以监听这个广播来执行对应的逻辑 , 比如你在看直播 ,突然 wifi 断了切换成了 4G 网络, 应用就可以提醒用户是否使用移动网络继续观看, 毕竟网络直播还是很耗流量的.

当然监听这个广播也是应用自启动的一个手段

案例

下图可以看到五个监听了网络变大的广播接收器 (只显示了五个 , 其实有 200 多个) , 监听到网络变化后拉起自身

  1. 包名: com.alibaba.android.rimet(钉钉)
  2. 接收器 : com.xiaomi.push.service.receivers.NetworkStatusReceiver
  3. 包名: cn.xuexi.android
  4. 接收器: com.xiaomi.push.service.receivers.NetworkStatusReceiver
  5. 包名: com.sina.weibo
  6. 接收器: com.xiaomi.push.service.receivers.NetworkStatusReceiver
  7. 包名: com.sdu.didi.psnger
  8. 接收器 : com.didi.sdk.push.PushNetReceiver
  9. 包名: com.meelive.ingkee
  10. 接收器 : com.network_optimization.NetWorkStateReceiver

图中 packageName 就是对于的应用的包名, name 是启动的组件

1
android.net.wifi.STATE_CHANGE

媒体库扫描

系统监听到文件变化或者存储盘变化也会发通知给各个应用 , 比如说增加了一个图片或者文档 , 其对于的广播如下

1
2
3
android.intent.action.MEDIA_SCANNER_STARTED
android.intent.action.MEDIA_SCANNER_FINISHED
android.intent.action.MEDIA_EJECT

当然监听这个广播也是应用自启动的一个手段

案例: jd 监听 MEDIA_SCANNER_STARTED 广播自启

下面是一个典型的监听媒体库扫描广播进行自启动的案例:

com.jd.jrapp 的广播接收器 com.jd.jrapp.library.longconnection.receiver.BootReceiver 监听到 android.intent.action.MEDIA_SCANNER_STARTED 广播后, 启动自己进程开始处理

三方 SDK - 个推

个推是各个应用接入的一个三方 SDK , 用于消息推送 , 但其实个推也集成了上面说的哪几种自启动的方式 , 包括 BOOT_COMPLETED,CONNECTIVITY_CHANGE,USER_PRESENT 这些

关于个推,由于可定制型比较强, 比如 在项目源码中添加一个继承自 com.igexin.sdk.PushService 的自定义 Service 就可以 , 所以从 EventLog 和 Dumpsys 没法直接看出来哪个用了个推来保活或者相互唤醒, 不过其对于的子进程得设置为 :pushservice , 可以根据这个做判断(有可能不准)

所以我们直接看个推的配置文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<service
android:name="com.igexin.sdk.PushService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"
android:label="NotificationCenter"
android:process=":pushservice"/>
<receiver android:name="com.igexin.sdk.PushReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.intent.action.USER_PRESENT" />
<!-- 以下三项为可选的 action 声明,有助于提高 service 存活率 -->
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
</intent-filter>
</receiver>
<activity
android:name="com.igexin.sdk.PushActivity"
android:excludeFromRecents="true"
android:exported="false"
android:process=":pushservice"
android:taskAffinity="com.igexin.sdk.PushActivityTask"
android:theme="@android:style/Theme.Translucent.NoTitleBar" >
</activity>
<activity
android:name="com.igexin.sdk.GActivity"
android:excludeFromRecents="true"
android:exported="true"
android:process=":pushservice"
android:taskAffinity="com.igexin.sdk.PushActivityTask"
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>

像最前面同提到的 BootComplete , com.ss.android.ugc.aweme:pushservice 可能就是接入了个推

关联启动的技术分析

关联启动指的是借助其他应用来启动自己 , 比如说很多 App 接入了同一个 SDK , 那么一旦你启动了接入这个 SDK 的应用 ,那么这个 SDK 就可以启动同样接入了这个 SDK 的其他应用, 达到关联唤醒的目的

这个 SDK 可以是 BAT 集团内部自研的通用 SDK , 也可以是三方提供的 SDK , 根据我自己的调试来看 , 大家提到的 xxx 启动了 xxx , 大部分都是通过三方 SDK 来实现的 , 大部分是用了极光推送.

下面就以一个案例来看极光推送是怎么利用一个已经启动的 App 来启动另外一个没有启动的 App 的.

首先我们看 EventLog 可以看到进程的启动信息 , 包括进程名, 进程 pid , 启动的组件, 启动的组件类型.

1
[0,19428,10195,com.qq.reader,service,{com.qq.reader/cn.jpush.android.service.DaemonService}] 

上面这条 Log 解释一下就是

  1. 启动进程 : com.qq.reader(QQ 阅读)
  2. 启动 pid :19428
  3. 启动组件: cn.jpush.android.service.DaemonService
  4. 组件类型: service

案例:起点 App 通过极光推送拉起 QQ 阅读

执行 adb shell am force-stop com.qq.reader , 强制杀掉 QQ 阅读 , 观察 EventLog, 从下面的可以看到 , QQ 阅读的进程被拉起, 拉起的是 Service 这个组件, 其具体的内容是 com.qq.reader/cn.jpush.android.service.DaemonService

当然从 Event Log 里面我们看不出来是谁拉起了这个 Service ,这时候就需要 dumpsys activity 的帮助了, 由于是 Service 组件被拉起, 那么我们可以看 com.qq.reader 的 ServiceRecord , 其内容如下, 可以看到其 Connections 一栏, 是被 com.qidian.QDReader:pushcore 这个进程拉起的

那么对应的 , 在小米的 MIUI 12 关联启动界面就会显示 : 起点读书 在 8:48 分拉起了 QQ 阅读(由于没有小米手机, 所以没法截图, 大家自己看高票答案 https://www.zhihu.com/question/391494145 自己脑补一下就可以了)

极光推送的官方文档其实也说的很清楚, 提供了被拉起和拉起别人的功能, 看你自己怎么用.

极光官方文档唤醒配置

极光关联启动文档

手机厂商应对

最前面的有说到, 进程管理是应用开发者和 Rom 开发者斗争最激烈的部分 , MIUI 选择了将斗争的过程展示给了普通消费者, 让普通消费者也知道了这场斗争的细节 . 其他的厂商也做了相同的事情 , 否则整个系统基本上是没法用的 , 就像我手上现在这台测试用的 pixel , 不断有进程因为整机内存太小被 LMK 杀掉, 然后马上被各种手段重新启动 , 耗电极快, 卡的连娘都不认识了.

我们从极光和个推的官方文档就可以看到各个手机厂商的应对方法和开关的界面, 这里列出来是方便大家进去看一下, 因为各个手机厂商的白名单配置不一样, 或者有时候用户自己改过但是忘记了 , 都可以进去重新设置一下 , 对于那些你退出了就不想让他继续活动的应用 ,果断去掉白名单.

极光推送白名单配置

个推白名单配置

EMUI OS(华为)

  • 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程不会开启,只能手动开启应用
  • 后台应用保护:需要手动把应用加到此列表,否则设备进入睡眠后会自动杀掉应用进程,只有手动开启应用才能恢复运行
  • 通知管理:应用状态有三种:提示、允许、禁止。禁止应用则通知栏不会有任何提醒

Flyme OS(魅族)

  • 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程无法开启
  • 通知栏推送:关闭应用通知则收到消息不会有任何展示

Funtouch OS(VIVO)

  • 自启动管理:需要将应用加入“i 管家”中的【自启动管理】列表,否则重启手机后进程不会自启。但强制手动杀进程,即使加了这个列表中,后续进程也无法自启动。

Color OS(OPPO)

  • 冻结应用管理:需要将应用加入纯净后台,否则锁屏状态下无法及时收到消息
  • 自启动管理:将应用加入【自启动管理】列表的同时,还需要到设置-应用程序-正在运行里锁定应用进程,否则杀进程或者开机后进程不会开启,只能手动开启应用

MIUI OS (小米)

  • 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程无法开启
  • 省电策略:需要禁用应用省电策略,否则后台几分钟后会被系统限制联网
  • MIUI 7 神隐模式: 允许用户设置后台联网应用,开启后应用即可在后台保持联网,否则应用进入后台时,应用无法正常接收消息。【设置】->【电量和性能】->【神隐模式】

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球 : 欢迎加入,多谢支持~

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 技术名词解释
    1. 1.1. 进程启动
    2. 1.2. 自启动
    3. 1.3. 关联启动
    4. 1.4. 启动阻断
  2. 2. 分析手段
    1. 2.1. Monkey
    2. 2.2. EventLog
    3. 2.3. Dumpsys
  3. 3. 自启动的技术分析
    1. 3.1. 开机广播
      1. 3.1.1. 案例: 腾讯新闻监听开机广播拉起后台进程
    2. 3.2. 网络变化
      1. 3.2.1. 案例
    3. 3.3. 媒体库扫描
      1. 3.3.1. 案例: jd 监听 MEDIA_SCANNER_STARTED 广播自启
    4. 3.4. 三方 SDK - 个推
  4. 4. 关联启动的技术分析
    1. 4.0.1. 案例:起点 App 通过极光推送拉起 QQ 阅读
    2. 4.0.2. 极光官方文档唤醒配置
    3. 4.0.3. 极光关联启动文档
  • 5. 手机厂商应对
    1. 5.1. 极光推送白名单配置
    2. 5.2. 个推白名单配置
      1. 5.2.1. EMUI OS(华为)
      2. 5.2.2. Flyme OS(魅族)
      3. 5.2.3. Funtouch OS(VIVO)
      4. 5.2.4. Color OS(OPPO)
      5. 5.2.5. MIUI OS (小米)
  • 6. 关于我 && 博客