首次安裝apk點(diǎn)擊Home鍵后再次打開導(dǎo)致APP重啟問(wèn)題

問(wèn)題描述:

開發(fā)者打包發(fā)布一個(gè)release版本app,將app包通過(guò)電腦QQ傳送到手機(jī)QQ上面,點(diǎn)擊安裝,安裝后選擇打開app,頁(yè)面結(jié)構(gòu)如下:
1、閃屏頁(yè)SplashActivity---> 登錄頁(yè)LoginActivity---> 主頁(yè)MainActivity
2、用戶下載app到手機(jī),通過(guò)文件管理器找到并安裝這個(gè)apk,安裝后提示:“安裝完成,你可以打開xxx應(yīng)用了”,
3、用戶打開app,輸入賬號(hào)密碼跳轉(zhuǎn)到了主頁(yè)MainActivity。
4、用戶按下Home鍵,然后在程序列表點(diǎn)擊app,
先后顯示:閃屏頁(yè)SplashActivity---> 登錄頁(yè)LoginActivity,APP重啟了!亮瞎了24K鈦合金狗眼的我們覺得這玩法不對(duì)吧?
期待頁(yè)面:顯示原先的主頁(yè)MainActivity。
奇怪的是:真機(jī)在debug開發(fā)調(diào)試時(shí)不會(huì)出現(xiàn)這個(gè)問(wèn)題???

問(wèn)題原因:

debug版本是通過(guò)adb安裝啟動(dòng)或平常的桌面Icon圖標(biāo)啟動(dòng),
release版本是安裝這類第三方平臺(tái)啟動(dòng)。
兩者的啟動(dòng)intent不相同!(相同是指:?jiǎn)?dòng)類,action、category等等全部一樣,不可多項(xiàng)也不可缺少)

在解決問(wèn)題前,先了解一下相關(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】。

那么我們來(lái)分析下App之間是怎么切換的。假設(shè)應(yīng)用都是單Task應(yīng)用(相對(duì)于大部分的普通App來(lái)說(shuō),都是采用單一Task來(lái)管理的)

桌面程序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的屬性,也就是說(shuō)該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入棧即可。

那么問(wèn)題來(lái)了:如果我們?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)攔截過(guò)濾器。因?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來(lái)啟動(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是安裝完畢后詢問(wèn)是否啟動(dòng)對(duì)應(yīng)程序的Activity

點(diǎn)擊打開: D1_F1_F2_A1_A2 ---- A1是入口閃屏頁(yè),A2是登錄Activity

返回桌面: F1_F2_A1_A2_D1 ---- 回到桌面頁(yè),也就是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)了再一次的閃屏頁(yè)【A1】,問(wèn)題定位成功!

PS:這里我稍微變種一下,因?yàn)橐话阄覀冮W屏頁(yè)都是在啟動(dòng)登錄頁(yè)后finish的,而登錄頁(yè)一般是singleTask模式

打開文件管理器: D1_F1_F2 ---- F2是安裝完畢后詢問(wèn)是否啟動(dòng)對(duì)應(yīng)程序的Activity

點(diǎn)擊打開: D1_F1_F2_A2 ---- A1是入口閃屏頁(yè),A2是登錄Activity,啟動(dòng)后A1業(yè)務(wù)邏輯應(yīng)該finish掉,所以從【TaskA】中挪去

返回桌面: F1_F2_A2_D1 ---- 回到桌面頁(yè),也就是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)登錄頁(yè) A2,但是登錄頁(yè)是singleTask模式,所以又回到了上次對(duì)應(yīng)的A2登錄頁(yè),所以現(xiàn)象為再一次出現(xiàn)閃屏頁(yè),然后回到原先的登錄頁(yè)界面。

解決方法

1、讓騰訊那些第三方平臺(tái)修正其啟動(dòng)Intent的設(shè)置,使其與原聲桌面啟動(dòng)Intent保持完全一致。(PS:基本不可能)
2、正常啟動(dòng)的閃屏頁(yè)Activity必定在【TaskA】的最底部(實(shí)際已finish掉被登錄頁(yè)取代),而第二次閃屏Activity不可能位于Task的最底部,所以在閃屏頁(yè)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ì)問(wèn):你上面說(shuō)的情形只適用閃屏頁(yè)和登錄頁(yè),如果登錄進(jìn)去主頁(yè)MainActivity按Home鍵,如何處理呢?
1、閃屏頁(yè)的OnCreate方法根據(jù)登錄狀態(tài)判斷跳轉(zhuǎn):

        if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
            if (UserInfo.getInstance().isLogined) { // 若已登錄,跳轉(zhuǎn)到主頁(yè)
                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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容