arcanum_jp’s blog

おっさんの日記

Androidアプリの終了メモ

 アクティビティの終了はfinish()など説明は多いが、アプリを終了させるというところではあまり説明が無かったのでメモ

 MainActivity ⇒ SubActivity1 ⇒ SubActivity2 と言う遷移で、最後のSubActivity2でアプリを終了したい場合のメモ。まずアプリの終了ってどうやってやるのって検索していると以下のような情報に行き着きました。どうやらアプリを正常に一瞬で終わらせる魔法のコマンドは無いみたいですね。

アクティビティ、または、アプリを終了させるには複数の方法が存在します。

この記事を書くにあたり、てくめも様のSystem.exit() を使ってはいけない理由と、終了方法のまとめを参考にさせて頂きました。

http://inujirushi123.blog.fc2.com/blog-entry-29.html


 アプリの終了とは何だろうと考えた場合に、Androidのアプリのライフサイクルを理解する必要があります。そこでまずこちらのサイトを参考にしました。(5回シリーズですが、初回のみをリンクしています)。アクティビティのみならず、それが乗るタスクであったりプロセスであったりするものの理解が必要なんですね・・・(ひえぇ・・)

Androidのライフサイクルというと、たいていActivityについての話だけになりがちです。しかし、Androidの基本知識として、Activity、Process、Taskはセットで理解しておくほうが良いと思います。そうしないと思わぬバグを含むことになります。

http://blogand.stack3.net/archives/127

 この中で、アプリを終了すると言う意味では、

  1. Back Stackがゼロになったときが「ユーザー目線ではアプリの終了」になる。
  2. ただし、プロセスは生きている

 と理解しました。つまり、バックスタックをゼロにしたとしても、アプリプロセスが生きているために、アプリ内で宣言した(変更されるという意味で)staticなfinalではない変数やシングルトンなどのオブジェクトは終了直前の値を持っています。アプリ履歴や、アプリアイコンタップで復帰したときその値を保持しています。

1.については、現在フォアグラウンドにいるアクティビティから、ルートアクティビティまでタスクを戻し、そのルートアクティビティをfinish()すればまず、ユーザー目線でのアプリの終了は実現出来ます。

2.については、1.の状態にしたとしてもプロセスは生きているため、staticなfinalではない変数やシングルトンなどのオブジェクトが生きています。それらを初期化してやればプロセスは生きているが、アプリケーションとしては終了したという認識になるのではないかと

先のリンクで言うと、

⑦Intent#setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) (2012/04/26追加)
Activity#startActivity(Intent intent) でアクティビティ

http://inujirushi123.blog.fc2.com/blog-entry-29.html

が、使えるのではないでしょうか。これならルートアクティビティまで一気に中間のアクティビティを殺しながら遷移できます。それで書いたのが以下のコードです。アプリ終了ボタンを押されたらルートアクティビティまで戻る、その際に終了フラグをルートアクティビティに伝播させます。ルートアクティビティでは終了フラグが来ていたら自分自身をfinishさせ、なおかつ、staticやシングルトンを初期化します。

MainActivity

public class MainActivity extends AbstractActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Button b = (Button) findViewById(R.id.call_sub1);
        b.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent i = new Intent(getBaseContext(), SubActivity1.class);
                        startActivity(i);
                    }
                }
        );


    }

    @Override
    protected void onPostResume() {
        super.onPostResume();

        // 値を見てみる
        Log.d("test", "--------------------------------------------");
        Log.d("test", "static: " + StaticUtility.HOGE);
        Log.d("test", "singleton: " + SingletonClass.getInstance().hashCode());
        Log.d("test", "singleton: " + SingletonClass.getInstance().getSomeFlag());
        Log.d("test", "--------------------------------------------");

        // 終了フラグが起動してのアクティビティ起動なら、アプリを終了しろの合図
        Intent i = getIntent();
        if (i.getBooleanExtra("FINISH_APP",false)) {
            Toast.makeText(getBaseContext(), "しゅうりょうします", Toast.LENGTH_SHORT).show();
            finish();
            SingletonClass.getInstance().setNullInstance();  // 初期化
            StaticUtility.HOGE = null;                       // 初期化
        }

    }
}

SubActivity1

public class SubActivity1 extends AbstractActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sub1);

        Button b = (Button) findViewById(R.id.call_sub2);
        b.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent i = new Intent(getBaseContext(), SubActivity2.class);
                        startActivity(i);
                    }
                }
        );

    }

}

SubActivity2

public class SubActivity2 extends AbstractActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sub2);

        // アプリの内部でセットされる
        StaticUtility.HOGE = "ほげほげ?";
        SingletonClass.getInstance().setSomeFlag(true);


        Button finishapp = (Button) findViewById(R.id.finishapp);
        finishapp.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        Log.d("123", "アプリの終了!");

                        // ルートアクティビティまで終了フラグ付きで一旦戻ります。
                        Intent i = new Intent(getBaseContext(), MainActivity.class);
                        i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                        i.putExtra("FINISH_APP", true);
                        startActivity(i);

                    }
                }
        );

    }
}

SingletonClass

public class SingletonClass {

    private static SingletonClass _instance;

    public static final SingletonClass getInstance() {
        if (_instance == null) {
            _instance = new SingletonClass();
        }
        return _instance;
    }

    public void setNullInstance() {
        _instance = null;
    }

    private SingletonClass() {
        // private for singleton
    }

    private boolean _someFlag;
    public void setSomeFlag(boolean b){
        _someFlag = b;
    }
    public boolean getSomeFlag() {
        return _someFlag;
    }

}

StaticUtility

public class StaticUtility {

    /**
     * アプリのどこかで変更されて使われる値
     */
    public static String HOGE;

}

補足

 MainActivityクラスの以下のコードを削除すると、表面上はアプリが終了しますが再度起動すると以前の値を見ることができるので、この変数の初期化が必要と言うことが分かります。また、履歴から削除するとプロセスが削除されるためか変数も初期化されます。

            SingletonClass.getInstance().setNullInstance();  // 初期化
            StaticUtility.HOGE = null;                       // 初期化


しかしここまで書いて実はプロセスが不要ならプロセスを殺せばいいじゃない?と言うマリーアントワネット的なセリフが浮かびました。あるいはSystem#exit(int)など・・・まぁ、、推奨されていないのでやらないけど・・・

以上、遷移先の深いところからアプリを終了するという部分でのメモ。当然、MainActivity表示時にBackボタン押下での終了は上記では入っていませんので注意が必要です。

んーー、たぶんこれでいいのかな?と言う認識ですので、参考程度にしてください。