背景
最近大神项目在做聊天迁移,把原来云信的聊天模块功能替换成 ngpush支持的聊天模块功能。刚好小版本要上一个 “极速版将军令” 的大神,其功能是在启动的时候通过用户配置切换不同的页面分支。如果用户配置了开启极速版,则拉起 “将军令” 页面。否则拉起主页。
问题来源
一开始新增一个 LauncherActivity 用于中转不同场景的切换,原旧版本主页面处理任何意图(intent)的逻辑将需要从 LauncherActivity 进行 “继承” 并处理。这意味着 LauncherActivity 收到任何意图之后处理的逻辑需要从主页面代码中拷贝过来。一些第三方推送比如小米,华为在进程被杀死的情况下进行消息推送拉起,其意图没有携带任何业务逻辑。 另外,当app处于将军令页面时,收到一些推送需要直接跳转到原二级页面,一级主页面此时并没有初始化依赖,这会造成很多隐藏的风险!
迭代了一年多的大神业务繁多,在小版本上做兼容测试和业务测试发现,在 LauncherActivity 意图处理上存在很多小细节问题。频繁的提单-修改-测试-回归的成本很大。在经过权衡之后,决定不采用 LauncherActivity 方案来解决新版本调整导致的依赖问题,同时细化 intent 类型解决第三方推送通知时,无法监听点击通知栏消息的问题。
解决问题
大部分问题都是由于原 app 已启动主页面就进入初始化流程,不存在任何依赖问题。所以不采用 LauncherActivity,而是在原主页面 onCreate() 在开头判断是否需要拉起 “将军令”。这样的好处,先拉起的将军令可以快速显示,同时主页面流程继续走。一些token校验,登陆信息等都可以通过异步回调返回各自页面。通过这种方案可以维持主页面原来处理意图的逻辑。
针对第三方推送,华为/小米/魅族等rom都开放了自己的推送sdk,不同的rom经过测试发现: “当 app 没有被启动情况下,通过点击推送构建的通知栏拉起应用的回调不一,有些甚至没有回调。”
下面是经过测试并总结的一些拉起场景。
由于被拉起的主页面的意图一般包含
1 | <action android:name="android.intent.action.MAIN" /> |
所以所有拉起场景只能通过意图的其他信息来区分,经调试, flag 为目前最有效的切入口。大部分 FLAG_ACTIVITY_* 为页面启动时携带标示。把拉去场景大致区分为:
安装之后点击打开拉起
1
2FLAG_ACTIVITY_NEW_TASK
FLAG_RECEIVER_FOREGROUND点击桌面图标拉起
1
2
3
4FLAG_ACTIVITY_NEW_TASK
FLAG_RECEIVER_FOREGROUND
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS第三方推送在app被完全杀死前提下拉起
1
2
3
4
5FLAG_ACTIVITY_NEW_TASK
FLAG_RECEIVER_FOREGROUND
FLAG_ACTIVITY_SINGLE_TOP
FLAG_ACTIVITY_REORDER_TO_FRONT
FLAG_RECEIVER_REPLACE_PENDINGapp在后台被拉起
不同推送sdk可能出现不一样的情况onNewIntent中回调
1
2
3
4
5
6
7FLAG_ACTIVITY_NEW_TASK
FLAG_RECEIVER_FOREGROUND
FLAG_RECEIVER_REPLACE_PENDING
FLAG_ACTIVITY_SINGLE_TOP
FLAG_ACTIVITY_CLEAR_TOP
FLAG_RECEIVER_REPLACE_PENDING
FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOTonCreate中回调
1
2
3
4
5
6
7FLAG_ACTIVITY_NEW_TASK
FLAG_RECEIVER_FOREGROUND
FLAG_RECEIVER_REPLACE_PENDING
FLAG_ACTIVITY_SINGLE_TOP
FLAG_RECEIVER_FROM_SHELL
FLAG_ACTIVITY_BROUGHT_TO_FRONT
FLAG_ACTIVITY_REORDER_TO_FRONT
从上述4个场景中克制,任意一次拉起都会同时包含 FLAG_ACTIVITY_NEW_TASK
和 FLAG_RECEIVER_FOREGROUND
这两个 flag.而符合这两个条件的优先判断是否同时包含 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
和 FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
。其次再判断被动冷启动,和被动热启动。下面封装了一段代码可以直接使用(持续优化更新).
1 | public enum AppStartType { |
值得注意的是,被动热拉在拉起主页面的时候,不同sdk的回调不一样,意图的判断可能出现在 onCreate() 或者 onNewIntent().
1 |
|
思考
上述的问题是在协同修改极速版本缺陷的时候遇到的,这类问题以前就存在于项目中。在面临新业务加入的时候问题得到放大不得不解决,组内的其他同事也尝试过解决未果。最后单落在我这里,追踪到推送 sdk 文档定位到最终是经过广播传递意图来启动的。既然是有意图,那必定可以解析意图来寻找多个场景的差异化。解析了 Action/Bundle/Categories 无法找到差异化,这也是以前同事的做法。但是多场景拉起,经过系统的分发之后必定应该存在能区分场景差异的内容,最终在所有接收意图的场景下解析所有 Flags,并分析每个场景的 Flags差异及参考源码注释解析来尝试,最终不负一番苦心 Fix 了这个历史痛点。
想想上个让我彻夜难眠的 BUG 还是去年年末。一直觉得 “系统是人设计的,代码是人写的,你想到的问题设计者应该也能想到,既然如此,问题出在代码,答案也应该在代码里面”。这个想法是我写代码以来处理任何问题的背后念头。有些问题,可能只是自己的知识面不足,又或许是解决问题的方向一开始就不对,只要有足够的时间给我思考,必定能解决遇到的难题!