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