1. 入坑之前,先弄明白 EMAS 到底替你干了什么
先说结论:EMAS 是阿里云的一个移动研发平台,集合了推送、崩溃分析、远程日志、热修复、移动测试、移动监控这一整套东西。也就是说,它不是一个单一 SDK,而是一条完整的移动开发支撑链——从你 App 打包出去的那一刻起,到线上用户手里跑到崩溃、收不到推送、卡顿,再到你事后排查问题,各个环节它都沾边。
我见过很多团队接入 EMAS 的动机各不相同:有的是公司要求统一用阿里云全家桶,有的是被推送和崩溃分析这两个刚需拉进来的,还有的是因为自带设备兼容性测试能力,想省掉一部分外包测试费用。不管哪种原因,入坑之前你最需要想清楚的一件事是:你究竟是缺某一个具体能力,还是缺一整条运维链路。
如果只是缺推送,那 EMAS 的移动推送只是选项之一,你完全可以对比友盟、极光、个推,甚至厂商自带通道。如果缺崩溃分析和日志排查,那你也要评估,是自建一套日志上报体系,还是直接用现成的托管服务。我当时选择 EMAS,主要看中的是两点:第一,推送、崩溃、日志、热修复都在一个平台里,账号体系统一,不用在好几个后台之间来回切;第二,阿里系服务在国内的节点覆盖和稳定性确实经历过大量真实业务验证,比自建省心得多。
但请注意,"省心"是相对的。接入 EMAS 不等于躺平,华为、小米、OPPO、vivo 四家厂商通道要一个一个去申请,通知栏权限要适配,Android 8.0 以上的通知渠道要处理,iOS 的 APNs 证书要配置。这一系列事情,每一个位置都有可能踩坑。接下来的内容,就是我从配置依赖、首启崩溃、推送到达率一直折腾到热修复限制的完整记录。
对于适合谁来参考,我的建议是:如果你正在做 Android 和 iOS 双端应用,并且团队里缺少专门做推送基建或崩溃监控体系的同学,这篇文章值得仔细看一遍;如果你只用其中一个能力,重点看对应章节即可。文章里的原理和排查思路,就算你最后不用 EMAS,换成其他平台,也是一样通用的。
2. 接入期的坑:从依赖配置到首启崩溃
2.1 依赖配置:仓库地址和版本号的坑,最容易在开局就卡住
先说结论:EMAS 的 Android SDK 并没有全部放在 Maven Central 或 Google 的仓库里,而是发布在阿里云的公共仓库,所以你需要手动在项目级 build.gradle 里加上 maven 仓库地址。这一步如果漏了,同步 Gradle 的时候就会报找不到 com.aliyun.ams:alicloud-android-push 之类的依赖错误。
我当时遇到的一个典型问题是:公司项目里同时用了集团内部的私有仓库,还有 JCenter 的历史依赖。JCenter 在 2021 年进入只读模式之后,很多老项目还挂着 jcenter() 不放,导致依赖解析时先去 JCenter 找,找不到再切到阿里云仓库,整个同步过程慢得离谱,偶尔还会因为网络波动直接超时。这个问题的解法很简单但很隐蔽:把阿里云仓库放到仓库列表的最前面,或者干脆把 JCenter 从配置里移除。因为 EMAS 的 SDK 依赖是肯定从阿里云仓库拉的,放前面能减少无谓的仓库访问。
版本号这块也有讲究。EMAS 的 SDK 更新频率不算低,主版本每个季度左右会有一轮,但小版本迭代很快。我的建议是不要锁定太死——比如用 2.0.0 这种精确版本,也不要全用 + 号通配。前者升级要手动改,后者某个小版本如果是非正式发布版本,可能会带来不可预期的行为。我常用的策略是锁定主版本,比如 3.x.0,然后定期看发布记录决定是否升级。另外,Android SDK 和 iOS SDK 的版本并不是必须完全一致,但同一套账号体系里的配置项最好对齐,否则后台下发的一些参数可能解析异常。
2.2 初始化时机:Application 里提前初始化是常识,但顺序还是有讲究
EMAS 的 SDK 要求在主进程的 Application.onCreate() 里做初始化,这一点官方文档写得比较清楚。但实际接入的时候,有个容易被忽略的细节:如果你的 Application 里还集成了其他第三方 SDK,初始化顺序会影响崩溃率。
我踩过一个真实案例:某团队在 Application 里先初始化了某个性能监控 SDK,再初始化 EMAS,结果那个监控 SDK 在 Android 8.0 以上的设备上启动时偶发空指针,导致 EMAS SDK 没初始化成功。由于 EMAS 初始化失败不会直接崩溃,后台表现为推送偶尔收不到、崩溃数据时有时无,排查了整整两天才发现是初始化被前置崩溃打断了。后来我把 EMAS 的初始化挪到第一行,并且对它的初始化结果做了布尔判断,问题立刻消失。
这里想提醒大家一个原理:EMAS 的各个子 SDK(推送、崩溃分析、远程日志、热修复)都是围绕一个统一的核心初始化的,核心没有跑起来,所有子模块都不会工作。所以初始化顺序不只是写完代码就完事了,还要考虑其他 SDK 的相互影响。稳妥的做法是,把 EMAS 初始化放在 Application 里所有依赖它的逻辑之前,并且用一个 try-catch 包住,即使初始化失败也不影响 App 主流程。这个习惯后来帮我在另一个项目上快速定位了问题。
2.3 混淆规则:启动崩溃的高发区,也是最容易被忽略的一环
接 EMAS 的 SDK,开启代码混淆和资源压缩之后,如果不加混淆规则,崩溃率会突然升高,而且堆栈信息往往牛头不对马嘴。我见过最典型的情况是:App 开启 minifyEnabled true 之后,第一次推送点击通知栏跳转时,直接抛 ClassNotFoundException。
这是因为推送 SDK 里用了大量反射和动态加载的逻辑,混淆之后类名被重写为 a、b、c 这种短名,反射拿不到正确的类,于是崩溃。EMAS 官方文档里给了完整的混淆规则,但很多人的问题是:项目里已经有一堆自有的 keep 规则,和官方规则合并之后产生了冲突。比如某团队在 proguard-rules.pro 里写了 -keep class com.aliyun.** {; },但漏了 com.alibaba.sdk.android.* 这个包名,推送和热修复相关的类照样被混淆掉。
根据我的经验,混淆规则不是直接复制粘贴就完事,你需要确认以下几点:第一,是否 keep 了 EMAS 相关的包名,包括 com.alibaba.sdk.android 和 com.alicloud 等前缀;第二,是否 keep 了消息接收的 Receiver 和 Service,因为它们需要在 AndroidManifest.xml 里被系统识别;第三,是否 keep 了所有 model 类,否则推送到达后台数据反序列化时会出错。另外,如果你用了 AndResGuard 这类资源混淆工具,要特别注意资源和动态注册的兼容性,EMAS 部分组件在资源混淆之后可能出现找不到资源 ID 的问题,需要在工具配置里排除掉相关目录。
2.4 多渠道打包与环境隔离:一个包名引发的数据混乱
多渠道打包是 Android 的传统艺能了,但 EMAS 有一个特殊性:它在初始化时会读取 AppKey 和 AppSecret,这两个参数如果通过 AndroidManifest 里的 meta-data 声明,多渠道打包时就必须按渠道覆盖这个值,否则多个渠道包会用同一套账号。
我之前踩过一个更隐蔽的坑:测试环境用的 EMAS 项目和生产项目共用同一个包名但不同 AppKey,为了方便测试,我们在 Manifest 里动态替换了 meta-data。结果有一次发版时,打包脚本写错了渠道和 AppKey 的映射关系,导致线上极少数用户上报数据混到了测试项目里,测试后台收到了真实的线上崩溃数据。这会影响什么呢?如果你用崩溃分析里的版本筛选来做线上质量评估,混数据会让某个版本的崩溃率虚高,误导开发判断。
所以我的方案是:每个环境建独立的 EMAS 项目,并且把 AppKey 和 AppSecret 配置放到构建脚本里动态注入,而不是直接在源码里写死。加上 android.applicationVariants.all 的配置,在打包时根据 buildType 自动替换 Manifest 占位符。这样既能保证环境隔离,又不会因为人为修改出错。
3. 移动推送的深水区:送达率和通知展示的玄学
3.1 厂商通道:不接入厂商通道,等于一半用户收不到推送
移动推送是 EMAS 使用率最高的模块,但也是"玄学"最多的地方。我接 EMAS 推送时做的第一件事就是把华为、小米、OPPO、vivo 四家厂商通道全部接上。为什么这么重要?在国内 Android 生态里,Google 的 FCM 基本不可用,如果你的 App 进程被杀掉,光靠 EMAS 自己的长连接通道,推送根本到达不了。只有通过厂商系统级的推送通道,才能在 App 死后把消息塞到通知栏。
这个环节的第一个坑是资质申请。每个厂商都有自己的推送平台,需要注册开发者账号、创建应用、获取 AppID 和 AppKey,然后把它们填到 EMAS 后台的厂商通道配置里。华为的申请比较快,小米要求应用在应用商店上架后才能开通正式权限,OPPO 和 vivo 的审核周期看人品,最长可能等一周。我建议在项目立项时就启动厂商通道申请,不要等到快发版才做。
第二个坑是厂商通道的消息限制和模板要求。比如小米推送对通知栏消息有折叠策略,OPPO 要求通知渠道优先级设置正确,vivo 对某些消息类型需要额外配置分类。这些限制官方文档不会全部写清楚,是需要你实际测试各厂商设备才能发现的。我当时在某个老款 OPPO 手机上测试,发现推送到达了但通知栏不显示,排查半天才发现是通知渠道被系统默认设为静默模式,需要在代码里手动指定 IMPORTANCE_HIGH。
3.2 Android 8.0 以上通知渠道:不创建渠道,通知栏直接不显示
这是 EMAS 推送接入里最经典的坑,没有之一。Android 8.0(API 26)开始,所有通知必须绑定一个 NotificationChannel,如果你创建通知时没有指定渠道,或者指定了一个不存在的渠道,通知会被系统直接丢弃。
EMAS 在部分版本里提供了默认渠道,但默认渠道的优先级可能不满足业务需求。举个例子:你在后台推送一个优惠活动,希望在通知栏有声音、有震动,如果 EMAS 使用的是默认的 IMPORTANCE_DEFAULT 渠道,声音可以出来,但部分国产 ROM 会拦截,导致用户没感知。根据我的实际操作,正确做法是在 App 启动时主动创建自己的通知渠道,设置好 importance 和声音,然后把这个渠道 ID 配置到 EMAS 后台的消息推送设置里。
这里要提一点:NotificationChannel 一旦创建,渠道的 importance 就不能再改了。所以你如果一开始用 IMPORTANCE_LOW 创建了渠道,后面想变成 IMPORTANCE_HIGH,只能通过 App 升级来改变。所以渠道的初始化代码要早想清楚,设计时按消息类型区分渠道,比如普通消息、营销消息、重要消息各自建一个,别混在一起。
3.3 离线消息、厂商通道回执与送达率的关系
推送送达里最让人头大的问题是:后台显示"已送达",但用户就是收不到;或者后台显示"已发送",但用户没收到通知。这两者在 EMAS 后台有不同的状态含义。已送达意味着消息已经推给设备端或者厂商通道,但不代表用户看到了通知。厂商通道还会返回回执,但回执有时会有延迟,尤其是在厂商系统繁忙的时候。
关于离线消息要特别解释一下。EMAS 推送本身支持离线消息存储,默认存 72 小时,也就是说用户离线期间收到的推送,会在下次联网时补发。但厂商通道不受 EMAS 离线存储时间控制,各自有自己的策略。比如小米的离线消息可以存 7 天,OPPO 可能是 3 天,vivo 在某些系统版本上对应用被销毁后的推送支持并不稳定。所以如果你的业务对推送时效非常敏感,建议在设计时提前评估离线时间窗口,别指望所有通道行为一致。
还有一个细节是:通知栏消息和静默消息要分清。通知栏消息是用户能看到的;静默消息是只把数据传给 App,不展示通知。很多团队用推送来触发 App 内部某个动作,比如刷新数据,就会用静默消息。但是国产 ROM 对静默消息的限制比通知栏消息严得多,进程被杀后大概率收不到。我的建议是:触发内部动作的推送消息,也尽量用通知栏消息,用户点击进去之后再执行动作,这样送达率才有保障。
3.4 推送到达率排查的三板斧
排查推送收不到的问题,不要上来就怀疑 SDK,按下面的顺序来:
第一板斧:确认厂商通道状态。在 EMAS 后台的"诊断工具"里能看到设备绑定的通道状态。如果是"未开通"或"通道失效",基本就是厂商通道配置或应用被系统冻结的问题。第二板斧:确认设备端日志。把 EMAS SDK 的日志开关打开,过滤 PushService 相关 tag,看设备是否有收到服务端下发的消息,如果收到了但没展示通知,问题大概率在通知渠道或厂商 ROM 限制上。第三板斧:确认 AndroidManifest 里的 Receiver 没有被系统或安全软件禁止。部分 ROM 会在用户手动停止应用后,禁止所有广播唤醒,这种情况只能引导用户在系统设置里开启自启动权限。
这三板斧走完,90% 的推送问题都能定位到具体环节。剩下 10% 是厂商系统级 bug,只能尝试升级系统或者联系厂商客服,这类问题不用太纠结。
4. 崩溃分析、远程日志与热修复的组合拳
4.1 崩溃分析:符号化是让堆栈可读的最后一公里
EMAS 的崩溃分析模块会自动捕获 Java 崩溃、Native 崩溃和 ANR。但有个关键操作很多人会漏掉:上传符号表。Native 崩溃如果没有符号表,堆栈显示的是一堆内存地址,根本看不出是在哪个函数里挂的。要拿到可读的 Native 堆栈,你需要在 EMAS 后台上传对应版本的 so 符号文件。
我在接入崩溃分析初期就吃过这个亏。第一次看到 Native 崩溃堆栈时,一行地址、一个模块名,毫无头绪。后来才发现 EMAS 后台要求上传带符号信息的 so 文件,路径通常在 build/intermediates/merged_native_libs 下。上传之后,堆栈立刻可读,定位到是一个图片解码库在特定分辨率下 OOM 挂掉的。这个过程里我还发现一个坑:不同 ABI(arm64-v8a、armeabi-v7a、x86)的 so 文件要分开上传,否则只能看到部分架构的符号。发布的包如果同时包含多架构,这几个架构的符号表一个都不能少。
Java 崩溃相对好处理,EMAS 会自动解析堆栈。但为了排查效率,我建议把崩溃分析里的自定义参数用起来。在崩溃发生前,把关键业务状态和用户操作路径标记到自定义参数里,比如"当前页面=支付页"、"网络状态=弱网"、"进入方式=首页 banner 点击"。这样崩溃分析后台会显示这些上下文,相当于给每次崩溃都附带了一张操作截图。这套逻辑帮我们复现过一个只在特定页面特定入口出现的空指针问题。
4.2 远程日志:崩溃现场还原的关键拼图
远程日志是我后来才认真用的功能,现在回头看,早期排查崩溃效率低就是因为没用好它。它的原理很简单:客户端在本地记录日志,并周期性上传到服务端,崩溃后你可以根据崩溃时间、设备 ID 去查对应的日志。
有一个特别实用的场景:崩溃分析的堆栈只能告诉你哪里崩了,但不一定能告诉你为什么崩。比如一个网络请求回调里参数为空的崩溃,你需要知道崩之前服务端返回了什么、客户端解析了什么。这时候远程日志就派上用场了。我通常会在关键业务流程里打日志,包括网络请求入参、返回结果、页面跳转参数等。注意日志不要打敏感数据,埋点内容要注意合规。
远程日志和崩溃分析配合使用有一个经验:崩溃发生后,第一时间去查这台设备的远程日志,重点看崩溃前 2 分钟内的输出。因为很多时候崩溃是一个多步操作后的结果,前因后果都在日志里。我自己还习惯在每个网络回调开始时打一行"请求开始"+ 时间戳,在回调结束打一行"请求成功/失败"+ 耗时。这套日志格式帮我快速定位过不少问题,比对着后台数据猜效率高多了。
4.3 热修复的双刃剑:能用和不能用之间,有一条清晰红线
移动热修复是 EMAS 里最需要谨慎使用的模块。它的原理是下发补丁包,在下次启动时替换已经加载的类,从而修复线上 bug。听起来很爽,但它的限制远比你想象的多。
首先,热修复不支持新增类。你可以修改现有类的实现,但不能新增类、不能修改 AndroidManifest.xml、不能修改资源文件。什么意思?如果你要修复的 bug 需要新增一个类,或者需要在清单里新增一个权限,热修复方案就不适用。这是很多团队评估时容易忽视的一点。
其次,热修复和 Google Play 政策的兼容性问题。热修复相当于动态加载代码,这在 Google Play 的审核政策里是受限制的。如果你的 App 要上海外市场,用了热修复可能会被拒或警告。所以海外版本我们完全没有接入热修复,只在国内版本启用。这算是一条清晰的红线。
还有一点是灰度发布的问题。补丁包下发后,不能一下子全量推送。EMAS 支持按用户、按版本灰度,但我在实际操作中发现,灰度比例设得太小,可能出现的问题不容易暴露。比如补丁包在低端机型上可能会导致启动变慢,灰度 1% 的用户根本没覆盖到低端机,结果全量之后就爆了。我的建议是灰度至少覆盖 5%-10%,并且选一个活跃用户量比较大的版本先发一轮,观察崩溃率和启动耗时,再决定继续扩大。
热修复测试起来也比较麻烦。你需要先在本地构造一个会崩溃的场景,然后打入修复后的补丁,验证补丁生效且没有新的副作用。这个流程要固化到发版流程里,专门准备一台低端 Android 测试机,专门用来验证热修复。如果团队里没有这个资源,我建议谨慎使用热修复,宁可发版频率高一点,也不要赌一个不可逆的线上风险。
5. 常见问题与排查技巧实录:一张速查表,省掉三天的头发
把我在接入 EMAS 后遇到的高频问题整理成了一张速查表,方便你直接按图索骥:
| 现象 | 可能原因 | 排查方向 | 解决建议 |
|---|---|---|---|
| 构建时找不到 EMAS SDK 依赖 | 仓库地址未配置或仓库顺序错误 | 检查项目级 build.gradle 的 repositories | 把阿里云仓库放到最前,移除 JCenter |
| 启动时崩溃,报 ClassNotFoundException | 混淆规则未配置完整 | 检查 proguard-rules.pro 是否覆盖 SDK 包名 | 补充 com.alibaba.sdk.android 等 keep 规则 |
| App 启动后 EMAS 功能都不工作 | 初始化失败或时序错误 | 检查 Application 中初始化是否被前置异常截断 | 初始化放最前,添加 try-catch 和状态判断 |
| 测试环境和生产环境数据混乱 | 多环境共用同一 AppKey 或配置替换错误 | 检查打包脚本的 meta-data 映射关系 | 每个环境独立项目,构建时动态注入配置 |
| 后台显示送达但用户看不到通知栏 | NotificationChannel 未创建或优先级过低 | 检查设备端是否创建了渠道并设置了 importance | 主动创建多个业务渠道,后台配置渠道 ID |
| 推送在 App 被杀后收不到 | 未接入厂商通道,或厂商通道配置失效 | 确认 EMAS 后台的通道状态和设备端绑定情况 | 完整接入华为、小米、OPPO、vivo 通道 |
| 厂商通道消息到达但样式异常 | ROM 对通知样式有限制 | 检查各厂商随附功能和通知渠道设置 | 按厂商文档适配,优先使用系统默认样式 |
| Native 崩溃堆栈全是内存地址 | 未上传符号表或 ABI 不匹配 | 检查 EMAS 后台的符号表上传情况 | 为每个 ABI 分别上传对应 so 的符号文件 |
| 崩溃分析缺少上下文信息 | 未添加自定义参数 | 在关键业务流程设置自定义崩溃参数 | 崩溃捕获前标记页面、网络、操作路径等 |
| 热修复补丁无效或副作用 | 触发了热修复的限制(新增类/资源) | 检查补丁内容是否超出可修复范围 | 严格按热修复限制评估,小比例灰度并观察指标 |
这张表里的问题,我个人碰到最多的是混淆和通知渠道这两类。老实说,混淆规则的问题最好解决,因为官方文档明确写了,只是很多人不看文档直接接。通知渠道的问题则需要在实际机型上验证,因为国产 ROM 的差异实在太大了,有时候同一个品牌不同系统版本行为都不一样。
在排查工具方面,EMAS 后台自带的诊断工具比大多数第三方工具都实用,尤其是"设备查询"和"消息轨迹"这两个入口。输入设备 ID 和消息 ID,就能看到一条消息从服务端到厂商通道再到设备端的完整状态流转。排查推送问题的时候,我第一件事就是去查消息轨迹,判断卡在哪个环节。
经验之谈,接入任何第三方服务,都不要在出问题的时候才去翻文档,至少在项目接入阶段把所有官方文档的"注意事项"和"常见问题"通读一遍。EMAS 的文档虽然不完美,但大部分坑都在常见问题里出现过,你提前看一遍,至少能少走一半弯路。
6. 根据我的经验,接这类平台最值得记住的几件事
如果只让我分享三条最核心的体会,我会这么说:
第一,所有云厂商的 SDK 都不是即插即用,它是一个需要持续维护的依赖项。你不可能接完就不管了。厂商通道的密钥会过期、SDK 会出新版本、Android 系统会上新版本、厂商 ROM 会改行为,这些都是持续性的维护工作。团队里最好有专人负责这些组件的更新和监控,不要等到线上出问题再临时抱佛脚。
第二,不要迷信后台的数据。EMAS 后台的数据指标是产品团队设计出来的,和真实用户体验之间存在差异。比如推送"送达率"可能只代表设备接收到了消息,不代表用户看到了通知;崩溃率的统计口径也可能受到上报时机的影响。你需要自己设计一套抽样验证的方式,比如定期用真机测试推送,或者通过用户反馈来交叉验证后台数据。
第三,接入之前的架构设计,决定接入之后踩多少坑。环境隔离、多渠道配置、混淆规则、通知渠道规划,这些如果在一开始就设计好了,后面确实能少很多麻烦。我见过太多团队为了快速上线而跳过这些前期设计,最后返工花费的时间远超预期。
最后补一句:如果你在纠结选 EMAS 还是别的平台,建议把重点放到"平台提供的核心能力是否匹配你的业务模型"上,而不是谁的 SDK 接入更快。推送和崩溃分析这类底层能力,一旦接进去,迁移成本相当高。前期多花两天做技术选型,好过接入之后再一根一根拔钉子。