HomeAsUpで色々な画面に戻れるようにする

&autolink(id=main)

目次


参考にするリンク


構成説明

画面構成。

今回説明する画面構成を描いておく。

まずは HomeAsUp を有効にする。

setDisplayHomeAsUpEnabled(true)

onCreate()でもどこでもいいので次の一文を書く。
  1. getActionBar().setDisplayHomeAsUpEnabled(true);
アプリアイコンの左に「<」の矢印が出ればイベントがくるようになる。

onOptionsItemSelected(MenuItem)

そうしたら onOptionsItemSelected で android.R.id.home を受ける。
  1. @Override
  2. public boolean onOptionsItemSelected(MenuItem item) {
  3. boolean result = true;
  4. switch (item.getItemId()) {
  5. case android.R.id.home:
  6. Toast.makeText(this, "home", Toast.LENGTH_SHORT).show();
  7. break;
  8.  
  9. default:
  10. result = super.onOptionsItemSelected(item);
  11. break;
  12. }
  13. return result;
  14. }
この実装だと「home」っていうToastが表示される。

Top画面に戻るだけならこれでいい。

Developers Guid によるとこうなる。
これでどの画面からでも HomeActivity に戻ることができる。
  1. @Override
  2. public boolean onOptionsItemSelected(MenuItem item) {
  3. boolean result = true;
  4. switch (item.getItemId()) {
  5. case android.R.id.home:
  6. // 戻る先は HomeActivity
  7. Intent intent = new Intent(this, HomeActivity.class);
  8. // 戻るときに開いていたActivityを全部閉じる
  9. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  10. startActivity(intent);
  11. break;
  12.  
  13. default:
  14. result = super.onOptionsItemSelected(item);
  15. break;
  16. }
  17. return result;
  18. }

ここまでのソース(これを各Activityで継承)

public class MyBaseActivity extends Activity
  1. abstract public class MyBaseActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. getActionBar().setDisplayHomeAsUpEnabled(true);
  6. }
  7.  
  8. @Override
  9. public boolean onOptionsItemSelected(MenuItem item) {
  10. boolean result = true;
  11. switch (item.getItemId()) {
  12. case android.R.id.home:
  13. // 戻る先は HomeActivity
  14. Intent intent = new Intent(this, HomeActivity.class);
  15. // 戻るときに開いていたActivityを全部閉じる
  16. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  17. startActivity(intent);
  18. break;
  19.  
  20. default:
  21. result = super.onOptionsItemSelected(item);
  22. break;
  23. }
  24. return result;
  25. }
  26. }

特定のActivityに戻るように書き換える。

○○画面を通過していたらそこに戻りたい理由。

で、本題。
1つ1つのActivityやFragmentに同じことを書くのは嫌なので、
共用のユーティリティクラスやActivity/Fragmentを継承した
MyBaseActivity みたいなものを普段は作る。
そこで自動的に HomeActivity に戻るようにすると、
  • [HomeActivity] => [FirstActivity]
  • [HomeActivity] => [FirstActivity] => [IlkActivity]
  • [HomeActivity] => [FirstActivity] => [IkinciActivity]
  • [HomeActivity] => [SecondActivity]
  • [HomeActivity] => [SecondActivity] => [ErsteActivity]
のどれからでも HomeActivity に戻る・・・のだが、
  1. FirstActivity を経由していたら FirstActivity に戻る
  2. SecondActivity を経由していたら SecondActivity に戻る
としたい。

これだけなら 第3階層 の
  • IlkActivity
  • IkinciActivity
  • ErsteActivity
に、専用の MyBaseActivity を作ればいいんだけど、
  • ErsteActivity
は 他アプリ からも起動される画面なので、
SecondActivityを通過していないから戻りたくない。

そんなワガママなことをしたい。

解1:ActivityLifecycleCallbacksでActivityの通過を受ける。

概要はこちらに書いてあるので、まずは読んで欲しい。
Application.ActivityLifecycleCallbacksでActivityを監視する

Activityのスタックを管理(ActivityStackManager)

長いのでファイルで。
ActivityStackManager.java
では適当に解説。

ActivityStackManager#ActStack
各Activityのスタックを覚える。
あとで android.content.ComponentName を使ってIntentを投げるので覚えておく。
インスタンスの見分け方は色々あるけれど、
Activity#toString は ハッシュコード付きなんで“ほぼ”特定可能。
絶対特定したいなら「専用の見分ける」コードを作るといい。

ActivityStackManager#searchRootComponentName(Activity)
指定Activityが登録してあるスタックの、ルートComponentNameを返す。
普段はIntentを投げるActivityから呼び出される想定なので、
そのActivityの登録を見に行く。
他の使い方が全く思いつかないのでこうしているけれど、
専用の「見分ける何か」をActivityに準備しているなら、
それを渡すようにしてもいいと思う。

Activityのスタックを管理(Application)

ま、これは定型文。
使うだけならこれでも十分。
  1. public class TestApplication extends Application {
  2.  
  3. private static final ActivityStackManager CALLBACK = new ActivityStackManager();
  4.  
  5. @Override
  6. public void onCreate() {
  7. super.onCreate();
  8.  
  9. registerActivityLifecycleCallbacks(CALLBACK);
  10. }
  11.  
  12. public ComponentName getRootComponentName(Activity activity) {
  13. return CALLBACK.searchRootComponentName(activity);
  14. }
  15. }

Activityのスタックを管理(Activity)

自分がルートの場合「name.equals(this.getComponentName())」は
とりあえず戻ることにした。
それ以外の時は「intent.setComponent(name);」を設定することで
ルートに戻る。
Intent.makeRestartActivityTask(ComponentName)
を使いたかったが
Intent.FLAG_ACTIVITY_CLEAR_TASK
が指定されているので全てのActivityを殺してしまう。
ちょっと今回の意図と違う動きなので自分で投げることに。

ソースはこんな感じで。

  1. @Override
  2. abstract public class MyBaseActivity extends Activity {
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. getActionBar().setDisplayHomeAsUpEnabled(true);
  7. }
  8.  
  9. @Override
  10. public boolean onOptionsItemSelected(MenuItem item) {
  11. boolean result = true;
  12. switch (item.getItemId()) {
  13. case android.R.id.home:
  14. TestApplication application = (TestApplication) getApplication();
  15. ComponentName name = application.getRootComponentName(this);
  16. if (name.equals(this.getComponentName())) {
  17. onBackPressed();
  18. } else {
  19. Intent intent = new Intent();
  20. intent.setComponent(name);
  21. // 戻るときにそれ以降で開いていたActivityを全部閉じる
  22. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  23. startActivity(intent);
  24. }
  25. break;
  26.  
  27. default:
  28. result = super.onOptionsItemSelected(item);
  29. break;
  30. }
  31. return result;
  32. }
  33. }

解1:まとめ

ActivityLifecycleCallbacksを利用すればスタックを保存できるので、
どんな場所にも移動できる処理は書ける。
ActivityStackManager.java
で管理自体は可能。

でも、本当にやりたいのはAndroid標準でサポートしているナニカを使い、
できるだけ自動で動かしたいところだ。

まとめ

現在は「解1」しか手段を持っていない。
それでも実現自体は可能なので問題ないのだが・・・。

ActivityのLifecycleはどうしてもフレームワークの実装に偏るので、
日本の端末では特殊な動作でフレームワークがActivityを消すルートがありそうで
この実装では怖い。

もっと簡単に、手軽にスタックを取得できないものだろうか。

関連リンク

&trackback()



最終更新:2012年02月13日 00:03