作者:极光高级工程师——史坤坤
企业出海图景
在疫情持续,叠加复杂多变的国际贸易环境下,中国对外直接投资流量和存量连续四年稳居全球前三,近八成中国企业将维持和扩大对外投资意向,看好对外投资前景。
企业出海,第一要务就是要建立与用户的触达通道。在APP环境之外,海外触达用户的通道与国内用户略有区别,海外是以邮件为主要的通道,而国内主要是以短信为主,辅以微信等社交通道。在APP环境之内,海外与国内触达用户的区别不大,均是以APP推送为主。
接下来,我们着重从海外APP推送通道来做一些对比与分析。
APP消息推送通道现状
在海外,手机厂商除苹果外,安卓仍然以三星为领头羊,独占19%的市场份额。小米、OPPO、Vivo、realme紧随其后,市场份额占比分别为12%、9%、8%以及6%。
图一:2021年第四季度全球智能手机出货量报告(数据来自市调机构Conuterpoint Research)
企业APP的消息触达用户的通道,最重要的仍然还是推送通知消息。各个头部厂商对Android原生系统均有所定制,但与国内环境还是有所区别。
在中国大陆,谷歌受地域限制,无法使用谷歌相关服务,国内厂商对GMS服务套件进行了系统层的阉割,谷歌官方的FCM推送通道也相应的无法使用。进而替代的是各个厂商自己的厂商通道。顾名思义,厂商通道就是指手机硬件厂商提供的系统级别的推送通道,因为是系统服务,随着设备开机后就一直存在着,有效的保证了推送通道长连接的高可用性。
在海外,由于安卓系统默认支持谷歌FCM通道,且网络环境不受地域限制,因此厂商未对出口海外版的手机进行GMS的阉割,而是保留厂商通道与谷歌FCM通道共存的方式。谷歌FCM通道,是安卓系统自带的通道服务,与谷歌Firebase后台保持长连接,服务归属是谷歌而非设备厂商。
经实际测试、同时与厂商官方技术沟通后,总结关于通知通道的使用情况如下:
厂商通道与谷歌FCM通道的区别
那么,既然海外的Google服务是不受限制的,为什么厂商还要保留两个通道呢?他们有什么具体的区别吗?确实是存在一些区别。
区别一:就是上边提到的通道归属服务方不同,厂商通道由硬件设备厂商提供服务,谷歌FCM通道由谷歌官方提供服务。
区别二:厂商通道,在网络通畅且推送消息内容合法的情况下,通过厂商通道推送消息给该厂商设备,不论应用进程是否存活,都能保证消息可以推送到位。所以,厂商通道是消息高效触达的一种保障。同时,也对企业用户的拉活、促新、留存有一定的提升。而FCM通道,通过Firebase后台推送消息给安卓设备时,设备收到消息后,会先根据应用的某种状态来决定是否展示消息。如果APP进程被用户主动杀死,将不会继续进行展示推送消息。
对于这个规则,起初我们也是持有怀疑态度,如果这样的话,那与APP自己实现消息推送相比,就没有高可用的通道服务的优势了,难道只是为了帮助开发者简化推送服务的实现流程,降低开发成本?带着这个疑问,我们展开了专项测试和分析。分析过程如下:
下面是具体的分析过程,涉及到系统源码的分析,如果不感兴趣可略过阅读,直接看最后总结的结论。
01. 验证进程存活与被杀死情况下,消息的展示情况的现象
a. 退入后台,无操作停留2小时
通过usb连接手机,进入ADB模式,执行ps命令后,进程仍然存在,说明并未被系统回收资源。此时发送推送消息,如预期,通知消息正常展示。
b. 退入后台,通过最近任务,滑动杀死APP
同样,进入ADB后,通过ps命令查看进程状态,进程已不存在,但不确定资源是否被系统及时回收。此时发送推送消息,通知消息能正常展示。还比较符合厂商通道的优势特征。
c. 进入应用详情,强行停止APP
不出意料的,进程肯定已经不存在了,ps命令查看也是如此。同样的,不能确定资源是否被系统及时回收。此时发送推送消息,通知消息却不能正常展示。由此推测,FCM通道不依赖APP的进程是否存活。这个特点,是优于APP自己实现推送通知的。
d. 重启手机
重启手机后,由于应用已没有接收重启系统的广播权限,进程肯定已经不存在了,同时,资源肯定也是被系统回收了的。此时发送推送消息,通知依然能正常展示。由此进一步确认,系统缓存了应用的某种状态,FCM会依据该状态来决定是否展示通知。
02. 分析消息的生命周期状态
有了以上试验的现象,那么接下来重点分析下为什么强行停止APP后,就不能正常收到通知并展示了。
首先看推送API的响应,正常。推送任务已提交至谷歌Firebase后台,姑且认为服务器已下发到设备。
通过adb logcat >log.log 抓取现场系统日志,在log中有以下打印:
16600 16600 W GCM : broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg=cn.jiguang.junion.jpushtestdemo (has extras) }
确认消息已正常送达到了设备,因此怀疑GMS的消息广播就没有正常发出来。
03. 深入分析系统源码、GMS源码、FCM反编译代码
先根据FCM 方法调用链,从消息分发处入手往上跟进,确认其调用步骤。
a. FirebaseMessagingService
在系统源码中跟踪到FirebaseMessagingService 负责分发通知消息到SDK。
b. EnhancedIntentService 负责解析处理收到的消息
c. 继续分析消息是哪里接收来的,跟踪到是通过AIDL形式把广播中的消息传到Service中的
然后发送到FirebaseInstanceIdReceiver
d. 最终的广播来源CloudMessagingReceiver
因此可以明确知道:FCM 的消息也是从广播来的。
04. 总结结论
最终,在AMS中找到了答案。应用被用户主动kill后,系统直接把死亡进程所属的广播,都直接过滤掉了,从而不对其发送广播。
在Android系统中,应用被用户主动kill后,在ams 中会调用finishForceStopPackageLocked()中 发送内部广播:ACTION_PACKAGE_RESTARTED, 它会限制包的自启或者通知移除等等。而广播是通过sendBroadcast来发送的,在AMS broadcastIntentLocked 中,明确添加了intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);该Flag后续会从系统中查找缓存,过滤广播的代码:
registeredReceivers = mReceiverResolver.queryIntent(intent,resolvedType, false /*defaultOnly*/, userId);
在IntentResolver.queryIntent方法内部有调用:
final boolean excludingStopped = intent.isExcludingStopped();
if (excludingStopped && isFilterStopped(filter, userId)) { -------这里会过滤Stopped 和进程Stop的
if (debug) {
Slog.v(TAG, " Filter's target is stopped; skipping");
}
}
isExcludingStopped()的定义代码:
public boolean isExcludingStopped() {
return (mFlags&(FLAG_EXCLUDE_STOPPED_PACKAGES|FLAG_INCLUDE_STOPPED_PACKAGES))== FLAG_EXCLUDE_STOPPED_PACKAGES;
}
官方的issues也有类似的官方回复,感兴趣的可以看下:
引自:https://github.com/firebase/firebase-android-sdk/issues/2917
更多关于海外厂商通道的集成方法,请看下回分解。
关于极光
极光(Aurora Mobile,纳斯达克股票代码:JG)成立于2011年,是中国领先的客户互动和营销科技服务商。成立之初,极光专注于为企业提供稳定高效的消息推送服务,凭借先发优势,已经成长为市场份额遥遥领先的移动消息推送服务商。随着企业对客户触达和营销增长需求的不断加强,极光前瞻性地推出了消息云和营销云等解决方案,帮助企业实现多渠道的客户触达和互动需求,以及人工智能和大数据驱动的营销科技应用,助力企业数字化转型。