(Android) アプリのbackground/foregroundを検知する

Update 2018.01.18

2017年のGoogle I/OでArchitecture Componentsが発表されました。

Architecture Componentsにはいろいろな機能が含まれますが、その中にLifecycleという一連のコンポーネントがあります。

これはいろいろなクラスにAndroidのActivityやFragmentといったコンポーネントのライフサイクルを監視する機能を追加するためのコンポーネントです。
この中に、ProcessLifecycleOwnerというコンポーネントが用意されています。
下に記載しているMyActivityLifecycleCallbacksの代わりに似たようなLifecycleObserverを実装して、Application#onCreateとかでProcessLifecycleOwner#addObserverすると、実装がよりシンプルになるかもしれません。

ProcessLifecycleOwnerを使った際のざっくりしたイベントの対照表は下記のとおりです。

Lifecycle.Event対応するイベント
ON_CREATEアプリ起動時に一度だけ
ON_RESUMEアプリ起動時/バックグラウンドからの復帰時
ON_STARTアプリ起動時/バックグラウンドからの復帰時
ON_PAUSEアプリ終了時/バックグラウンドへの移行時
ON_STOPアプリ終了時/バックグラウンドへの移行時
ON_DESTROY一度も呼ばれない

---更新終わり。↓下から本文---


単純にonResume/onStartでバックグラウンド復帰時の処理を書くとActivityの生成時やバックキーで戻ってきた時等、処理しなくていいタイミングでもコードが走ってしまいます。

ActivityManager#getRunningAppProcessesで実行中のプロセスを取得し、アプリのforeground/backgroundステータスを見ることもできますが、一部端末でうまく動作しないことがあるようです(そもそもbackground/foregroundを判定するためだけに実行中のプロセスを全部調べるのもアホくさい気がします)。

そこで、API14(ICS)から追加された、Application.ActivityLifecycleCallbacksを利用します。このAPIを利用すると、すべてのActivityのライフサイクルを監視し、任意の処理を実行することができます。

コードは下記のとおりです。

java
public class MyApp extends Application {

    private AppStatus mAppStatus = AppStatus.FOREGROUND;

    public void onCreate() {
        super.onCreate();

        registerActivityLifecycleCallbacks(new MyActivityLifecycleCallbacks());
    }

    public MyApp get(Context context) {
        return (MyApp) context.getApplicationContext();
    }

    public AppStatus getAppStatus() {
        return mAppStatus;
    }

    // check if app is foreground
    public boolean isForeground() {
        return mAppStatus.ordinal() > AppStatus.BACKGROUND.ordinal();
    }

    public enum AppStatus {
        BACKGROUND,                // app is background
        RETURNED_TO_FOREGROUND,    // app returned to foreground(or first launch)
        FOREGROUND;                // app is foreground
    }

    public class MyActivityLifecycleCallbacks implements ActivityLifecycleCallbacks {

        // running activity count
        private int running = 0;

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

        }

        @Override
        public void onActivityStarted(Activity activity) {
            if (++running == 1) {
                // running activity is 1,
                // app must be returned from background just now (or first launch)
                mAppStatus = AppStatus.RETURNED_TO_FOREGROUND;
            } else if (running > 1) {
                // 2 or more running activities,
                // should be foreground already.
                mAppStatus = AppStatus.FOREGROUND;
            }
        }

        @Override
        public void onActivityResumed(Activity activity) {
        }

        @Override
        public void onActivityPaused(Activity activity) {
        }

        @Override
        public void onActivityStopped(Activity activity) {
            if (--running == 0) {
                // no active activity
                // app goes to background
                mAppStatus = AppStatus.BACKGROUND;
            }
        }

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

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

あとは任意の場所でMyApp.get(getContext()).getAppStatus()とか、MyApp.get(getContext()).isForeground()とか呼んであげれば、アプリが現在foregroundにいるのかbackgroundにいるのか、判定することができます。

backgroundからforegroundに復帰した時かどうか知りたい!ってときはMyApp.get(getContext()).getAppStatus()AppStatus.RETURNED_TO_BACKGROUNDと比較してあげればいいです。

軽く仕組みを説明すると、ActivityonStart/onStopに相当するonActivityStarted/onActivityStoppedで現在アクティブなActivityをカウントしているだけです。

Activity間を遷移していると、最低でも今いるActivityと、一つ前のActivityがアクティブな状態になります(running > 1な状態)。 アプリがbackgroundになると、すべてのActivityonStopを通るので、非アクティブな状態になります(running == 0な状態)。 また、アプリがforegroundに復帰すると直前まで表示されていたActivityonStartのみが実行されるので、running == 1になります。

この状態の変化を利用して、アプリのbackground/foregroundステータスを検知するのが上記のコードです。

Activityが複数作られる、という前提のコードなので1Activity複数FragmentなアプリやMortar/Flowなアプリだと意味がなさそうです。

こちらからは以上です。