恰逢項目小版本要上一個 極速版輕應用內嵌於主端 APP 內,在 APP 啓動時經過讀取配置決定是否優先顯示輕應用界面。所以,在啓動 APP 時須要實時進行不一樣場景切換。android
一開始咱們經過新增一個 LauncherActivity 用於中轉不一樣場景的切換,原 APP 主頁面處理任何 Intent 的邏輯將須要從舊啓動頁進行 「繼承」 處理。這意味着 LauncherActivity 收到任何 Intent 處理的邏輯需從舊啓動頁代碼中拷貝。在繼承 LauncherActivity 以後咱們發現 一些第三方推送好比小米,華爲在進程被殺死的狀況下點擊通知欄拉起 APP,其 Intent 並無攜帶任何業務邏輯進而致使沒法精準跳轉業務頁面。 同時當 APP 處於輕應用頁面時收到推送需直接跳轉到原主端二級頁面時,這些頁面的依賴尚未被主端初始化進而致使 Crash !微信
在該版本作 MTL 兼容測試和業務迴歸測試時發現:LauncherActivity Intent 處理上存在不少小細節問題,頻繁的 「提單-修改-測試-迴歸」 的成本很大。在通過權衡以後決定不採用 LauncherActivity 方案來解決新版本調整致使問題,同時研究細化 Intent 類型來解決第三方推送通知時沒法監聽點擊通知欄消息的問題。app
大部分問題都是由於原啓動流程發生更改致使的,故不採用 LauncherActivity,而是在舊主頁面 onCreate方法 在開頭判斷是否須要拉起 極速版輕應用 。這樣的好處是:先拉起輕應用能夠快速顯示同時主頁面流程繼續走,好比一些 token 校驗,登錄信息等行爲。ide
因爲第三方推送 華爲/小米/魅族 等都開放了各自推送 SDK,不一樣的 ROM 通過測試發現:當 app 沒有被啓動狀況下點擊通知欄推進信息拉起應用時回調信息不一,有些甚至沒有回調。測試
下面是通過測試並總結一些拉起場景。職業規劃
因爲被拉起的主頁面的 Intent 通常包含如下信息設計
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
全部拉起場景只能經過 Intent 的其餘信息來區分。經調試, FLAG 爲目前最有效的切入口。頁面啓動時攜帶標示 FLAG_ACTIVITY_XXX。把拉起場景大體區分爲 4 種場景。調試
安裝以後點擊打開拉起code
FLAG_ACTIVITY_NEW_TASK FLAG_RECEIVER_FOREGROUND
點擊桌面圖標拉起繼承
FLAG_ACTIVITY_NEW_TASK FLAG_RECEIVER_FOREGROUND FLAG_ACTIVITY_RESET_TASK_IF_NEEDED FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
第三方推送在 APP 被徹底殺死前提下拉起
FLAG_ACTIVITY_NEW_TASK FLAG_RECEIVER_FOREGROUND FLAG_ACTIVITY_SINGLE_TOP FLAG_ACTIVITY_REORDER_TO_FRONT FLAG_RECEIVER_REPLACE_PENDING
APP 在後臺被拉起,但不一樣推送 SDK 可能出現回調不同的狀況
onNewIntent方法 中回調
FLAG_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_BOOT
onCreate方法 中回調
FLAG_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_ACTIVITY_RESET_TASK_IF_NEEDED 和 FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS,其次再判斷是否爲 被動冷啓動 和 被動熱啓動。
下面封裝了一段代碼能夠直接使用。
public enum AppStartType { NONE, //不屬於拉起 AFTER_INSTALL, //安裝後啓動 LAUNCHER, //Launcher啓動 COOL_BE_OPEN, //被動冷拉 HOT_BE_OPEN //被動熱拉 } @NonNull public static AppStartType parseStartType(Intent intent) { parseIntent(intent, "打印intent"); if (intent != null) { int flags = intent.getFlags(); if ((flags & FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK && (flags & FLAG_RECEIVER_FOREGROUND) == FLAG_RECEIVER_FOREGROUND) { flags = flags ^ FLAG_ACTIVITY_NEW_TASK ^ FLAG_RECEIVER_FOREGROUND; if (flags != 0) { if ((flags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == FLAG_ACTIVITY_RESET_TASK_IF_NEEDED && (flags & FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) { return AppStartType.LAUNCHER; } else if ((flags & FLAG_RECEIVER_REPLACE_PENDING) == FLAG_RECEIVER_REPLACE_PENDING) { if (((flags & FLAG_ACTIVITY_CLEAR_TOP) == FLAG_ACTIVITY_CLEAR_TOP && (flags & 0x04000000) == 0x04000000) || (flags & 0x00400000) == 0x00400000) { return AppStartType.HOT_BE_OPEN; } if ((flags & FLAG_ACTIVITY_REORDER_TO_FRONT) == FLAG_ACTIVITY_REORDER_TO_FRONT) { return AppStartType.COOL_BE_OPEN; } return AppStartType.NONE; } else { return AppStartType.NONE; } } else { return AppStartType.AFTER_INSTALL; } } } return AppStartType.NONE; }
值得注意的是,被動熱拉 在拉起主頁面不一樣廠商的 SDK 回調不同,所以還須要在 onCreate方法 或者 onNewIntent方法 判斷下。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 防止安裝完直接點擊打開而後Home鍵回到桌面再點擊圖標緻使的多個實例 // (getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0 if (!isTaskRoot()) { ActivityUtils.AppStartType startType = ActivityUtils.parseStartType(getIntent()); if(startType == ActivityUtils.AppStartType.HOT_BE_OPEN){ //處理業務邏輯 } finish(); return; } ...... } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); ActivityUtils.AppStartType startType = ActivityUtils.parseStartType(getIntent()); if(startType == ActivityUtils.AppStartType.HOT_BE_OPEN){ //處理業務邏輯 } }
上述遇到的問題是在迭代極速版本時到的,但實際上該問題早就存在於項目中,只是恰好在新業務加入時問題被放大到不得不解決的程度。組內其餘同事也嘗試過解決,但未果,最後缺陷單落在我這裏。
一直以爲 「系統是人設計的,代碼是人寫的,你想到的問題設計者應該也能想到,既然如此,問題出在代碼,答案也應該在代碼裏面」。 經過問題的表象追蹤到推送 SDK 的技術文檔對比,到調試不一樣 ROM最終鎖定由 Intent 拉起差別化 致使的結果。既然是 Intent,那一定可解析 Intent 來尋找多個場景的差別化,既然解析了 Action/Bundle/Categories 沒法找到差別點,就嘗試源碼是否還有更多信息提示,在 Flags 方向上嘗試,解析出每一個場景對應的 Flags 並參考源碼註釋來分析,最終不負一番苦心解決了這個問題。
想一想上個讓我徹夜難眠的 BUG 仍是去年年底。這個想法是我寫代碼以來處理任何問題的背後念頭。有些問題,可能只是本身的知識面不足,又或許是解決問題的方向一開始就不對,只要有足夠的時間給我思考,一定能解決遇到的難題!
若是文章對你幫助,歡迎點贊評論。
歡迎關注 「Android之禪」公衆號,和你分享有價值有思考的技術文章。 可添加微信 「Ming_Lyan」備註 「進羣」 加入技術交流羣,討論技術問題嚴禁一切廣告灌水。 若有 Android 領域有遇到技術難題亦或對將來職業規劃有疑惑,一塊兒討論交流。 歡迎來擾。