要判斷當前應用是否處於後臺,有個很簡單的標準,當應用處於後臺的時候,應用中全部的activity確定都不處於運行中狀態,而且應用全部處於運行中的activity在切後臺時確定會執行onPause方法。所以經過判斷應用中全部的activity都不處於運行狀態就能夠知道當前應用處於後臺,當有一個應用或多個activity處於運行狀態時應用就處於前臺。下面是很經典的activity的生命週期圖。html
由上面可知,要判斷一個應用是否處於後臺狀態,就是要判斷應用中全部的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
由上面可知,要判斷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
//將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接口中實現相應的判斷切換先後臺邏輯就能夠了。框架
一、實現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,但此時應用是處於前臺的。
上面的方法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的生命週期函數的執行作一個統計處理,利用這個統計判斷應用是否處於先後臺。三者的關係圖:
當時在處理切換先後臺時的思路首先想到的是根據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…