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/…

相關文章
相關標籤/搜索