問題描述:
開發(fā)者打包發(fā)布一個(gè)release版本app媚污,將app包通過電腦QQ傳送到手機(jī)QQ上面京髓,點(diǎn)擊安裝甸私,安裝后選擇打開app,頁面結(jié)構(gòu)如下:
1、閃屏頁SplashActivity---> 登錄頁LoginActivity---> 主頁MainActivity
2家破、用戶下載app到手機(jī)喊积,通過文件管理器找到并安裝這個(gè)apk,安裝后提示:“安裝完成,你可以打開xxx應(yīng)用了”,
3、用戶打開app愉老,輸入賬號(hào)密碼跳轉(zhuǎn)到了主頁MainActivity璧尸。
4、用戶按下Home鍵活烙,然后在程序列表點(diǎn)擊app,
先后顯示:閃屏頁SplashActivity---> 登錄頁LoginActivity,APP重啟了!亮瞎了24K鈦合金狗眼的我們覺得這玩法不對(duì)吧?
期待頁面:顯示原先的主頁MainActivity。
奇怪的是:真機(jī)在debug開發(fā)調(diào)試時(shí)不會(huì)出現(xiàn)這個(gè)問題???
問題原因:
debug版本是通過adb安裝啟動(dòng)或平常的桌面Icon圖標(biāo)啟動(dòng),
release版本是安裝這類第三方平臺(tái)啟動(dòng)。
兩者的啟動(dòng)intent不相同!(相同是指:?jiǎn)?dòng)類,action、category等等全部一樣,不可多項(xiàng)也不可缺少)
在解決問題前,先了解一下相關(guān):
1援所、Home主界面其實(shí)也是一個(gè)Activity耻涛。
當(dāng)從APP界面按下Home鍵盤墨辛,實(shí)際是啟動(dòng)APP跳轉(zhuǎn)到Home主界面,這樣我們的程序就被置于后臺(tái),被這個(gè)Home主界面Activity覆蓋。
2彩掐、Activity的Task管理
Android系統(tǒng)的App啟動(dòng)與切換管理依賴于相關(guān)Activity的Task的管理。一個(gè)Task之中可能含有若干個(gè)Activity,為了簡(jiǎn)便起見,我們這里記錄
【Task A】的Activity分別為 【A1】 、【A2】等,
【Task B】的Activity分別為 【B1】 诗充、【B2】茎匠。
那么我們來分析下App之間是怎么切換的造烁。假設(shè)應(yīng)用都是單Task應(yīng)用(相對(duì)于大部分的普通App來說煤伟,都是采用單一Task來管理的)
桌面程序App:【TaskA】 ---- 存在Activity有【A1】 ---- 其棧的結(jié)構(gòu)為 A1
應(yīng)用程序B:【TaskB】 ---- 存在Activity有【B1】【B2】 ---- 其棧的結(jié)構(gòu)為 B1_B2
應(yīng)用程序C: 【TaskC】 ---- 存在Activity有【C1】【C2】 ---- 其棧的結(jié)構(gòu)為 C1_C2
a、那么我們進(jìn)入桌面時(shí):Task之間的結(jié)構(gòu)是 A1 ---- 也就是只有一個(gè)【TaskA】棧(桌面Task),并且位于最前端(這里表現(xiàn)為最后添加的末端)
b押赊、然后我們點(diǎn)擊應(yīng)用程序B的圖標(biāo)咽袜,啟動(dòng)B :Task之間的結(jié)構(gòu)是 A1B1B2 ---- 添加了一個(gè)【TaskB】,而且【TaskB】也是位于最前端瓜浸,現(xiàn)在顯示的是【TaskB】的B2的Activity的界面
c、接著點(diǎn)擊home鍵: Android對(duì)于home做了特殊默認(rèn)處理比原,就是會(huì)把桌面Task挪到所以Task最前端插佛,Task結(jié)構(gòu)應(yīng)該變成 B1_B2_A1 ---- 【TaskA】挪到隊(duì)列最前端,現(xiàn)在顯示的是【TaskA】的A1的Activity的界面量窘,也就是桌面
d雇寇、我們?cè)僭谧烂纥c(diǎn)擊應(yīng)用程序C的圖標(biāo),啟動(dòng)C : Task之間的結(jié)構(gòu)變成 B1B2A1C1C2 ---- 添加了一個(gè)【TaskC】蚌铜,而且【TaskC】也是位于最前端锨侯,現(xiàn)在顯示的是【TaskC】的C2的Activity的界面
從上面的例子,我們可以知道:
我們編寫任何一個(gè)Activity的時(shí)候冬殃,都可以在AndroidManifest里面顯式指定一個(gè)taskAffinity的屬性囚痴,也就是說該Activity歸屬于對(duì)應(yīng)taskAffinity的棧;如果沒有指定任何taskAffinity审葬,那么該Activity將會(huì)直接歸屬于包名所在的Task之下深滚。而我們啟動(dòng)一個(gè)Activity時(shí)(這里只討論standard啟動(dòng)模式)骂束,那么回去先搜尋對(duì)應(yīng)的Task是否存在,如果不存在成箫,新建一個(gè)Task并將Activity入棧展箱,如果已經(jīng)存在對(duì)應(yīng)的Task,那么直接在對(duì)應(yīng)Task入棧即可蹬昌。
那么問題來了:如果我們?cè)谏厦娴赿步點(diǎn)擊的圖片并不是程序C的圖標(biāo)混驰,而是重新點(diǎn)擊了程序B的圖標(biāo),此時(shí)【TaskB】是已經(jīng)存在的了皂贩,那么為了不會(huì)講B的入口activity(B1)直接在【TaskB】入棧栖榨,而是將【TaskB】挪到前臺(tái)并不做任何Activity啟動(dòng)的操作呢?
3明刷、桌面的啟動(dòng)管理:
回頭研究下AndroidManifest這個(gè)文件婴栽,我們輕而易舉發(fā)現(xiàn),但凡是App入口Activity辈末,那么一定會(huì)包含
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
這幾行代碼愚争。這里到底有什么玄機(jī)呢?其實(shí)這個(gè)就是跟桌面約定好的啟動(dòng)攔截過濾器挤聘。因?yàn)樽烂嬗幸粋€(gè)很明顯的需求就是轰枝,如果我們?cè)俅吸c(diǎn)擊已經(jīng)在后臺(tái)的App圖標(biāo)時(shí),是應(yīng)該將該后臺(tái)任務(wù)挪到前臺(tái)而不是再次啟動(dòng)該App程序组去。
而從柯元旦所著的《android內(nèi)核剖析》一書中有記錄如下規(guī)則:
每次啟動(dòng)Intent導(dǎo)致新創(chuàng)建Task的時(shí)候鞍陨,該Task會(huì)記錄導(dǎo)致其創(chuàng)建的Intent;而如果后續(xù)需要有一個(gè)新的與創(chuàng)建Intent完全一致(完全一致定位為:?jiǎn)?dòng)類从隆,action诚撵、category等等全部一樣,不可多項(xiàng)也不可缺少)键闺,那么該Intent并不會(huì)觸發(fā)Activity的新建啟動(dòng)寿烟,而只會(huì)將已經(jīng)存在的對(duì)應(yīng)Task移到前臺(tái);這也就是為什么桌面會(huì)在再次點(diǎn)擊圖標(biāo)時(shí)將后臺(tái)任務(wù)挪到前臺(tái)而不是重新啟動(dòng)App的實(shí)現(xiàn)艾杏。
那么為啥要指定入口Activity特定的action和category呢韧衣,那就是為了讓桌面啟動(dòng)app所用的Intent具有特殊性,也就是添加了特別的攔截器购桑,避免其他應(yīng)用內(nèi)或者應(yīng)用間的Intent對(duì)于這個(gè)啟動(dòng)方式的干擾畅铭。
原理剖析:
文件管理器雖然使用Intent來啟動(dòng)剛剛安裝的那個(gè)App,但:它的啟動(dòng)Intent并沒有跟桌面的啟動(dòng)Intent完全一致勃蜘!
我們將桌面的Task記為【TaskDesktop】硕噩,文件管理器的Task記為【TaskFile】,我們應(yīng)用的Task記為【TaskApp】缭贡,分析如下:
進(jìn)入桌面: D1 ---- D1是單純的桌面
打開文件管理器: D1_F1_F2 ---- F2是安裝完畢后詢問是否啟動(dòng)對(duì)應(yīng)程序的Activity
點(diǎn)擊打開: D1_F1_F2_A1_A2 ---- A1是入口閃屏頁炉擅,A2是登錄Activity
返回桌面: F1_F2_A1_A2_D1 ---- 回到桌面頁辉懒,也就是D1前置
點(diǎn)擊A的圖標(biāo): F1_F2_D1_A1_A2_A1 ---- 找到【TaskA】,挪到前臺(tái)谍失,由于比對(duì)Intent并不是完全一致眶俩,所以該請(qǐng)求是新啟動(dòng)Activity,那么把A1添加到對(duì)應(yīng)的【TaskA】中
所以bug出現(xiàn)了快鱼,出現(xiàn)了再一次的閃屏頁【A1】颠印,問題定位成功!
PS:這里我稍微變種一下抹竹,因?yàn)橐话阄覀冮W屏頁都是在啟動(dòng)登錄頁后finish的塞栅,而登錄頁一般是singleTask模式
打開文件管理器: D1_F1_F2 ---- F2是安裝完畢后詢問是否啟動(dòng)對(duì)應(yīng)程序的Activity
點(diǎn)擊打開: D1_F1_F2_A2 ---- A1是入口閃屏頁忘晤,A2是登錄Activity把敢,啟動(dòng)后A1業(yè)務(wù)邏輯應(yīng)該finish掉惠窄,所以從【TaskA】中挪去
返回桌面: F1_F2_A2_D1 ---- 回到桌面頁,也就是D1前置
點(diǎn)擊A的圖標(biāo): F1_F2_D1_A2_A1 -> 找到【TaskA】袄琳,挪到前臺(tái)询件,由于比對(duì)啟動(dòng)的Intent不完全一致,所以新創(chuàng)建一個(gè)A1 Activity跨蟹,那么把A1添加到對(duì)應(yīng)的【TaskA】中雳殊,然后A1所再一次觸發(fā)啟動(dòng)登錄頁 A2,但是登錄頁是singleTask模式窗轩,所以又回到了上次對(duì)應(yīng)的A2登錄頁,所以現(xiàn)象為再一次出現(xiàn)閃屏頁座咆,然后回到原先的登錄頁界面痢艺。
解決方法
1、讓騰訊那些第三方平臺(tái)修正其啟動(dòng)Intent的設(shè)置介陶,使其與原聲桌面啟動(dòng)Intent保持完全一致堤舒。(PS:基本不可能)
2、正常啟動(dòng)的閃屏頁Activity必定在【TaskA】的最底部(實(shí)際已finish掉被登錄頁取代)哺呜,而第二次閃屏Activity不可能位于Task的最底部舌缤,所以在閃屏頁Activity的onCreate代碼:
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 避免從桌面啟動(dòng)程序后,會(huì)重新實(shí)例化入口類的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;
}
}
}
}
或者
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//首次啟動(dòng) Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT 為 0某残,再次點(diǎn)擊圖標(biāo)啟動(dòng)時(shí)就不為零了
if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
finish();
return;
}
}
讀到這里国撵,細(xì)心的讀者一定會(huì)問:你上面說的情形只適用閃屏頁和登錄頁,如果登錄進(jìn)去主頁MainActivity按Home鍵玻墅,如何處理呢介牙?
1、閃屏頁的OnCreate方法根據(jù)登錄狀態(tài)判斷跳轉(zhuǎn):
if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
if (UserInfo.getInstance().isLogined) { // 若已登錄澳厢,跳轉(zhuǎn)到主頁
readyGoThenKill(MainActivity.class);
} else { // 若未登錄环础,引導(dǎo)用戶登錄
readyGoThenKill(UserLoginActivity.class);
}
return;
}
2囚似、設(shè)定MainActivity的launchMode="singleTask",在AndroidManifest.xml修改
<activity
android:name="com.emp.frame.MainActivity"
android:launchMode="singleTask" />
轉(zhuǎn)載:https://blog.csdn.net/LVXIANGAN/article/details/82870762