Android松耦合监听先后台切换框架

应用处于后台知足什么条件

要判断当前应用是否处于后台,有个很简单的标准,当应用处于后台的时候,应用中全部的activity确定都不处于运行中状态,而且应用全部处于运行中的activity在切后台时确定会执行onPause方法。所以经过判断应用中全部的activity都不处于运行状态就能够知道当前应用处于后台,当有一个应用或多个activity处于运行状态时应用就处于前台。下面是很经典的activity的生命周期图。html

在这里插入图片描述

activity和application的关系

由上面可知,要判断一个应用是否处于后台状态,就是要判断应用中全部的activity都不处于运行状态。怎样判断应用中的activity都不处于运行状态呢?因为一个应用只有application,而且这个application是全部activity所公有的。下面由activity和application的关系来看怎么判断应用中的全部activity都不处于运行状态。 下面是Activity在执行时onCreate()时的代码,可知在activity的onCreate中调用了Application的dispatchActivityCreated方法。java

@MainThread
    @CallSuper
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.dispatchCreate();
      	getApplication().dispatchActivityCreated(this, savedInstanceState);
    }
复制代码

其实在Activity执行的每一个生命周期都会调用Application中相应的方法,对应的关系以下图: react

在这里插入图片描述
当应用中执行onResume方法的次数和执行onPause的次数同样时,能够判断全部活跃的activity都执行了onPause方法此时应用中全部的activity都不活跃,确定处于后台。由于每个activity执行onPause方法的时候都会调用application中的disPatchActivityOnPause方法,执行onResume方法的时候都会调用application中的disPatchActivityOnResume方法,所以能够在这两个方法中进行统计,当两种方法的执行次数相同时此时切应用切后台,当onResume的次数大于onPause的次数1时此时从后台切到前台。

application和ActivityLifecycleCallbacks 关系

由上面可知,要判断application是否处于后台,就要用到application中的disPatchActivityOnPause方法和disPatchActivityOnResume方法,统计这两个方法执行的次数是否相同,下面是application中的dispatchActivityResumed和dispatchActivityPaused方法,因为dispatchActivityResumed和dispatchActivityPaused是package权限的所以咱们并不能继承application而且重写dispatchActivityResumed和dispatchActivityPaused方法在dispatchActivityResumed和dispatchActivityPaused方法中进行计数和判断是否切后台。android

/* package */ void dispatchActivityResumed(Activity activity) {
        Object[] callbacks = collectActivityLifecycleCallbacks();
        if (callbacks != null) {
            for (int i=0; i<callbacks.length; i++) {
                ((ActivityLifecycleCallbacks)callbacks[i]).onActivityResumed(activity);
            }
        }
    }

    /* package */ void dispatchActivityPaused(Activity activity) {
        Object[] callbacks = collectActivityLifecycleCallbacks();
        if (callbacks != null) {
            for (int i=0; i<callbacks.length; i++) {
                ((ActivityLifecycleCallbacks)callbacks[i]).onActivityPaused(activity);
            }
        }
    }
复制代码

可是在这两个方法中都调用了ActivityLifecycleCallbacks中相应的方法,所以能够从ActivityLifecycleCallbacks和application关系入手处理。查看application中的代码可知,ActivityLifecycleCallbacks 是application中的内部类,下面是ActivityLifecycleCallbacks 中的方法。git

public interface ActivityLifecycleCallbacks {
        void onActivityCreated(Activity activity, Bundle savedInstanceState);
        void onActivityStarted(Activity activity);
        void onActivityResumed(Activity activity);
        void onActivityPaused(Activity activity);
        void onActivityStopped(Activity activity);
        void onActivitySaveInstanceState(Activity activity, Bundle outState);
        void onActivityDestroyed(Activity activity);
    }
    
复制代码

其中application和ActivityLifecycleCallbacks 的方法的关系以下图: github

在这里插入图片描述
再看看application和ActivityLifecycleCallbacks的关系,

//将ActivityLifecycleCallbacks 注册到application中
    public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
        synchronized (mActivityLifecycleCallbacks) {
            mActivityLifecycleCallbacks.add(callback);
        }
    }
//注销注册
    public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
        synchronized (mActivityLifecycleCallbacks) {
            mActivityLifecycleCallbacks.remove(callback);
        }
    }
    
复制代码

因为咱们不能直接在application中的方法中进行处理统计,可是根据application和ActivityLifecycleCallbacks 关系,能够本身定义一个ActivityLifecycleCallbacks 而后注册到application中,而后在ActivityLifecycleCallbacks 的方法中进行统计处理判断应用是否切先后台。其中activity和application和ActivityLifecycleCallbacks 三个的关系以下图: app

在这里插入图片描述

实现先后台切换监听框架

由上面可知,能够经过实现ActivityLifecycleCallbacks接口,并将其注入到application中,根据activity,application,和ActivityLifecycleCallbacks的关系,只要在ActivityLifecycleCallbacks接口中实现相应的判断切换先后台逻辑就能够了。框架

方法1 判断是否处于先后台

一、实现Application.ActivityLifecycleCallbacks接口中的onActivityPaused和onActivityResumed方法ide

class Foreground implements Application.ActivityLifecycleCallbacks {
	private boolean foreground;//是否处于前台
    private static Foreground instance;

    public static void init(Application app){
        if (instance == null){
            instance = new Foreground();
            app.registerActivityLifecycleCallbacks(instance);
        }
    }

    public static Foreground get(){
        return instance;
    }

	public boolean isForeground(){
    return foreground;
	}

	public boolean isBackground(){
    return !foreground;
	}

	public void onActivityPaused(Activity activity){
    foreground = false;
	}

	public void onActivityResumed(Activity activity){
    foreground = true;
	}
}
复制代码

在Foreground类中,经过init方法将Foreground注入到当前的application中,监听application中全部activity的生命周期变化状态。因为判断是否切换先后台只须要在onActivityPaused,和onActivityResumed方法中进行,因此对这两个方法进行重写。 使用方法:在任何一个activity中可使用:函数

Foreground.get(context.getApplication()).isForeground()
复制代码

来判断当前应用是否处于先后台,可是这个方法有两个缺点:

一、只能判断当前应用是不是先后台,可是不能监听到先后台变换,也就是说若是要实时监听先后台变换,是不能的。

二、当用户在进行两个activity切换的时候,在第一个activity执行完onPause方法,而第二个activity还没执行到onResume方法的时候,此时isForeground()方法返回的值是false,但此时应用是处于前台的。

方法2 改进的先后台监听方法

上面的方法1中,存在的第一点问题是应用是否处于先后台须要在代码中实时的获取,不能获得切换先后台变化的通知,所以可使用监听的方式,往Foreground类中注入监听,当应用切换先后台时遍历注册了监听的类,通知其先后台切换变化。

class Foreground implements Application.ActivityLifecycleCallbacks {

    public interface Listener {
        public void onBecameForeground();//当应用切前台,调用onBecameForeground()方法
        public void onBecameBackground();//当应用切后台时,调用onBecameBackground()方法
    }

    ...
}

复制代码

在ActivityLifecycleCallbacks 中定义了监听Listener ,和两个方法用于监听切后台和切前台操做。下面提供注册监听和注销监听的方法。

private List listeners =
    new CopyOnWriteArrayList();

public void addListener(Listener listener){
    listeners.add(listener);
}

public void removeListener(Listener listener){
    listeners.remove(listener);
}
复制代码

而后在ActivityLifecycleCallbacks 的onActivityResumed方法和onActivityPaused方法中进行以下处理:

@Override
public void onActivityResumed(Activity activity) {
    paused = false;
    boolean wasBackground = !foreground;
    foreground = true;

    if (check != null)
        handler.removeCallbacks(check);

    if (wasBackground){
        Log.i(TAG, "went foreground");
        for (Listener l : listeners) {
            try {
                l.onBecameForeground();
            } catch (Exception exc) {
                Log.e(TAG, "Listener threw exception!", exc);
            }
        }
    } else {
        Log.i(TAG, "still foreground");
    }
}

@Override
public void onActivityPaused(Activity activity) {
    paused = true;

    if (check != null)
        handler.removeCallbacks(check);

    handler.postDelayed(check = new Runnable(){
        @Override
        public void run() {
            if (foreground && paused) {
                foreground = false;
                Log.i(TAG, "went background");
                for (Listener l : listeners) {
                    try {
                        l.onBecameBackground();
                    } catch (Exception exc) {
                        Log.e(TAG, "Listener threw exception!", exc);
                    }
                }
            } else {
                Log.i(TAG, "still foreground");
            }
        }
    }, CHECK_DELAY);
}

复制代码

在上面的代码中提供了一个paused ,和wasBackground 的bool变量,这两个变量很是重要。

一、wasBackground变量 当有activity执行onResume方法触发了onActivityResumed方法时,此时要先判断应用以前是否处于后台,若是应用处于后台才通知全部的监听此时应用从后台切到了前台。不然若是应用原本就在前台,执行onActivityResumed方法时不能通知全部的监听。

二、paused 变量 前面提到过当两个activity进行切换的时候,一个activity会执行onPause和另外一个activity会执行onResume方法,在执行onPause方法和onResume方法之间的间隔里面,应用处于前台,可是isForeground的值此时会是false,因此要对这个时间间隔进行处理,使得在这个间隔内,后台监听不可以回调。 三、handler 因为onPause和onResume执行的时间间隔内可能会出现错误的通知,因此把onPause方法中的通知延后一个时间间隔再执行,延后一个时间间隔后若是此时应用还处于前台而且没执行onActivityResumed方法,此时就通知切后台监听。

注意

虽然在onPause方法中增长了Handler来延时执行切后台监听通知,可是仍是存在问题的,好比用户快速切后台并返回,若是在速度在handler延迟的时间间隔以内,此时后台监听是收不到回调的。

完整代码

public class Foreground implements Application.ActivityLifecycleCallbacks {

    public static final long CHECK_DELAY = 500;
    public static final String TAG = Foreground.class.getName();

    public interface Listener {

        void onBecameForeground();

        void onBecameBackground();

    }

    private static Foreground instance;

    private boolean foreground = false, paused = true;
    private Handler handler = new Handler();
    private List<Listener> listeners = new CopyOnWriteArrayList<>();
    private Runnable check;

    /** * Its not strictly necessary to use this method - _usually_ invoking * get with a Context gives us a path to retrieve the Application and * initialise, but sometimes (e.g. in test harness) the ApplicationContext * is != the Application, and the docs make no guarantees. * * @param application * @return an initialised Foreground instance */
    public static Foreground init(Application application) {
        if (instance == null) {
            instance = new Foreground();
            application.registerActivityLifecycleCallbacks(instance);
        }
        return instance;
    }

    public static Foreground get(Application application) {
        if (instance == null) {
            init(application);
        }
        return instance;
    }

    public static Foreground get(Context ctx) {
        if (instance == null) {
            Context appCtx = ctx.getApplicationContext();
            if (appCtx instanceof Application) {
                init((Application) appCtx);
            } else {
                throw new IllegalStateException(
                        "Foreground is not initialised and " +
                                "cannot obtain the Application object");
            }
        }
        return instance;
    }

    public static Foreground get() {
        if (instance == null) {
            throw new IllegalStateException(
                    "Foreground is not initialised - invoke " +
                            "at least once with parameterised init/get");
        }
        return instance;
    }

    public boolean isForeground() {
        return foreground;
    }

    public boolean isBackground() {
        return !foreground;
    }

    public void addListener(Listener listener) {
        listeners.add(listener);
    }

    public void removeListener(Listener listener) {
        listeners.remove(listener);
    }

    @Override
    public void onActivityResumed(Activity activity) {
        paused = false;
        boolean wasBackground = !foreground;
        foreground = true;

        if (check != null)
            handler.removeCallbacks(check);

        if (wasBackground) {
            Log.i(TAG, "went foreground");
            for (Listener l : listeners) {
                try {
                    l.onBecameForeground();
                } catch (Exception exc) {
                    Log.e(TAG, "Listener threw exception!", exc);
                }
            }
        } else {
            Log.i(TAG, "still foreground");
        }
    }

    @Override
    public void onActivityPaused(Activity activity) {
        paused = true;

        if (check != null)
            handler.removeCallbacks(check);

        handler.postDelayed(check = new Runnable() {
            @Override
            public void run() {
                if (foreground && paused) {
                    foreground = false;
                    Log.i(TAG, "went background");
                    for (Listener l : listeners) {
                        try {
                            l.onBecameBackground();
                        } catch (Exception exc) {
                            Log.e(TAG, "Listener threw exception!", exc);
                        }
                    }
                } else {
                    Log.i(TAG, "still foreground");
                }
            }
        }, CHECK_DELAY);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }
}
复制代码

使用方法

能够在任何一个activity中注册监听先后台切换监听,而且在回调中进行相应的处理以下:

public class MainActivity extends AppCompatActivity {
    Foreground.Listener foregroundListener = new Foreground.Listener() {
        @Override
        public void onBecameForeground() {
            Log.d("foreground", "onBecomeForeground");
        }

        @Override
        public void onBecameBackground() {
            Log.d("foreground", "onBecameBackground");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Foreground.get(this).addListener(foregroundListener);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Foreground.get(this).removeListener(foregroundListener);
    }
}
复制代码

补充

补充1: 切换先后台最重要的是利用了activity和application的生命周期的关系进行的,由于每个activity执行生命周期函数的时候都会调用到application中对应的方法,因此能够在application中对全部执行过的activity的生命周期函数的执行作一个统计处理,利用这个统计判断应用是否处于先后台。三者的关系图:

在这里插入图片描述
补充2:

当时在处理切换先后台时的思路首先想到的是根据application的状态来判断。可是发现application其实并无提供一个方法来判断应用是否处于先后台。application实际上是一个进程而后看下这个进行中有哪些状态。

在这里插入图片描述

更简单完美的方法

上面的方法太复杂要根据所有的activity的状态来判断application的状态,Android提供了一个更加方便的方法来监听应用的先后台切换。 使用ProcessLifecycleOwner来处理 ProcessLifecycleOwner会监听application的状态。其中给出的说明以下:

* It is useful for use cases where you would like to react on your app coming to the foreground or
 * going to the background and you don't need a milliseconds accuracy in receiving lifecycle
 * events.
复制代码

ProcessLifecycleOwner这个类就是专门用来进行监听application的切换先后台状态的。 使用方法 定义ApplicationObserver 方法实现LifecycleObserver 接口,在里面定义onForeground(),和onBackground()方法来处理切换先后台的操做。

static class ApplicationObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void onForeground() {
            Log.d(TAG, "onForeground!");
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void onBackground() {
            Log.d(TAG, "onBackground!");
        }
    }
    
复制代码

在要监听的地方加上

ProcessLifecycleOwner.get().getLifecycle().addObserver(new ApplicationObserver());
复制代码

使用的一个完整例子:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ProcessLifecycleOwner.get().getLifecycle().addObserver(new ApplicationObserver());
        setContentView(R.layout.activity_main);
    }

    class ApplicationObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void onForeground() {
            Log.d(TAG, "onForeground!");
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void onBackground() {
            Log.d(TAG, "onBackground!");
        }
    }
}

复制代码

很是简单 注意 使用ProcessLifecycleOwner须要在项目的build.gradle中进行配置:

implementation "android.arch.lifecycle:extensions:1.1.1"
复制代码

参考文献

一、steveliles.github.io/is_my_andro…

二、developer.android.com/guide/compo…

三、medium.com/@arturogdg/…

相关文章
相关标签/搜索