應用處於後臺滿足什麼條件
要判斷當前應用是否處於後臺,有個很簡單的標準,當應用處於後臺的時候,應用中所有的activity肯定都不處於運行中狀態,並且應用所有處於運行中的activity在切後臺時肯定會執行onPause方法。因此通過判斷應用中所有的activity都不處於運行狀態就可以知道當前應用處於後臺,當有一個應用或多個activity處於運行狀態時應用就處於前臺。下面是很經典的activity的生命週期圖。
activity和application的關係
由上面可知,要判斷一個應用是否處於後臺狀態,就是要判斷應用中所有的activity都不處於運行狀態。怎樣判斷應用中的activity都不處於運行狀態呢?由於一個應用只有application,並且這個application是所有activity所公有的。下面由activity和application的關係來看怎麼判斷應用中的所有activity都不處於運行狀態。
下面是Activity在執行時onCreate()時的代碼,可知在activity的onCreate中調用了Application的dispatchActivityCreated方法。
@MainThread
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
}
其實在Activity執行的每個生命週期都會調用Application中相應的方法,對應的關係如下圖:
當應用中執行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方法中進行計數和判斷是否切後臺。
/* 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 中的方法。
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 的方法的關係如下圖:
再看看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 三個的關係如下圖:
實現前後臺切換監聽框架
由上面可知,可以通過實現ActivityLifecycleCallbacks接口,並將其注入到application中,根據activity,application,和ActivityLifecycleCallbacks的關係,只要在ActivityLifecycleCallbacks接口中實現相應的判斷切換前後臺邏輯就可以了。
方法1 判斷是否處於前後臺
1、實現Application.ActivityLifecycleCallbacks接口中的onActivityPaused和onActivityResumed方法
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()
來判斷當前應用是否處於前後臺,但是這個方法有兩個缺點:
1、只能判斷當前應用是否是前後臺,但是不能監聽到前後臺變換,也就是說如果要實時監聽前後臺變換,是不能的。
2、當用戶在進行兩個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變量,這兩個變量非常重要。
1、wasBackground變量
當有activity執行onResume方法觸發了onActivityResumed方法時,此時要先判斷應用之前是否處於後臺,如果應用處於後臺才通知所有的監聽此時應用從後臺切到了前臺。否則如果應用本來就在前臺,執行onActivityResumed方法時不能通知所有的監聽。
2、paused 變量
前面提到過當兩個activity進行切換的時候,一個activity會執行onPause和另一個activity會執行onResume方法,在執行onPause方法和onResume方法之間的間隔裏面,應用處於前臺,但是isForeground的值此時會是false,所以要對這個時間間隔進行處理,使得在這個間隔內,後臺監聽不能夠回調。
3、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"
參考文獻
1、http://steveliles.github.io/is_my_android_app_currently_foreground_or_background.html
2、https://developer.android.com/guide/components/activities/process-lifecycle