头图

作者:极光高级工程师——史坤坤

企业出海图景

在疫情持续,叠加复杂多变的国际贸易环境下,中国对外直接投资流量和存量连续四年稳居全球前三,近八成中国企业将维持和扩大对外投资意向,看好对外投资前景。企业出海,第一要务就是要建立与用户的触达通道。在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自己实现消息推送相比,就没有高可用的通道服务的优势了,难道只是为了帮助开发者简化推送服务的实现流程,降低开发成本?带着这个疑问,我们展开了专项测试和分析。分析过程如下:

  • 验证进程存活与被杀死情况下,消息的展示情况的现象。
  • 分析消息的生命周期状态
  • 深入分析系统源码、GMS源码、FCM源码
  • 总结结论

下面是具体的分析过程,涉及到系统源码的分析,如果不感兴趣可略过阅读,直接看最后总结的结论。

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/f...  

更多关于海外厂商通道的集成方法,请看下回分解。

关于极光

极光(Aurora Mobile,纳斯达克股票代码:JG)成立于2011年,是中国领先的客户互动和营销科技服务商。成立之初,极光专注于为企业提供稳定高效的消息推送服务,凭借先发优势,已经成长为市场份额遥遥领先的移动消息推送服务商。随着企业对客户触达和营销增长需求的不断加强,极光前瞻性地推出了消息云和营销云等解决方案,帮助企业实现多渠道的客户触达和互动需求,以及人工智能和大数据驱动的营销科技应用,助力企业数字化转型。


极光JIGUANG
1.3k 声望1.3k 粉丝

极光(www.jiguang.cn)是中国领先的移动大数据服务商。其团队核心成员来自腾讯、摩根士丹利、豆瓣、Teradata和中国移动等公司。公司自2011年成立以来专注于为app开发者提供稳定高效的消息推送、统计分析、即时通...