android监测程序进入后台以及从后台返回

来源:互联网 发布:能源消费总量 数据 编辑:程序博客网 时间:2024/05/26 05:52

最近项目需要监测android程序从后台返回的事件,百度了下,没有什么系统的总结,于是上StackOverFlow找到了个关于这个问题的讨论:How to detect when an Android app goes to the background and come back to the foreground。讨论过程中出现了几种都比较可行的方法,在此总结一下,供大家参考。

一、定时器方法

在按Home键的时候,Activity会调用onResume和onPause方法,重写这两个方法是一个基本入口。但是,在正常跳转的时候,Activity也会调用onResume和onPause方法,所以必须要对onResume和onPause的调用做一个判断,从而忽略掉正常跳转时的调用问题。那么怎么来实现这个判断呢?于是就有人想到了利用时间来判断:正常跳转过程中,从前一个Activity的onPause被调用,到新的Activity的onResume被调用,应该在一个很短的时间内就可以完成(除非你的Activity在onCreate中干了耗时操作);而进入桌面再从桌面返回的速度就会慢很多(需要人手动操作,相比跳转的毫秒级时间确实差距比较明显)。根据这个思路,就可以利用计时器去解决这个问题了。主要代码逻辑如下:
首先,你需要定义一个全局的Timer和TimerTask,确保在Activity之间可以共享,可以放在自定义Application中,也可以使用单例模式。Application示例如下:
public class MyApplication extends Application {    private Timer mActivityTransitionTimer;    private TimerTask mActivityTransitionTimerTask;    public boolean wasInBackground;    // 阀值,可定义为一个可接受的时间(足够Activity跳转,来不及从桌面返回)    private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;         public void startActivityTransitionTimer() {         this.mActivityTransitionTimer = new Timer();        this.mActivityTransitionTimerTask = new TimerTask() {            public void run() {                MyApplication.this.wasInBackground = true;            }        };        this.mActivityTransitionTimer.schedule(mActivityTransitionTimerTask,                                           MAX_ACTIVITY_TRANSITION_TIME_MS);    }    public void stopActivityTransitionTimer() {        if (this.mActivityTransitionTimerTask != null) {            this.mActivityTransitionTimerTask.cancel();        }        if (this.mActivityTransitionTimer != null) {            this.mActivityTransitionTimer.cancel();        }        this.wasInBackground = false;    }}
然后,再你的BaseActivity中(我相信一个合格的开发人员都会定义这样一个Activity的基类),重写onResume和onPause方法:
@Overridepublic void onResume(){    super.onResume();    MyApplication myApp = (MyApplication)this.getApplication();    // 如果定时器已经结束了,wasInBackgroudn就会变成true,这个时候onResume就是从背景返回    if (myApp.wasInBackground){        //从背景返回了    }    myApp.stopActivityTransitionTimer();}@Overridepublic void onPause(){    super.onPause();    // 启动定时器    ((MyApplication)this.getApplication()).startActivityTransitionTimer();}
这个方法简单易懂,准确率通常也会比较高。不过也有不足的地方,比如没有办法监测进入背景的事件。同时,由于每次跳转都会开启一个计时器任务,存在一定程度上的内存浪费(不过基本可以忽略)。

二、onTrimMemory监听(API>=14)

在API14中,android提供了一个新的状态监听的接口 ComponentCallbacks2。在当中的onTrimMemory方法中,提供了一个状态参数 TRIM_MEMORY_UI_HIDDEN。官方文档对这个状态的解释如下:
“Level for onTrimMemory(int): the process had been showing a user interface, and is no longer doing so. Large allocations with the UI should be released at this point to allow memory to be better managed.” 该进程已经展示一个UI界面(onCreate完成?),并且停止了展示。UI中分配了的大块内存应该在这个时候被释放,以保证更佳的内存管理。
所以,这应该算是一个“正式”的进入后台标志了,之所以打引号,是因为这个状态本意上并不是为了监听进入后台而设计的,它只是为了让开发人员能够在程序进入后台的时候去释放一些内存。总而言之,这也是一种方法。
首先,你需要实现一个ComponentCallbacks2接口,并监听TRIM_MEMORY_UI_HIDDEN状态:
public class MemoryBoss implements ComponentCallbacks2 {    @Override    public void onConfigurationChanged(final Configuration newConfig) {    }    @Override    public void onLowMemory() {    }    @Override    public void onTrimMemory(final int level) {        if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {            // 进入后台        }        // 如果有必要,你也可以进行一些清理内存操作    }}
然后,在BaseActivity注册监听:
MemoryBoss mMemoryBoss;@Overridepublic void onCreate() {   super.onCreate();   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {      mMemoryBoss = new MemoryBoss();      registerComponentCallbacks(mMemoryBoss);   } }@Overridepublic void onDestroy(){    super.onDestroy();    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {        unregisterComponentCallbacks(mMemoryBoss);    }}
如果想要监听从后台返回的事件的话,在进入后台的时候设置个boolean值,然后在onResume时候进行判断即可。如果onResume的时候,程序已经进入后台,则可以判断是从后台返回的了。准确率是100%,但是只支持API14及之后。

三、计数方法

在Activity跳转的时候,总是先调用新Activity的onStart方法,然后再调用旧Activity的onStop方法。因此,可以利用这个逻辑,做一个Activity的计数效果,在onStart的时候加1,在onStop的时候减一。那么跳转的时候,因为是先加后减,所以值是恒大于0的。而进入后台的时候,先减后加,会出现等于0的效果。实现代码如下所示:
public abstract class BaseActivity extends Activity {    private static int sessionDepth = 0;    @Override    protected void onStart() {        super.onStart();if (sessionDepth == 0) {    // 从后台返回}               sessionDepth++;    }    @Override    protected void onStop() {        super.onStop();        if (sessionDepth > 0)            sessionDepth--;        if (sessionDepth == 0) {            // 进入后台        }    }}
这个方法就显得比较优美了,代码逻辑简单,效果也不错。

四、利用onWindowFocusChanged的方法

这个方法是利用onWindowFocusChanged事件来进行跳转的判断。这个方法的原理和计数法类似,都是利用onStart在onStop之前被调用。当Activity结束时,activity会首先失去焦点,因此,可以通过这个事件去获取进入后台事件。代码如下:
    // 用于判断是否从后台返回或者是否到后台    public static boolean isAppWentToBg = false;    public static boolean isWindowFocused = false;    public static boolean isBackPressed = false;    @Override    public void onBackPressed() {        isBackPressed = true;super.onBackPressed();    }    @Override    protected void onStart() {        if (isAppWentToBg) {            isAppWentToBg = false;            // 从后台返回        }        super.onStart();    }    @Override    protected void onStop() {        super.onStop();        if (!isWindowFocused) {            isAppWentToBg = true;            // 进入后台        }    }    @Override    public void onWindowFocusChanged(boolean hasFocus) {        isWindowFocused = hasFocus;        if (isBackPressed && !hasFocus) {            isBackPressed = false;            isWindowFocused = true;        }        super.onWindowFocusChanged(hasFocus);    }
同样的,这个逻辑准确率也高。同时,它还加入了判断返回键的逻辑,更加人性化。

五、总结

从理论上来说,监测后台和程序之间的跳转事件,应该是需要从onStart和onStop入手的。因为在官方文档中,对这两个方法的解释是这样的:
onStart:Called when the activity is becoming visible to the user.当Activity可见时被调用
onStop:Called when the activity is no longer visible to the user, because another activity has been resumed and is covering this one. This may happen either because a new activity is being started, an existing one is being brought in front of this one, or this one is being destroyed.当Activity不可见时被调用,因为另一个Activity覆盖到了当前Activity上。这可能是由于一个新的Activity被启动了,一个已经存在的Activity被重新显示了,或者当前Activity被销毁了
因此,上述逻辑中的重写onResume和onPause操作都应当改为onStart和 onStop。上述的各种方法都是可用的,准确率也有保障,因此可以根据理解自行选择。
另外,在我自己实现这个逻辑的过程中,发现了这样一种现象。如果当前页面存在多个Activity的层级,从后台返回时会先调用第二个Activity的onStart,而不是栈顶的Activity。这可能是因为我设置了滑动返回的效果,即在栈顶Activity滑动过程中,之前的Activity会被显示出来。因此,必须保证两个Activity都是可见状态。这种情况下,想要在栈顶Activity实现返回弹出一个对话框的效果,如果直接按上述逻辑进行处理,则对话框会被显示在之前的Activity中,然后被新的Activity给覆盖掉。于是,我在监测到返回事件的时候,作了一个延迟操作,然后通过动态的获取栈顶Activity来弹出对话框,进而解决了这个问题。


总得来说,研究这个的过程还是十分的有趣,也让我一直以来对onStart和onStop的误解得到了修正。还是不得不吐槽一下,Google还是比百度强大很多。

0 0