Android 自己並有提供這樣的監聽,因此就只能走偏門了。java
首先,須要定義一下,什麼叫「前臺」,什麼叫「後臺」。本文定義以下:android
前臺git
Activity 處在 FOREGROUND 優先級
後臺github
App進程沒有中止,除去在「前臺」的全部狀況
因此,退到後臺的方式太多了,大體有:app
既然是監聽變化,因此確定是在相關生命週期的回調中來進行處理。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); } }
在關掉/點亮屏幕的狀況下,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 中判斷若是是從鎖屏界面恢復,則回到「前臺」狀態。
存在問題:點亮屏幕的時候就被認爲回到「前臺」狀態,這個暫未找到好的方法避免。
這個問題在 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 的概率也比較大。
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。
回調事件比較晚,在生命週期中已經跑無缺幾圈以後,廣播才接收到。並且在各個生命週期內沒法判斷是否由於鎖屏致使的週期變化。
順便說一下, ACTION_USER_PRESENT 和 ACTION_SCREEN_ON 存在下面幾種狀況:
這樣的話,沒法經過簡單的計數來判斷,須要加入 hashCode 來處理。
因此最終這種快速切換的狀況被我忽略了,由於無非就是多了一個週期而已。
對於 API>=14 的系統上,能夠直接增長 ActivityLifecycleCallbacks 監聽,這樣能夠省去定義 BaseActivity 的步驟,減小侵入性。
因此在 API>=14 的系統上,直接使用 ActivityLifecycleCallbacks, 若是須要兼容 2.3,那麼仍是須要抽象出 BaseActivity。
演示代碼地址 : https://github.com/xesam/AppMonitor
##Q羣:315658668