天天看點

Android中Activity的isTaskRoot()方法使用和原理

App打包apk安裝後重複啟動根界面的問題

這個問題很特殊,一般情況下很難被發現,是Android系統一直以來的一個Bug。

當我們把app打包成apk安裝程式,通過點選apk檔案進行安裝時,會啟動安裝界面,

并在安裝成功後會跳轉安裝完成界面,

如圖:

Android中Activity的isTaskRoot()方法使用和原理

安裝完成界面

我們點選圖中的打開按鈕,此時會啟動我們的app

這裡為了讓大家更容易了解一些,我們假設app有兩個界面

啟動界面SplashActivity

主界面MainActivity

app啟動後打開SplashActivity,3秒後自動跳轉MainActivity,界面不做強制finish

接下來,我們需要了解下Task任務棧和Back Stack傳回棧,

如果有同學對這兩個概念還不熟悉的,

可以看一下官方文檔,講得很詳細:

Android任務和傳回棧官方文檔

這裡我們引用官方文檔的一句話:

The device Home screen is the starting place for most tasks. When the user touches an icon in the application launcher (or a shortcut on the Home screen), that application's task comes to the foreground. If no task exists for the application (the application has not been used recently), then a new task is created and the "main" activity for that application opens as the root activity in the stack.

當我們點選home界面的應用啟動圖示時(安裝完成,界面點選icon打開同理)

如果沒有對應Task任務棧存在,則會建立一個新的任務棧,

并且把應用啟動的首頁面作為根Activity放到任務棧中。

如果存在對應的Task任務棧,則會直接調用對應的Task任務棧到前台,并将棧頂的界面顯示給使用者,

那麼當我們的app啟動後打開SplashActivity并跳轉主界面MainActivity後,我們app的任務棧應該如圖所示:

Android中Activity的isTaskRoot()方法使用和原理

此時,當我們點選Home鍵退回到桌面,

app的Task任務棧進入背景,然後我們點選桌面上的啟動圖示,正常情況下,app應該會把它對應的Task任務棧調到前台,并顯示剛剛棧頂的MainActivity界面,

正常流程:

Android中Activity的isTaskRoot()方法使用和原理

然而,實際情況是,app會把它的Task任務棧調用到前台,

并在任務棧上重新建立新的SplashActivity ,再跳轉到MainActivity,

在不重新加載application的情況下,它又重新走了一遍啟動的流程,這個時候,我們會發現任務棧中的Activity重複了,SplashActivity跟MainActivity都變成了兩個

為了更清晰的讓大家了解,這裡畫了兩個圖,

錯誤的bug流程

錯誤狀态下的Task任務棧

bug流程:

Android中Activity的isTaskRoot()方法使用和原理

新調用的SplashActivity會被置于該app的task棧頂

Android中Activity的isTaskRoot()方法使用和原理

多出了兩個Activity

當然這個bug一般使用者也很難注意到,它的産生必須滿足下面的條件:

點選apk檔案安裝app

安裝完成界面點選打開按鈕

點選Home鍵,進入系統桌面,此時app退到背景

再點選桌面上啟動圖示

那麼對于這種問題我們如何來處理呢?

按照上文的舉例,

在正常流程下啟動app進入MainActivity界面時的任務棧:

Android中Activity的isTaskRoot()方法使用和原理

正常情況

bug情況下,會調起任務棧到前台并添加根Acitivy SplashActivity到棧頂,此時的任務棧:

Android中Activity的isTaskRoot()方法使用和原理

我們可以看到,在bug情況下啟動app時,SplashActivity(app的根Activity)再次建立并疊加到Task任務棧上了

理應隻會出現在棧底的SplashActivity出現在了其他位置,是以這裡我們直接判斷了app根Activity SplashActivity的位置

在app的SplashActivity(app的根Activity)的onCreate方法中通過 isTaskRoot() 方法來判斷是否是任務棧中的根Activity,如果是就不做任何處理,如果不是則直接finish掉;

public class SplashActivity extends BaseActivity {

@Override

    protected void onCreate(@Nullable Bundle savedInstanceState) {

        setTheme(R.style.AppTheme_NoActionBar);

        super.onCreate(savedInstanceState);

        if (!isTaskRoot()) {

            finish();

            return;

        }

    }

}

這樣棧頂的SplashActivity在還未執行其他代碼的情況下就finish()掉了,此時會顯示棧頂的MainActivity。