Android Tips #33 TaskStackBuilder と NavUtils を使って Intent のスタックをつくる

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

TaskStackBuilder と NavUtils

TaskStackBuilder で合成スタックを簡単につくれる

TaskStackBuilder は Android 4.1 (APIレベル16) で導入された合成スタックをつくるユーティリティクラスです。複数の Intent をスタックしたタスクを簡単につくることができます。Support Package にも含まれているので Android 1.6 (APIレベル4) から実装することができます!

NavUtils で "Up" の画面遷移を実装できる

NavUtils は親子関係にある Activity の画面遷移を実装するためのユーティリティクラスです。NavUtils に含まれているメソッドは Android 4.1 以降であれば Activity のメソッドになっていますが、 Android 4.0 以前のバージョンで実装するためのクラスです。こちらも Support Package に含まれているので Android 1.6 から使うことができます。
"Up" という画面遷移は Android 3.0 (APIレベル11) で初めて導入されました。Android 2.3.3 以前のバージョンでは "戻る" 遷移はハードウェアキーの "Back" ボタンしかありませんでしたが、Android 3.0 からは ActionBar の "Up" ボタンで新しい形の戻る遷移ができるようになりました。

使うと便利なケース

TaskStackBuilder と NavUtils は、以下のような ルート 以外の Activity をいきなり起動したい場合に便利です。

  • App Widget からアプリを起動するとき
  • Notification からアプリを起動するとき
  • 暗黙的 Intent からアプリを起動するとき

合成スタックをつくった上で Activity を起動することで、 Back キーを操作をしたときに戻り先の Activity に遷移することができます。また Up 遷移を使うことによって起動した Activity の親の Activity に (これまで一度も遷移したことがなくても) 遷移することもできます。

TaskStackBuilder と NavUtils の使いかた

1. Activity の親子関係を定義する

まずは Activity 同士の親子関係を AndroidManifest.xml に定義します。Up で戻る先の Activity が親の Activity です。今回は下記のような親子関係を定義します。

  • GrandParentActivity
  • ParentActivity (GrandParentActivity の子)
  • MainActivity (ParentActivity の子)

これを AndroidManifest.xml に書くと下記のようになります。Android 3.0 以降は activity タグの android:parentActivityName 要素に、Android 2.3.3 (APIレベル10) 以前は meta-data タグに親 Activity のクラス名を定義します。

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    
    <!-- MainActivity -->
    <activity
        android:name="com.example.navutilssample.MainActivity"
        android:label="@string/app_name"
        android:parentActivityName="com.example.navutilssample.ParentActivity" >
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.example.navutilssample.ParentActivity" >
        </meta-data>
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    
    <!-- ParentActivity -->
    <activity
        android:name="com.example.navutilssample.ParentActivity"
        android:label="@string/app_name"
        android:parentActivityName="com.example.navutilssample.GrandParentActivity" >
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.example.navutilssample.GrandParentActivity" >
        </meta-data>
    </activity>
    
    <!-- GrandParentActivity -->
    <activity
        android:name="com.example.navutilssample.GrandParentActivity"
        android:label="@string/app_name" >
    </activity>
    
</application>

2. TaskStackBuilder で Intent をスタックする

次に TaskStackBuilder を使って Intent をスタックします。Android 4.1 以降は android.app.TaskStackBuilder を、Support Package を使う場合は android.support.v4.app.TaskStackBuilder を参照します。 addNextIntent() で Intent をスタックに追加していきます。追加後 startActivities() で Activity を開始します。すると最後にスタックに追加した Intent で指定した Activity が表示されます。
今回はサンプルなのでボタンを押したときに実行するような実装にしていますが、実際のタイミングは Notification がクリックされて起動したときなどになると思います。

findViewById(R.id.build_button).setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {

        // 複数の Intent をスタックして起動する
        TaskStackBuilder builder = TaskStackBuilder.create(MainActivity.this);
        builder.addNextIntent(new Intent(MainActivity.this, GrandParentActivity.class));
        builder.addNextIntent(new Intent(MainActivity.this, ParentActivity.class));
        builder.addNextIntent(new Intent(MainActivity.this, ParentActivity.class));
        builder.addNextIntent(new Intent(MainActivity.this, MainActivity.class));
        builder.addNextIntent(new Intent(MainActivity.this, MainActivity.class));
        builder.startActivities();
        finish();

    }
});

Android 2.3.3 以前ではスタックされない!

上記で実装した画面を Back キーで戻る操作をしてみると、下図のようになると思います。

Android 3.0 以降の場合
GrandParentActivity ← ParentActivity(2) ← ParentActivity(1) ← MainActivity(2) ← MainActivity(1)

Android 2.3.3 以前の場合
MainActivity

Android 2.3.3 以前の場合は TaskStackBuilder で積まれた Intent は無視され、最後に追加した Intent で起動した Activity だけになります。これは Back キーで前のタスクに戻ることができるようにするための仕様のようです。
じゃあ startActivity() と変わらないじゃん。と言われるとそんな気もしてきますが…少なくとも Android 2.3.3 以前のみのバージョンにだけ対応したい場合はそうかも知れません。しかし Android 3.0 以降は有効なので、これからは必要になってくるのではないでしょうか。全バージョンで同じような画面遷移にしたい場合はもう一工夫加えなければいけないと思います。

3. Up の画面遷移を実装する

次に Up の画面遷移を入れてみましょう。Up の画面遷移を実装するには Android 4.1 以降であれば Activity#navigateUpTo() メソッドを使います。Android 4.0 以前のバージョンは NavUtils#navigateUpTo() メソッドを使います。NavUtils はナビゲーションをサポートするユーティリティクラスで Support Package に含まれています。
また、親の Activity の Intent を取得するには Activity#getParentActivityIntent() を使います。Android 4.0 以前のバージョンは NavUtils#getParentActivityIntent() を使います。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    // API レベル 16 以降
    Intent upIntent = getParentActivityIntent();
    navigateUpTo(upIntent);
} else {
    // API レベル 15 以前
    Intent upIntent = NavUtils.getParentActivityIntent(MainActivity.this);
    NavUtils.navigateUpTo(MainActivity.this, upIntent);
}

上記の処理を MainActivity と ParentActivity に実装し、Up の操作をおこなうと以下のようになります。

GrandParentActivity ← ParentActivity ← MainActivity

ParentActivity と MainActivity の間に Activity がいくつもあったとしても Up は親の Activity に遷移するので、その間の Activity はクリアされます。また、スタック内に親の Activity がない場合は生成して遷移するので、確実に親の Activity に遷移できます。

まとめ

Android アプリの画面遷移は、正しく理解せずに実装するとすぐカオスになります。さらに Android 3.0 からは Up という新しい画面遷移が追加されました。さらにカオスになりそうなところですが、これはユーザーに一貫したユーザーエクスペリエンスを体験させるために生み出された概念です。Android デザインガイドラインを正しく理解し実装することで、ユーザーが「どのように操作すればどのように表示されるか」予想できるようなアプリに仕上げることができます。
最近はバージョン問わず Android 4.0 の UI・UX に合わせているアプリが増えてきています。今後は Android 4.0 に合わせた UI・UX を作っていくべきだと思います。TaskStackBuilder と NavUtils を使って Android 4.0 Ready なアプリをつくっていきましょう。

参考