被後臺殺死後,Android應用如何重新走閃屏邏輯

看書的小蝸牛發表於2018-01-05

Android應用執行在後臺的時候,經常被系統的LowMemoryKiller殺掉,當使用者再次點選icon或者從最近的任務列表啟動的時候,程式會被重建,並且恢復被殺之前的現場。什麼意思呢?假如APP在被殺之前的Activity堆疊是這樣的,A<B<C,C位於最上層

後臺殺死與恢復的堆疊.jpg

APP被後臺殺死後,APP端程式被銷燬了,也就不存在什麼Activity了,也就沒有什麼Activity堆疊,不過AMS的卻是被保留了下來:

後臺殺死與恢復的堆疊-殺後.jpg

當使用者再次啟動APP時候會怎麼樣呢?這個時候,首先看到其實C,而不是棧底部的A,也就是說往往被殺死後,恢復看到的第一個介面是使用者最後見到的那個介面。

後臺殺死與恢復的堆疊-恢復.jpg

而使用者點選返回,看到的就是上一個介面B,其次是A

後臺殺死與恢復的堆疊-恢復b.jpg

之所以這樣是因為APP端Activity的建立其實都是由AMS管理的,AMS嚴格維護這APP端Activity對應的ActivityRecord棧,可以看做當前APP的場景,不過,APP端Activity的銷燬同AMS端ActivityRecord的銷燬並不一定是同步的,最明顯的就是後臺殺死這種場景。Android為了能夠讓使用者無感知後臺殺死,就做了這種恢復邏輯,不過,在開發中,這種邏輯帶了的問題確實多種多樣,甚至有些產品就不希望走恢復流程,本文就說說如何避免走恢復流程。結合常見的開發場景,這裡分為兩種,一種是針對推送喚起APP,一種是針對從最近任務列表喚起APP(或者icon)。

從最近的任務列表喚起,不走恢復流程

首先,APP端必須知道當前Activity的啟動是不是在走恢復流程,Activity有一個onCreate方法,在ActivityThread新建Activity之後,會回撥該函式,如果是從後臺殺死恢復來的,回撥onCreate的時候會傳遞一個非空的Bundle savedInstanceState給當前Activity,只要判斷這個非空就能知道是否是恢復流程。

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
   }
複製程式碼

知道恢復流程之後,如何處理呢?其實很簡單,直接吊起閃屏頁就可以了,不過這裡有一點要注意的是,在啟動閃屏頁面的時候,必須要設定其IntentFlag:Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK,這樣做的理由是為了清理之前的場景,不然之前的ActivityRecord棧仍然保留在ActivityManagerService中,具體實現如下,放在BaseActivity中就可以:

Intent intent = new Intent(this, SplashActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
複製程式碼

如果不設定會怎麼樣呢?舉個例子,最常見的就是閃屏之後跳轉主介面,主介面經常有router邏輯,並且其啟動模式一般都是singleTask,處理一些推送,所以其onCreate跟onNewIntent都有相應的處理,如果不設定,在閃屏結束後,在startActivity啟動主介面的時候,其實是先走恢復邏輯,然後走singleTask的onNewIntent邏輯,也就是說,onNewIntent跟onCreate是會同時呼叫的,也可能就會引發重複處理的邏輯,因此最好清理乾淨。

從推送喚起被殺APP時,如何走閃屏邏輯

對於推送訊息的處理,其路由器一般放在MainActivity,並且在onCreate跟onNewIntent都有新增,如果APP存活的情況,可以直接跳轉目標頁面,如果APP被殺,這個時候,希望先跳轉主介面,再跳轉目標頁面,在效果上來看就是,使用者先看到目標頁面,點選返回的時候再看到主介面,如果加上閃屏,希望達到的效果是先看到閃屏、點選返回看到目標頁,再點選返回看到主頁面。如果簡單劃分一下推送場景,可以看做一下三種

  • 程式存活,Activity存活
  • 程式存活,但是沒有Activity存活
  • 程式不存在(無論是否被殺)

其實後面兩種完全可以看做一種,這個時候,都是要先start MainActivity,然後讓MainActivity在其OnCreate中通過startActivityForResult啟動SplashActivity,SplashActivity返回後,在start TargetActivity。下面的討論都是針對後面兩種,需要做的有兩件事

  • 一是:檢測出後面兩種場景,並且在喚起主介面的時候需要新增Intent.FLAG_ACTIVITY_CLEAR_TASK清理之前的現場
  • 二是:在MainActivity的路由系統中,針對這兩種場景要,先跳轉閃屏,閃屏回來後,再跳轉推送頁

如何判斷呢,後面兩種場景其實只需要判斷是否有Activity存活即可,也就是查查APP的topActivity是否為null,注意不要去向AMS查詢,而是在本地程式中查詢,可以通過反射查詢ActivityThread的mActivities,也可以根據自己維護的Activity堆疊來判斷,判斷沒有存活Activity的前提下,就跳轉主頁面去路由

Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.setDate(跳轉的Uri scheme)
startActivity(intent);
複製程式碼

在MainActivity的路由中,需要準確區分是否是推送跳轉進來的,如果不是推送跳轉進來,就不需要什麼特殊處理,如果是推送跳轉進來一定會攜帶跳轉scheme資料,根據是否攜帶資料做區分即可,看一下MainActivity的程式碼:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Uri uri= getIntent().getData();
    <!--只有在intent被設定了跳轉資料的時候才去跳轉,一般是推送就來,如果冷啟動,是沒有資料的-->
    if(uri!=null){
        SplashActivity.startActivityForResult(this,JUMP_TO_TARGET)
    }
}
<!--Intent.FLAG_ACTIVITY_CLEAR_TASK保證了onNewIntent被呼叫的時候,程式一定是正常活著的-->
@Override
protected void onNewIntent(Intent intent) {
    Uri uri= intent.getData();
    intent.setData(null);
    router(uri);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode==JUMP_TO_TARGET && requestCode == RESULT_OK){
        router(getIntent().getData());
        getIntent().setData(null);
    }
}

private void router(Uri uri) {

}
複製程式碼

通過上面兩部分的處理,基本能夠滿足APP“死亡”的情況下,先跳轉閃屏的需求。

作者:看書的小蝸牛 原文連結:被後臺殺死後,Android應用如何重新走閃屏邏輯

僅供參考,歡迎指正

相關文章