1. 程式人生 > >Android 應用在後臺時,跳轉 Activity 會自動切換應用至前臺

Android 應用在後臺時,跳轉 Activity 會自動切換應用至前臺

本部落格 demo 見:demo。

平常用手機的時候經常碰到這種情況,用首屏廣告舉個栗子~很多應用都會有首屏廣告 activity A,假設此應用是 app C,如果此時要使用別的應用,就會使得 app C 在後臺執行。可是當 activity A 的廣告結束後自動跳轉 activity B 的時候 app C 總是會自動跳出來切換到前臺展示,擋住了我們正在使用的應用,體驗非常不好。

這是 android 4.4 後修改的新特性。理想的體驗應該是如果應用在後臺啟動 activity B,那 activity B 也應該同樣保持在後臺。也就是啟動的 activity B 應該保持和啟動前時應用的前後臺狀態一致,才不會影響使用者的使用。

有 2 種方案:

1. 在當前 activity A 裡處理
在跳轉 activity B 前判斷應用 C 是否在後臺,如果應用 C 在後臺,那麼就不跳轉,並標記變數 ifStartSecondActivity = true,等到應用 C 被切換到前臺的時候,因為還沒有跳轉,所以相當於 activity A 重新在前臺展示的時候,在 onResume() 裡判斷變數 ifStartSecondActivity == true 則執行 startActivity() 跳轉至 B;如果應用 C 在前臺,正常跳轉即可。

判斷應用是否在前臺:

public boolean isAppOnForeground() {
    ActivityManager activityManager = (ActivityManager) getApplicationContext()
            .getSystemService(Context.ACTIVITY_SERVICE);
    String packageName = getApplicationContext().getPackageName();
 
    List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
    if (appProcesses == null)
        return false;
 
    for (RunningAppProcessInfo appProcess : appProcesses) {
        // The name of the process that this object is associated with.
        if (appProcess.processName.equals(packageName)
                && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
            return true;
        }
    }
 
    return false;
}

注:這樣當切換應用至前臺時,跳轉 B 的時候比較生硬突然,體驗不是很好,因為這時其實先顯示的還是 A,只是會立馬跳轉 B,會有一瞬間的閃動,銜接不是很好,如果對時間要求不高,可以加一個延時再跳轉。

如果不想延時,希望倒計時結束後開啟應用 C ,直接展示的是 activity B,可以使用下面的方法 2。

2. 在需要跳轉的 activity B 裡處理
這裡使用到 activity 的 moveTaskToBack(boolean nonRoot) 方法來使應用切至後臺。

官方文件:

/**
 * Move the task containing this activity to the back of the activity
 * stack.  The activity's order within the task is unchanged.
 *
 * 將包含此 activity 的 task 移到活動堆疊的後面。該 task 裡的 activity 的順序不會變。
 *
 * @param nonRoot If false then this only works if the activity is the root
 *                of a task; if true it will work for any activity in
 *                a task.
 *
 *                false:這隻會在 activity 是 task 的第一個的時候起作用。
 *                true:對 task 裡的所有 activity 都起作用。
 *
 * @return If the task was moved (or it was already at the
 *         back) true is returned, else false.
 *         
 *         如果 task 被移動(或者它已經在後臺),則返回 true,否則返回 false。
 */
public boolean moveTaskToBack(boolean nonRoot) {
    try {
        return ActivityManagerNative.getDefault().moveActivityTaskToBack(
                mToken, nonRoot);
    } catch (RemoteException e) {
        // Empty
    }
    return false;
}
其中,判斷是否是根 task,可以用 isTaskRoot() 來判斷。

/**
 * Return whether this activity is the root of a task.  The root is the
 * first activity in a task.
 *
 * @return True if this is the root activity, else false.
 */
public boolean isTaskRoot() {
    try {
        return ActivityManagerNative.getDefault()
                .getTaskForActivity(mToken, true) >= 0;
    } catch (RemoteException e) {
        return false;
    }
}
若翻譯有誤,請指正(^_^)

在 activity B 裡呼叫 moveTaskToBack 後,應用會被切至後臺執行。

編寫程式碼列印 log 測試:

activity A 裡有個倒計時,倒計時結束後會呼叫 startActivity() 啟動 activity B,我們在 activity B 的 onCreate() 裡新增 moveTaskToBack()。

activity B 的生命週期如下:

(1)應用在前臺開啟 activity A ,啟動倒計時,在倒計時結束前手動將應用切換至後臺。

03-29 19:49:59.756 8312-8312/com.app D/moveTaskToBack: onCreate
03-29 19:49:59.843 8312-8312/com.app D/moveTaskToBack: moveTaskToBack
03-29 19:49:59.939 8312-8312/com.app D/moveTaskToBack: onStart
03-29 19:49:59.944 8312-8312/com.app D/moveTaskToBack: onResume
03-29 19:49:59.951 8312-8312/com.app D/moveTaskToBack: onPause
03-29 19:49:59.984 8312-8312/com.app D/moveTaskToBack: onStop
activity A 在後臺倒計時結束後會啟動 activity B。

可見 activity B 會執行 onPause() 和 onStop() 自動切換至後臺,沒有 finish 掉。

(2)倒計時結束後手動將應用切換至前臺,會直接展示 activity B。

03-29 19:56:27.842 8312-8312/com.app D/moveTaskToBack: onRestart
03-29 19:56:27.850 8312-8312/com.app D/moveTaskToBack: onStart
03-29 19:56:27.852 8312-8312/com.app D/moveTaskToBack: onResume
activity B 不會再執行 onCreate()。

(3)按返回鍵返回 activity A。

03-29 19:57:34.397 8312-8312/com.app D/moveTaskToBack: onPause
03-29 19:57:44.432 8312-8312/com.app D/moveTaskToBack: onStop
03-29 19:57:44.433 8312-8312/com.app D/moveTaskToBack: onDestroy
注:不過此方法仍然有個問題,當倒計時進行中時開啟多工介面(Recents screen),倒計時結束的跳轉,activity B 的 moveTaskToBack() 不僅會將應用切至後臺,還會關閉多工介面。

觀察多工介面開啟、關閉時 activity 的生命週期,發現開啟多工介面時,會執行 onPause() - onStop(),關閉多工介面時,會執行 onRestart() - onStart() - onResume()。我的猜想是多工介面和 activity A 在同一個任務棧裡面,所以一起切換至後臺了。

注:這裡是判斷的應用是否在前臺,而不是當前 activity A 是否在前臺(參見部落格 Start Acitivity in background on Android 4.4 KitKat)。因為如果此時別的 activity 有 dialog 顯示,activity A 被部分遮擋會執行 onPause(),導致 activity B 會執行 moveTaskToBack,出現的效果就是 app 閃退到後臺了。可以把他的變數 paused 換成 isVisible,放在 onStart() 和 onStop() 進行賦值判斷 activity A 是否可見。

參考:Start Acitivity in background on Android 4.4 KitKat

轉載:https://blog.csdn.net/u013719138/article/details/79743895