Android框架自身并未提供前后台监听的解决方案,但是在应用诸多使用场景中又的确需要进行前后台判断,譬如客户端需要在用户切到到后台时开启自动签到以及推送。
王俊峰于2016年7月份提出了一份《Android 前台切换监听新的实现》,其中借助一种类似延时任务的方式进行了前后台切换监听的实现,口袋助理现在使用的前后台监听也是在此基础上稳定运行的。
最近在预研手势锁的需求,其中最常见的应用场景就是:在从后台切换到前台时,要弹出手势锁页面进行解锁。现有的前后台监听机制在用户“开启不保留活动”后,就不能正常触发,然而在手势锁该需求中又必须兼容这种情况,否则用户开启了不保留活动就相当于破解了手势锁,因此,催生了对android前后台监听的再次研究。
一、综述
实现方式 | onStop生命周期中检测代码当前运行在前台的进程不是我们自己的进程 | 通过延时任务的实现 | 通过计数的实现 |
---|---|---|---|
依据 | 当应用切到后台的时候,运行在前台的进程由我们的app变成了桌面app 或 其他进程 | 内部跳转,onPause后一定会触发OnResume,在onPause中启动延时任务,onResume中取消;一旦没有onResume,就触发任务,意味着进入了后台 | onStart和onStop的配对出现原则:内部跳转,当我们从activity A到Activity B时,执行的生命周期的执行顺序如下: A.onPause -> B.onCreate -> B.onStart -> B.onResume -> A.onStop |
实现原理 | 在Activity的onStop生命周期中执行检测代码,如果发现当前运行在前台的进程不是我们自己的进程,说明应用切到了后台 | 同上 | 在onStop和onResume中进行计数处理,一旦到达临界值则证明为前后台切换操作 |
优势 | 使用系统api,屏蔽屏幕旋转或不保留活动等额外操作的影响 | 稳定性可靠,解决了部分机型判断processInf.importance不灵敏的问题 | 屏蔽系统api的差异性,稳定可靠 |
弊端 | 定制化系统的api函数可能有一定的触发时机差异 | 不支持不保留活动(不保留活动开启后,因为activity的destory,主线程的延时任务被取消掉,即便后台了,也不触发监听) | 不支持屏幕旋转(当我们旋转设备时activity将会重建,onStart方法将被再次调用,这时将会错误的判断为app第二次被打开。所以若是APP支持水平模式,则不能采用这个办法) |
注意事项 | 部分手机的前台进程判断有问题 | 开启不保留活动后失效 | 第一次启动也会认为是后台转前台 |
二、onStop生命周期中检测代码当前运行在前台的进程不是我们自己的进程
当应用切到后台的时候,运行在前台的进程由我们的app变成了桌面app,依据这一点,我们可以实现检测应用前后台切换的功能。在Activity的onStop生命周期中执行检测代码,如果发现当前运行在前台的进程不是我们自己的进程,说明应用切到了后台。
想想为什么要在onStop中检测,而不是onPause?这是由于A启动B时,生命周期的执行顺序如下:A.onPause->B.onCreate->B.onStart->B.onResume->A.onStop,也就是说,在A的onPause方法中,B的生命周期还没有执行,进程没有进入前台,当然是检测不到的。我们把代码移到onPause生命周期中,发现确实没有效果
/用来控制应用前后台切换的逻辑
private boolean isCurrentRunningForeground = true;
@Override
protected void onStart() {
super.onStart();
if (!isCurrentRunningForeground) {
Log.d(TAG, ">>>>>>>>>>>>>>>>>>>切到前台 activity process");
}
}
@Override
protected void onStop() {
super.onStop();
isCurrentRunningForeground = isRunningForeground();
if (!isCurrentRunningForeground) {
Log.d(TAG,">>>>>>>>>>>>>>>>>>>切到后台 activity process");
}
}
public boolean isRunningForeground() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
List appProcessInfos = activityManager.getRunningAppProcesses();
// 枚举进程
for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessInfos) {
if (appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
if (appProcessInfo.processName.equals(this.getApplicationInfo().processName)) {
Log.d(TAG,"EntryActivity isRunningForeGround");
return true;
}
}
}
Log.d(TAG, "EntryActivity isRunningBackGround");
return false;
}
三、onStart和onStop方法中用变量count计数
我们需要先明确一个生命周期规律:
MainActivity跳转到SecondActivity:
11-06 14:59:09.821 16683-16683/sangfor.com.teststaticlife I/MainActivity: onPause()
11-06 14:59:09.841 16683-16683/sangfor.com.teststaticlife I/SecondActivity: onCreate()
11-06 14:59:09.851 16683-16683/sangfor.com.teststaticlife I/SecondActivity: onStart()
11-06 14:59:09.851 16683-16683/sangfor.com.teststaticlife I/SecondActivity: onPostCreate()
11-06 14:59:09.851 16683-16683/sangfor.com.teststaticlife I/SecondActivity: onResume()
11-06 14:59:09.851 16683-16683/sangfor.com.teststaticlife I/SecondActivity: onPostResume()
11-06 14:59:09.861 16683-16683/sangfor.com.teststaticlife I/SecondActivity: onAttachedToWindow()
11-06 14:59:09.901 16683-16683/sangfor.com.teststaticlife I/SecondActivity: onWindowFocusChanged():true
11-06 14:59:09.941 16683-16683/sangfor.com.teststaticlife I/MainActivity: onWindowFocusChanged():false
11-06 14:59:10.311 16683-16683/sangfor.com.teststaticlife I/MainActivity: onStop()
也就是说:当我们从activity A到Activity B时,执行的生命周期的执行顺序如下:
A.onPause -> B.onCreate -> B.onStart -> B.onResume -> A.onStop
因此我们在Activity的onStart和onStop方法中用变量count计数,在onStart中先判断是否等于0再将变量加1,onStop中先减1再判断是否等于0
假设应用有两个Activity,分别为A和B。
情况1,首先启动A,A再启动B,然后关掉B:
A启动后(onStart后),count=1;
A启动B,先B.onStart 然后A.onStop,count先加1后减1,count为1。
关掉B,先A.onStart 然后B.onStop,count先加1后减1,count为1
情况2,首先启动A,然后按Home键返回桌面:
A启动后(onStart后),count=1;
按Home键返回桌面,会执行A.onStop,count的计数变位0。
关键代码
public class BaseTaskSwitch implements Application.ActivityLifecycleCallbacks {
public int mCount = 0;
private OnTaskSwitchListener mOnTaskSwitchListener;
private static BaseTaskSwitch mBaseLifecycle;
public static BaseTaskSwitch init(Application application) {
if (null == mBaseLifecycle) {
mBaseLifecycle = new BaseTaskSwitch();
application.registerActivityLifecycleCallbacks(mBaseLifecycle);
}
return mBaseLifecycle;
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
@Override
public void onActivityStarted(Activity activity) {
if (mCount++ == 0) {
mOnTaskSwitchListener.onTaskSwitchToForeground();
}
}
@Override
public void onActivityResumed(Activity activity) {}
@Override
public void onActivityPaused(Activity activity) {}
@Override
public void onActivityStopped(Activity activity) {
if (--mCount == 0) {
mOnTaskSwitchListener.onTaskSwitchToBackground();
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
@Override
public void onActivityDestroyed(Activity activity) {}
public void setOnTaskSwitchListener(OnTaskSwitchListener listener) {
mOnTaskSwitchListener = listener;
}
public interface OnTaskSwitchListener {
void onTaskSwitchToForeground();
void onTaskSwitchToBackground();
}
}
四、结论
鉴于android机型的不可控性,最终还是选择了《onStart和onStop方法中用变量count计数》方式进行前后台监听的实现,从而实现了更准确的前后台监听策略,规避了开启不保留活动对判断的干扰