【Android】先後臺切換監聽

【Android】先後臺切換監聽

Android 自己並有提供這樣的監聽,因此就只能走偏門了。java

首先,須要定義一下,什麼叫「前臺」,什麼叫「後臺」。本文定義以下:android

前臺git

Activity 處在 FOREGROUND 優先級

後臺github

App進程沒有中止,除去在「前臺」的全部狀況

因此,退到後臺的方式太多了,大體有:app

  1. 按Home鍵
  2. 按「最近任務」鍵
  3. 從通知欄啓動其餘應用
  4. 從應用內部啓動其餘應用
  5. 關掉屏幕

既然是監聽變化,因此確定是在相關生命週期的回調中來進行處理。ActivityA 啓動 ActivityB 的生命週期方法調用順序以下:ide

ActivityA#onPause -> ActivityB#onStart -> ActivityB#onResume -> ActivityA#onStop

從 ActivityB 回退到 ActivityA 的生命週期方法調用順序以下:測試

ActivityB#onPause -> ActivityA#onStart -> ActivityA#onResume -> ActivityB#onStop -> ActivityB#onDestroy

一個思路就是經過統計當前活動的 Activity 數目來計算。ui

在 Activity#onStart 中來檢測前一個狀態是不是「後臺」,若是是,則觸發「切換到前臺」事件,並將活動 Activity 數目加1。 在 Activity#onStop 中並將活動 Activity 數目減1。若是活動的 Activity 數目等於0, 就認爲當前應用處於「後臺」狀態, 並觸發「切換到後臺」事件。this

因此,一個初步方案大體是,實現一個基類 BaseActivity,並重寫如下 onStart 和 onStop 回調方法:code

private static int compatStartCount = 0;
    private static boolean isCompatForeground = true;

    @Override
    public void onStart() {
        super.onStart();
        compatStartCount++;
        if (!isCompatForeground) {
            isCompatForeground = true;
            onBackgroundToForeground(activity);
        }
    }
    
    @Override
    public void onStop() {
        super.onStop();
        compatStartCount--;
        if (compatStartCount == 0) {
            isCompatForeground = false;
            onForegroundToBackground(activity);
        }
    }

開始趟坑

  1. 在關掉/點亮屏幕的狀況下,android3 以前不會觸發 onStart 和 onStop 回調。只會觸發 onPause 和 onStop。因此以上代碼失效。 按理來講,這應該是 Android 的含糊之處,onStop 的觸發時機定義以下:

    Called when you are no longer visible to the user

按理來講,屏幕關閉的時候也符合條件,可是 android3 以前並未按照如此定義而來。因此須要 hack 一下:

private static boolean isCompatLockStop = false;

    private static boolean isStandard() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }
    
    public static boolean isInteractive(Context context) {
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT_WATCH) {
            return pm.isInteractive();
        } else {
            return pm.isScreenOn();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (isStandard()) {
            //np
        } else {
            if (isCompatLockStop) {
                isCompatLockStop = false;
                onStart();
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (isStandard()) {
            //np
        } else {
            if (!isInteractive(activity)) { //鎖屏觸發
                isCompatLockStop = true;
                onStop();
            }
        }
    }

對於 android3 以前的系統,咱們在 onPause 中處理 鎖屏問題,若是 onPause 被觸發的時候,手機處於 「非交互」 狀態,就認爲是按下了電源鍵(或者其餘鎖屏方式),觸發「後臺」狀態。 在 onResume 中判斷若是是從鎖屏界面恢復,則回到「前臺」狀態。

存在問題:點亮屏幕的時候就被認爲回到「前臺」狀態,這個暫未找到好的方法避免。

  1. 快速鎖屏/點亮的狀況下,會屢次觸發生命週期回調。

這個問題在 nexus 5 (Android 6.0) 上穩定重現,重現步驟:關掉屏幕後快速點亮再快速關閉。原本預期的表現應該是:

onPause
onStop

可是實際的表現以下:

onPause
onStop
onStart
onResume
onPause
onStop

因此,最終結果就是:

前臺 -> 後臺
後臺 -> 前臺 
前臺 -> 後臺

多了一個週期。

這個問題我尚未解決。嘗試過解決辦法有:

1.在 onStart 中判斷 inKeyguardRestrictedInputMode 狀態:

public static boolean inKeyguardRestrictedInputMode(Context context) {
        KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
        return keyguardManager.inKeyguardRestrictedInputMode();
    }

在鎖屏狀態下就不觸發計數。可是這個判斷根本不可信,在快速切換的狀況下,在非鎖屏狀況下返回 true 的概率也比較大。

  1. 網上有一種判斷應用是否在前臺的方法
public boolean isAppOnForeground() {
    
        ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); 
        String packageName = getApplicationContext().getPackageName(); 
        
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager 
            .getRunningAppProcesses(); 
        if (appProcesses == null) 
          return false; 
        
        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { 
          // The name of the process that this object is associated with. 
          if (appProcess.processName.equals(packageName) 
              && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { 
            return true; 
          } 
        } 
        return false; 
    }

可是這種測試方法並不靠譜,好比在魅族手機上,鎖屏狀態下也是返回 true。

  1. 監聽 ACTION_USER_PRESENT, ACTION_SCREEN_ON, ACTION_SCREEN_OFF 等事件。

回調事件比較晚,在生命週期中已經跑無缺幾圈以後,廣播才接收到。並且在各個生命週期內沒法判斷是否由於鎖屏致使的週期變化。

順便說一下, ACTION_USER_PRESENT 和 ACTION_SCREEN_ON 存在下面幾種狀況:

  1. 只觸發 ACTION_SCREEN_ON
  2. 先觸發 ACTION_SCREEN_ON,再觸發 ACTION_USER_PRESENT (電源鍵,而後解鎖)
  3. 先觸發 ACTION_USER_PRESENT,再觸發 ACTION_SCREEN_ON (指紋解鎖)

這樣的話,沒法經過簡單的計數來判斷,須要加入 hashCode 來處理。

因此最終這種快速切換的狀況被我忽略了,由於無非就是多了一個週期而已。

不完美實現

對於 API>=14 的系統上,能夠直接增長 ActivityLifecycleCallbacks 監聽,這樣能夠省去定義 BaseActivity 的步驟,減小侵入性。

因此在 API>=14 的系統上,直接使用 ActivityLifecycleCallbacks, 若是須要兼容 2.3,那麼仍是須要抽象出 BaseActivity。

演示代碼地址 : https://github.com/xesam/AppMonitor

##Q羣:315658668

相關文章
相關標籤/搜索