前言
前些天凰棉,測試MM發(fā)現(xiàn)了一個比較奇怪的bug膀哲。
具體表現(xiàn)是:
1往产、將app包通過電腦QQ傳送到手機QQ上面,點擊安裝某宪,安裝后選擇打開app (此間的應(yīng)用邏輯應(yīng)該是要觸發(fā) 【閃屏頁Activity】仿村, 然后跳轉(zhuǎn) 【主頁Activity】)
2、然后MM在 【主頁Activity】 時按下了 【Home鍵】兴喂,回到桌面
3蔼囊、再點擊app的icon圖標,原諒耿直的我們都是覺得應(yīng)該直接回到【主頁Activity】衣迷,但是結(jié)果卻是又一次觸發(fā) 【閃屏頁Activity】畏鼓,亮瞎了24K鈦合金狗眼的我們覺得這玩法不對吧?
4蘑险、然后滴肿,收拾收拾心情開始定位之路吧~
現(xiàn)象分析
先說說項目結(jié)構(gòu)吧,我們這邊的項目需求邏輯是 先進入 【閃屏頁Activity】(普通的Activity佃迄,啟動模式為standard)泼差,然后根據(jù)一堆初始化操作和判斷,一般是接著進入【主頁Activity】(Activity的啟動模式為singleTask)呵俏;點擊home鍵不做任何攔截處理堆缘,按照系統(tǒng)默認邏輯返回Lanuch桌面。
也就是說普碎,app的整體交互邏輯并沒有特殊之處吼肥,并非業(yè)務(wù)邏輯導(dǎo)致的bug。那么回顧下不同的地方,也就是啟動App的入口的區(qū)別了缀皱,一者是平常的桌面Icon圖標啟動斗这,一者是QQ安裝這類第三方平臺啟動。我們都知道啤斗,桌面啟動的話也是通過startActivity這個api通過特定的Intent向ActivityManagerServer發(fā)起啟動任務(wù)表箭;所以我們可以推導(dǎo)出QQ安裝啟動這類方式也是通過Intent啟動對應(yīng)的App。
再往下分析的話钮莲,可能需要一些前置知識需要了解才能更好的理解免钻。
前置知識
1、Activity的Task管理
一般來說崔拥,整個Android系統(tǒng)的App啟動與切換管理依賴于相關(guān)Activity的Task的管理极舔。一個Task之中可能含有若干個Activity,為了簡便起見链瓦,我們這里記錄【Task A】的Activity分別為 【A1】 拆魏、【A2】等,【Task B】的Activity分別為 【B1】 澡绩、【B2】稽揭。
那么我們來分析下App之間是怎么切換的俺附。
假設(shè)應(yīng)用都是單Task應(yīng)用(相對于大部分的普通App來說肥卡,都是采用單一Task來管理的)
桌面程序App:【TaskA】 ---- 存在Activity有【A1】 ---- 其棧的結(jié)構(gòu)為 A1
應(yīng)用程序B:【TaskB】 ---- 存在Activity有【B1】【B2】 ---- 其棧的結(jié)構(gòu)為 B1B2
應(yīng)用程序C: 【TaskC】 ---- 存在Activity有【C1】【C2】 ---- 其棧的結(jié)構(gòu)為 C1C2
a、那么我們進入桌面時:Task之間的結(jié)構(gòu)是 A1 ---- 也就是只有一個【TaskA】棧(桌面Task)事镣,并且位于最前端(這里表現(xiàn)為最后添加的末端)
b步鉴、然后我們點擊應(yīng)用程序B的圖標,啟動B :Task之間的結(jié)構(gòu)是 A1B1B2 ---- 添加了一個【TaskB】璃哟,而且【TaskB】也是位于最前端氛琢,現(xiàn)在顯示的是【TaskB】的B2的Activity的界面
c、接著點擊home鍵: Android對于home做了特殊默認處理随闪,就是會把桌面Task挪到所以Task最前端阳似,Task結(jié)構(gòu)應(yīng)該變成 B1B2A1 ---- 【TaskA】挪到隊列最前端,現(xiàn)在顯示的是【TaskA】的A1的Activity的界面铐伴,也就是桌面
d撮奏、我們再在桌面點擊應(yīng)用程序C的圖標,啟動C : Task之間的結(jié)構(gòu)變成 B1B2A1C1C2 ---- 添加了一個【TaskC】当宴,而且【TaskC】也是位于最前端畜吊,現(xiàn)在顯示的是【TaskC】的C2的Activity的界面
從上面的例子,我們可以大致了解到Android是怎么管理不同app之間切換的邏輯:
我們編寫任何一個Activity的時候户矢,都可以在AndroidManifest里面顯式指定一個taskAffinity的屬性玲献,也就是說該Activity歸屬于對應(yīng)taskAffinity的棧;如果沒有指定任何taskAffinity,那么該Activity將會直接歸屬于包名所在的Task之下捌年。而我們啟動一個Activity時(這里只討論standard啟動模式)瓢娜,那么回去先搜尋對應(yīng)的Task是否存在,如果不存在礼预,新建一個Task并將Activity入棧恋腕,如果已經(jīng)存在對應(yīng)的Task,那么直接在對應(yīng)Task入棧即可逆瑞。
那么問題來了:如果我們在上面第d步點擊的圖片并不是程序C的圖標荠藤,而是重新點擊了程序B的圖標,此時【TaskB】是已經(jīng)存在的了获高,那么為了不會講B的入口activity(B1)直接在【TaskB】入棧哈肖,而是將【TaskB】挪到前臺并不做任何Activity啟動的操作呢?
2念秧、桌面的啟動管理:
回頭研究下AndroidManifest這個文件淤井,我們輕而易舉發(fā)現(xiàn),但凡是App入口Activity摊趾,那么一定會包含
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
這幾行代碼币狠。這里到底有什么玄機呢?其實這個就是跟桌面約定好的啟動攔截過濾器砾层。因為桌面有一個很明顯的需求就是漩绵,如果我們再次點擊已經(jīng)在后臺的App圖標時,是應(yīng)該將該后臺任務(wù)挪到前臺而不是再次啟動該App程序肛炮。
而從柯元旦所著的《android內(nèi)核剖析》一書中有記錄如下規(guī)則:
每次啟動Intent導(dǎo)致新創(chuàng)建Task的時候止吐,該Task會記錄導(dǎo)致其創(chuàng)建的Intent;而如果后續(xù)需要有一個新的與創(chuàng)建Intent完全一致(完全一致定位為:啟動類侨糟,action碍扔、category等等全部一樣,不可多項也不可缺少)秕重,那么該Intent并不會觸發(fā)Activity的新建啟動不同,而只會將已經(jīng)存在的對應(yīng)Task移到前臺;這也就是為什么桌面會在再次點擊圖標時將后臺任務(wù)挪到前臺而不是重新啟動App的實現(xiàn)溶耘。
那么為啥要指定入口Activity特定的action和category呢二拐,有一個原因我們可以確定,就是為了讓桌面啟動app所用的Intent具有特殊性汰具,也就是添加了特別的攔截器卓鹿,避免其他應(yīng)用內(nèi)或者應(yīng)用間的Intent對于這個啟動方式的干擾。
說了這么多留荔,我們可以著手分析上續(xù)bug的產(chǎn)生原因了吟孙。
原理剖析
從此我們可以知道QQ安裝器其實也就是使用Intent來啟動其剛剛安裝的那個App澜倦,但是問題所在的是:他們的啟動Intent并沒有跟桌面的啟動Intent完全一致!
我們將桌面的Task記為【TaskL】杰妓,QQ安裝器的Task記為【TaskQ】藻治,我們應(yīng)用的Task記為【TaskA】,那么分析如下:
進入桌面: L1 ---- L1是單純的桌面
打開QQ: L1Q1Q2 ---- Q2是安裝完畢后詢問是否啟動對應(yīng)程序的Activity
點擊打開: L1Q1Q2A1A2 ---- A1是入口閃屏頁巷挥,A2是主頁Activity
返回桌面: Q1Q2A1A2L1 ---- 回到桌面頁桩卵,也就是L1前置
點擊A的圖標: Q1Q2L1A1A2A1 ---- 找到【TaskA】,挪到前臺倍宾,由于比對Intent并不是完全一致雏节,所以該請求是新啟動Activity,那么把A1添加到對應(yīng)的【TaskA】中
所以bug出現(xiàn)了高职,出現(xiàn)了再一次的閃屏頁【A1】钩乍,問題定位成功!
PS:這里我稍微變種一下怔锌,因為一般我們閃屏頁都是在啟動主頁后finish的寥粹,而主頁一般是singleTask模式
打開QQ: L1Q1Q2 ---- Q2是安裝完畢后詢問是否啟動對應(yīng)程序的Activity
點擊打開: L1Q1Q2A2 ---- A1是入口閃屏頁,A2是主頁Activity埃元,啟動后A1業(yè)務(wù)邏輯應(yīng)該finish掉涝涤,所以從【TaskA】中挪去
返回桌面: Q1Q2A2L1 ---- 回到桌面頁,也就是L1前置
點擊A的圖標: Q1Q2L1A2A1 -> Q1Q2L1A2A1 ---- 找到【TaskA】岛杀,挪到前臺阔拳,由于比對Intent并不是完全一致,所以該請求是新啟動Activity楞件,那么把A1添加到對應(yīng)的【TaskA】中衫生,然后A1所再一次觸發(fā)啟動主頁裳瘪,但是主頁是singleTask模式土浸,所以又回到了上次對應(yīng)的A2主頁,所以現(xiàn)象為再一次出現(xiàn)閃屏頁彭羹,然后回到原先的主頁界面黄伊。
解決思路
1、讓騰訊那些第三方平臺修正其啟動Intent的設(shè)置派殷,使其與原聲桌面啟動Intent保持完全一致还最。(PS:基本不可能)
2、自身業(yè)務(wù)代碼規(guī)避毡惜,我們可以知道拓轻,如果是多余的閃屏頁入口Activity的話,其基本不可能位于Task的根部经伙,而如果正常啟動的話扶叉,閃屏頁入口Activity必定在多對應(yīng)的Task的根部位置,那么我們可以從這個地方對于這個bug進行規(guī)避,方法就是在閃屏頁入口Activity的onCreate代碼加入如下一段代碼:
// 避免從桌面啟動程序后枣氧,會重新實例化入口類的activity
if (!this.isTaskRoot()) {
Intent intent = getIntent();
if (intent != null) {
String action = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) {
finish();
return;
}
}
}
問題解決痰哨!
參考文章: Android Bug分析系列:第三方平臺安裝app啟動后屯阀,home鍵回到桌面后點擊app啟動時會再次啟動入口類bug的原因剖析