[Android] 如何偵測 App 退回背景與重回前景

我記得在早期職涯剛接觸 Android 的時候,那時遇到這個需求,我的第一個想法就是在 Application 存放一個 boolean flag isAppActive,然後定義一個 abstract base activity,在 onStart 跟 onStop 的時候通知 Application:

public class MyApplication extends Application {
  private boolean isAppActive = false;
  private int activeActivityCounter = 0;

  public boolean isActive() {
    return isAppActive;
  }

  public void onActivityStart() {
    if (activeActivityCounter == 0) {
      isAppActive = true;
    }
    activeActivityCounter++;
  }

  public void onActivityStop() {
    activeActivityCounter--;
    if (activeActivityCounter == 0) {
      isAppActive = false;
    }
  }
}

public abstract class MyBaseActivity extends Activity {

  private MyApplication app;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    app = (MyApplication) getApplication();
  }

  @Override
  protected void onStart() {
    super.onStart();
    app.onActivityStart();
  }

  @Override
  protected void onStop() {
    super.onPause();
    app.onActivityStop();
  }
}

App 裡的每個 Activity 都必須繼承這個 Base Activity,這樣我們就能知道目前 app 是處於前景還是背景。

這個寫法基本上沒什麼問題,但如果做 code review,我會提幾個 concerns:
1. Activity 跟 Application 的互動方式。在這個寫法裡面,Application 算是 “被動" 地接收 Activity 的狀態變化通知。如果可以,我會希望是 Application “主動” 地觀察每個 Activity 的狀態變化。
2. 責任被分散在各個 Activity。如果團隊中有人寫了一個新的 Activity,但忘記繼承 Base Activity,這個方法就會出錯。

後來,Android 提供了一個更便利的方法讓我們觀察每個 Activity 的狀態變化:ActivityLifecycleCallbacks

public class MyApplication extends Application {
  private boolean isAppActive = false;
  private int activeActivityCounter = 0;

  public boolean isActive() {
    return isAppActive;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(
        new ActivityLifecycleCallbacks() {
          @Override
          public void onActivityCreated(
              @NonNull Activity activity, @Nullable Bundle savedInstanceState) {}

          @Override
          public void onActivityStarted(@NonNull Activity activity) {
            if (activeActivityCounter == 0) {
              isAppActive = true;
            }
            activeActivityCounter++;
          }

          @Override
          public void onActivityResumed(@NonNull Activity activity) {}

          @Override
          public void onActivityPaused(@NonNull Activity activity) {}

          @Override
          public void onActivityStopped(@NonNull Activity activity) {
            activeActivityCounter--;
            if (activeActivityCounter == 0) {
              isAppActive = false;
            }
          }

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

          @Override
          public void onActivityDestroyed(@NonNull Activity activity) {}
        });
  }
}

這個方法跟上面那個方法基本上是同一套邏輯,只是差別在於 Application 是主動觀察每個 Activity 的狀態變化,而且每個 Activity 也不需另外通知 Application,Android Framework 會幫我們完成這些事情。

這個寫法其實已經沒什麼好挑剔的,但如果真的要挑,我們可以考慮把 activeActivityCounterregisterActivityLifecycleCallbacks 裡面的內容做封裝,讓這些邏輯獨立在 Application 之外,這樣做不但可以保持 Application 乾淨簡短,我們也可以替這段邏輯做命名:AppStatusDetector。之後其他工程師看到這個 class,不用讀完整個檔案,馬上就可以理解它的目的。

其實,在我們這麼想的同時,Android 又提供了一個更好用的方法。

Google 在 AndroidX 裡面提供了很多改進以及好用的 libraries,其中一項很重要的東西 Lifecycle-aware components 就是為了解決生命週期以及狀態變化這個棘手問題。為了更優雅地處理 App 的狀態改變,我們可以利用 androidx.lifecycle 裡面的 ProcessLifecycleOwnerLifecycleObserver

public class MyApplication extends Application implements LifecycleObserver {
  private boolean isAppActive = false;

  public boolean isActive() {
    return isAppActive;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
  }

  @OnLifecycleEvent(Lifecycle.Event.ON_START)
  private void onAppStart() {
    isAppActive = true;
  }

  @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
  private void onAppStop() {
    isAppActive = false;
  }
}

得到的結果一樣,但 code 只有一半,因為 AndroidX 幫我們處理了很多細節。一些檢查邏輯都被封裝進 ProcessLifecycleOwner,如果我們點進去看,就會發現裡面做的事情跟上面第二種寫法的邏輯很類似,也是呼叫 registerActivityLifecycleCallbacks 來觀察每個 Activity 的狀態變化,只是它的判斷邏輯更精細、也更嚴謹。

這次會想要聊這個主題,主要是因為前陣子在一份 code 裡面,看到上面提到的第二種寫法,然後又讓我想起最近幾年看過的第三種寫法,想著想著… 又想起了自己剛學 Android 時寫過的第一種寫法。沒想到一個主題,可以帶我回顧自己的 Android 職涯,忽然有種感慨:時間過得好快啊…

發表留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料