Intentによる画面遷移

ここではサンプルアプリを元に、Androidで画面遷移の基になるIntentの説明をします。

 

■ はじめに


多くのアプリは、一覧画面と詳細画面、タイトル画面とメイン画面...のように画面を複数持っており、それぞれの画面の作り方は単一画面と変わりません。しかし、Androidは、今までの携帯アプリ(Doja:docomo、MIDP:au、WLLCOM)と画面の遷移方法が異なるようです。(この辺、小職は経験が浅いので、未確認。)

 

■ サンプルアプリ概要


親画面(Main)と子画面(Editor)の2つの画面での画面遷移と、画面間でのデータの受け渡しをします。


参考:「@IT Androidアプリ作成入門」http://www.atmarkit.co.jp/fjava/rensai4/android03/android03_1.html

以下にアプリケーションの画面の動きを説明します。


① Main画面起動。
  起動時、テキストビューには何もセットされていない。
② Go!ボタンをクリック操作。
  この時、Editor画面のテキストボックス初期値("000")を渡している。
③ Editor画面に遷移。
④ テキストボックス内の入力値をキーボードから修正。
⑤ OKボタンをクリック操作。
  この時、Main画面のテキストビュー表示値として、テキストボックスの入力値を渡している。
⑥ Main画面に遷移。
  Editor画面で入力した値がテキストビューに表示される。


image007.jpg

 

■ Intentのその前に・・・

 

参考:「Android Developers 開発の基礎」http://developer.android.com/intl/ja/guide/topics/fundamentals.html

 

● Androidアプリケーションについて

 

AndroidアプリケーションはすべてJavaプログラミング言語で記述します。コンパイル済みのJava コード(およびそのアプリケーションに必要なすべてのデータやリソース ファイル)は、aaptツールを使用してAndroidパッケージにバンドルします。Androidパッケージは、拡張子が .apk のアーカイブファイルです。ユーザーは、このファイルをデバイスにダウンロードして利用します。つまり、Androidパッケージは、アプリケーションをモバイルデバイスに配布およびインストールするための媒体として機能します。1 つの .apkファイルに含まれているすべてのコードが、1 つのアプリケーションと見なされます。
各Android アプリケーションは、以下に示すさまざまな方法で他のアプリケーションから独立しています。

・すべてのアプリケーションは、デフォルトではそのアプリケーション個別のLinuxプロセスで実行されます。Androidは、アプリケーションコードの実行が必要になったときにプロセスを開始し、その必要がなくなって他のアプリケーションからシステムリソースを要求されたときにプロセスを終了します。

・プロセスごとに専用のJava仮想マシン(VM)が割り当てられるため、アプリケーションコードは他のアプリケーションから隔離された状態で実行されます。

・デフォルトでは、アプリケーションごとに固有の Linux ユーザーIDが割り当てられます。権限が設定されているため、アプリケーションのファイルはそのユーザーからしか認識できず、そのアプリケーション自体からのみ利用できます。ただし、ファイルを他のアプリケーションにエクスポートすることは可能です。

 

● アプリケーションコンポーネントについて


Android は、アプリケーションから別のアプリケーションの要素を利用できるます。たとえば、開発中のアプリケーションで画像の一覧をスクロール表示したい場合、他のアプリケーションで開発済みの適切なスクローラがあり、その利用が許可されていれば、独自に開発しなくてもそのスクローラを利用できます。アプリケーションに他のアプリケーションのコードを組み込んだり、リンクを設定したりする必要はありません。必要になった時点で、他のアプリケーションの一部分を開始するだけです。
この仕組みが機能するには、アプリケーション プロセスの一部分を必要に応じて開始でき、その部分のJavaオブジェクトをインスタンス化できなくてはなりません。そのため、Androidアプリケーションには、他のシステムで動作するアプリケーションでよく使用されるような、アプリケーション全体にアクセスするための単一のエントリポイント(たとえば main()関数)はありません。代わりに、システムが必要に応じてインスタンス化して実行できるコンポーネントで構成されます。コンポーネントには以下の4つのタイプがあります。

・アクティビティ
  アクティビティは、ユーザーが 1 つの操作を集中的に行うための視覚的なユーザーインターフェースを表します。たとえば、ユーザーが選択できるメニューアイテムの一覧を表示するアクティビティや、写真をキャプション付きで表示するアクティビティなどが考えられます。SMSアプリケーションなら、あるアクティビティでメッセージを送信する連絡先の一覧を表示し、別のアクティビティで選択した連絡先へのメッセージを入力し、その他のアクティビティで古いメッセージを参照したり設定を変更したりできます。これらのアクティビティを組み合わせて全体としてのユーザー インターフェースを形成しますが、それぞれのアクティビティは相互に独立しています。各アクティビティは、Activity 基本クラスのサブクラスとして実装されます。

・サービス
  サービスは、視覚的なユーザー インターフェースを持たず、バックグラウンドにおいて明確な終了期限がなくで実行されます。たとえば、ユーザーが他の操作をしている間 BGM を再生するサービス、ネットワーク経由でデータをフェッチするサービス、何かを計算してその結果をアクティビティに提供するサービスなどが考えられます。各サービスは、Service 基本クラスの拡張です。

・ブロードキャストレシーバ
  ブロードキャスト レシーバは、ブロードキャストの連絡を受信してそれに対処するだけのコンポーネントです。ブロードキャストの多くが元々はシステム コードで、たとえばタイム ゾーンが変更されたこと、電池の残量が少なくなったこと、写真が撮影されたこと、ユーザーが言語設定を変更したことなどを連絡するために使用します。アプリケーションでも、たとえば何らかのデータがデバイスにダウンロードされて利用できるようになったことを、他のアプリケーションにブロードキャストで知らせることができます。 アプリケーションでは、重要と思われるすべての連絡に応答できるよう、ブロードキャスト レシーバをいくつでも設定できます。すべてのレシーバは、BroadcastReceiver 基本クラスの拡張です。

・コンテンツプロバイダ
  コンテンツプロバイダは、アプリケーションのデータを他のアプリケーションでも利用できるようにします。データは、ファイルシステムやSQLiteデータベースなど、一般に利用できる方法で格納されていれば使用できます。コンテンツプロバイダは、ContentProvider基本クラスの拡張です。プロバイダが制御する型のデータを、他のアプリケーションから取得および格納するための標準メソッドセットを実装しています。ただし、これらのメソッドをアプリケーションから直接呼び出すことはできません。代わりに、ContentResolverオブジェクトのメソッドを呼び出します。ContentResolverは、すべてのプロバイダと通信でき、プロバイダと連携して関係のあるすべてのプロセス間通信を管理します。

 

■ Intentとは・・・


参考:「Android Developers 開発の基礎」http://developer.android.com/intl/ja/guide/topics/fundamentals.html


メッセージのコンテンツを保持するオブジェクトで、非同期メッセージによってコンポーネント(アクティビティ、サービス、ブロードキャストレシーバ)をアクティブ化します。(因みにコンテンツプロバイダは、ContentResolverからのリクエストの対象になるとアクティブ化されます。)
アクティビティやサービスの場合のIntentオブジェクトの主な役割は、リクエストされているアクションを指名し、その対象となるデータのURI(Uniform Resource Identifier)を指定することで、ユーザーから画像を表示するリクエストや、テキストを編集するリクエストをアクティビティに伝達できます。ブロードキャストレシーバの場合は、Intentオブジェクトがこれから通知を行うアクションを指名します。たとえば、カメラのボタンが押されたことを、関係のあるブロードキャストレシーバに通知できます。

・アクティビティを起動する(または何か新しい処理を実行させる)には・・・

IntentオブジェクトをContext.startActivity()またはActivity.startActivityForResult()に渡します。応答アクティビティでgetIntent()メソッドを呼び出すと、最初にそのアクティビティが起動されたときのインテントの内容を確認できます。Android によってアクティビティのonNewIntent()メソッドが呼び出され、アクティビティが後続のインテントに渡されます。
開始するアクティビティから結果が返される場合は、startActivity()ではなくstartActivityForResult()を呼び出します。結果は、呼び出し側のアクティビティの onActivityResult()メソッドに渡したIntentオブジェクトで返されます。

・サービスを開始する(または実行中のサービスに新しい指示を与える)には・・・

Context.startService() にIntentオブジェクトを渡します。Androidにより、サービスのonStart()メソッドが呼び出されてIntentオブジェクトが渡されます。
同様に、インテントをContext.bindService()に渡すと、呼び出し側のコンポーネントと対象となるサービスの間の継続中の接続を確立できます。サービスは、onBind()呼び出しでIntentオブジェクトを受け取ります(サービスがまだ開始されていない場合は、必要に応じてbindService()で開始できます)。たとえば、音楽再生サービスとの接続を確立するアクティビティを使用して、ユーザーが再生を操作するための手段(ユーザーインターフェース)を提供できます。アクティビティでbindService()を呼び出して接続を確立してから、サービスに定義されているメソッドを呼び出して再生を操作します。

・アプリケーションでブロードキャストを開始するには・・・

Context.sendBroadcast()、Context.sendOrderedBroadcast()、Context.sendStickyBroadcast() などのメソッドのいずれかのバリエーションにIntentオブジェクトを渡します。AndroidによってonReceive()メソッドが呼び出され、関係のあるすべてのブロードキャストレシーバにインテントが配信されます。

本資料では、アクティビティの起動を利用して、親画面⇔子画面の画面遷移と画面間のデータの受け渡しを説明します。

 

■ コンポーネントの終了


参考:「Android Developers 開発の基礎」http://developer.android.com/intl/ja/guide/topics/fundamentals.html


アクティビティを終了させるには、その finish()メソッドを呼び出します。あるアクティビティから startActivityForResult()で開始した別のアクティビティは、finishActivity()を呼び出して終了させることができます。
サービスは、その stopSelf()メソッドを呼び出すか、Context.stopService()を呼び出すことで停止できます。
因みに、コンテンツプロバイダは、ContentResolverからのリクエストに応答している間のみアクティブになります。ブロードキャストレシーバは、ブロードキャストメッセージに応答している間のみアクティブになります。これらのコンポーネントを明示的に終了させる必要はありません。

 

■ 開発環境


初めての方のため、開発環境を簡単に紹介します。


参考:「@IT Androidアプリ作成入門」http://www.atmarkit.co.jp/fjava/rensai4/android01/android01_2.html

 

MicroSoft WindowsXP Professional Version 2002 SP3
Eclipse Platform 3.3.2
JDK1.6.0_14
Android 1.5
Android Development Toolkit 0.9.1

 

■ 画面遷移の実装


1.画面の作成。


 参考ホームページからダウンロードできるサンプルには、画面レイアウトのxmlファイルも含まれていますが、新規画面のレイアウト作成は、Android Layout Editorを使用します。
以下に、2つの画面レイアウトの編集画面と、編集後のxmlファイルを示します。

 Main.xml

   

image020.jpg

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
    <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/TextView01" />
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/Button01" android:text="Go!" />
</LinearLayout>


Editor.xml

  image021.jpg

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
    <EditText android:id="@+id/EditText01" android:layout_width="wrap_content" android:layout_height="wrap_content" />
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/Button01" android:text="OK" />
</LinearLayout>

 

2.画面から自動生成されたデータクラス・ソース。

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.example.android.dual;

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class id {
        public static final int Button01=0x7f050001;
        public static final int EditText01=0x7f050000;
        public static final int TextView01=0x7f050002;
    }
    public static final class layout {
        public static final int editor=0x7f030000;
        public static final int main=0x7f030001;
    }
    public static final class string {
        public static final int app_name=0x7f040000;
    }
}

 

3.マニフェストの修正

マニフェストファイル(AndroidManifest.xml)は、Androidプロジェクト作成時に自動生成されます。今回、画面遷移先を追加するにあたって、マニフェストにアクティビティを追加する必要があります。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example.android.dual"
      android:versionCode="1"
      android:versionName="1.0.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:label="@string/app_name" android:name=".Main">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:label="@string/app_name" android:name=".Editor"/>←追加した子画面のActivity
    </application>
</manifest>

 

4.Main.java、Editor.javaの作成。


 親画面(Main.javaのコード例を示します。

package com.example.android.dual;

 

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

 

public class Main extends Activity {

    // リクエストコードの定義

    private static final int SHOW_EDITOR = 0;

    protected static final String TAG = "Main"; // soka add

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

        Log.d(TAG, "### onCreate Start ###");   // soka add

        // 初期画面の生成

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

 

        Button button = (Button)findViewById(R.id.Button01);

        // コールバック関数を登録

        button.setOnClickListener(new View.OnClickListener() {                 ...①

 

            @Override

        // コールバック関数処理内容を定義

            public void onClick(View v) {                                       ...②

                Log.d(TAG, "### onClick Start ###");    // soka add

                Intent intent = new Intent(Main.this, Editor.class);           ...③

                TextView textView =

 (TextView)findViewById(R.id.TextView01);

                CharSequence text = textView.getText();

// 遷移先に渡す付加情報をIntentに設定

             intent.putExtra("TEXT", text + "000");                                 ...④

// 遷移先のActivity(結果有り)を起動

                startActivityForResult(intent, SHOW_EDITOR);                ...⑤

                Log.d(TAG, "### onClick Ended ###");    // soka add

            }

 

        });

        Log.d(TAG, "### onCreate Ended ###");   // soka add

    }

 

    @Override

    // 遷移先から結果が返された時のコールバック定義

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {...⑥

        Log.d(TAG, "### onActivityResult Start ###");   // soka add

        // リクエストコードの確認

        if (requestCode == SHOW_EDITOR) {                                                 ...⑦

            // 結果の確認

            if (resultCode == RESULT_OK) {                                                  ...⑧

                TextView textView = (TextView)findViewById(R.id.TextView01);

                // 子画面からのデータを取得

                textView.setText(data.getCharSequenceExtra("TEXT"));;...⑨

            }

        }

        Log.d(TAG, "### onActivityResult Ended ###");   // soka add

    }

 

} 

① ビュー(ここではボタン)をクリックした時のコールバック関数を登録する。
② ビュー(ここではボタン)をクリックした時の処理内容を定義する。
③ 遷移元のコンテキストと遷移先のクラスを指定して、Intentを作成する。
④ 遷移先に渡す付加情報を名前と値で設定する。
⑤ 遷移先のActivityを起動する。SHOW_EDITORは呼び出し元のActivityで定義したリクエストコードで、遷移先から戻ってきたときに使用する。
⑥ 遷移先から結果が返された際に呼び出されるメソッド。
⑦ ⑤で指定したリクエストコードの確認。
⑧ ⑰で指定した結果コードの確認。
⑨ 遷移先から渡された付加情報を取り出し、TextViewに設定している。

  

子画面(Editor.java)のコード例を示します。 

package com.example.android.dual;

 

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

 

public class Editor extends Activity {

    protected static final String TAG = "Editor";   // soka add

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

        Log.d(TAG, "### onCreate Start ###");   // soka add

        // 初期画面の生成

        super.onCreate(savedInstanceState);

        setContentView(R.layout.editor);

 

        Button button = (Button)findViewById(R.id.Button01);

 

        // 遷移元からのIntentのデータマップを展開

        Bundle extras = getIntent().getExtras();                                              ...⑪

        if (extras != null) {

            EditText editText = (EditText)findViewById(R.id.EditText01);

            // 親画面からのデータを取得

            editText.setText(extras.getCharSequence("TEXT"));                  ...⑫

        }

 

        // コールバック関数を登録

        button. setOnClickListener (new View.OnClickListener() {               ...⑬

 

            @Override

        // コールバック関数処理内容を定義

            public void onClick (View v) {                                      ...⑭

                Log.d(TAG, "### onClick Start ###");    // soka add

// 空のIntentの作成

                Intent intent = new Intent ();                                                 ...⑮

                EditText editText = (EditText)findViewById(R.id.EditText01);

                CharSequence text = editText.getText();

// 遷移元に渡す付加情報をIntentに設定

                intent. putExtra ("TEXT", text);                         ...⑯

// 遷移元に処理結果を返す

                setResult(RESULT_OK, intent);                                           ...⑰

// Activityを終了

                finish();                                                                                     ...⑱

                Log.d(TAG, "### onClick Ended ###");    // soka add

            }

 

        });

        Log.d(TAG, "### onCreate Ended ###");   // soka add

    }

 

}

 

⑪ getIntent()はActivityを開始するためのIntentを取得する。この場合、画面遷移元で作成されたIntentが返る。
getExtras()は、Intentから展開したデータマップを返す。
⑫ 遷移元から渡された付加情報をキーワード(ここでは"TEXT")を元に取り出し、テキストボックスにセットしている。
⑬ ビュー(ここではボタン)をクリックした時のコールバック関数を登録する。
⑭ ビュー(ここではボタン)をクリックした時の処理内容を定義する。
⑮ 空のIntentを作成する。
⑯ 遷移元に渡す付加情報をキーワード(ここでは"TEXT")と値で設定する。
⑰ 遷移元に処理結果を返す。
⑱ 自Avtivityを終了する。(遷移元に制御が戻る。)


5.要点


① 画面レイアウトはAndroid Layout Editor などを使用し、xmlファイルを作成します。
② 画面のxmlファイルからADT(Android Developers Tools)により自動生成されたR.javaのID群を使用してActivityから画面の部品にアクセスします。
③ プロゲクト作成ウィザードで自動生成されるAndroidManifist.xmlは、アプリケーション内のActivityを定義しているので、Activityの増減により手を加える必要性があります。
④ 画面遷移の概略は前述4.のコード例を参照のこと。


■ 余談:デバッグログの出力


ログ出力は、エラー>警告>情報>デバッグ>その他(">"はレベルの高>低を示す)のレベルに区別して出力することができます。

 

import android.util.Log;
        ・・・
        Log.e(String tag, String msg);  // ERROR    エラー
        Log.w(String tag, String msg);  // WARN     警告
        Log.i(String tag, String msg);  // INFO      情報
        Log.d(String tag, String msg);  // DEBUG    デバック
        Log.v(String tag, String msg);  // VERBOSE その他
        ・・・

 

●ログ出力をEclipseで確認する方法


ログ出力はEclipseからLogCatビューで確認できます。 

image003.gif


今回のサンプルでソースコード上の動きを確認するために実装したログ出力例を示します。

07-27 04:02:45.224: DEBUG/Main(712): ### onCreate Start ###
07-27 04:02:45.435: DEBUG/Main(712): ### onCreate Ended ###

(「Go!」ボタンのクリック)

07-27 04:02:51.295: DEBUG/Main(712): ### onClick Start ###
07-27 04:02:51.345: DEBUG/Main(712): ### onClick Ended ###
07-27 04:02:51.516: DEBUG/Editor(712): ### onCreate Start ###
07-27 04:02:51.685: DEBUG/Editor(712): ### onCreate Ended ###

(テキストボックスの修正)
(「OK」ボタンのクリック)

07-27 04:03:03.086: DEBUG/Editor(712): ### onClick Start ###
07-27 04:03:03.115: DEBUG/Editor(712): ### onClick Ended ###
07-27 04:03:03.144: DEBUG/Main(712): ### onActivityResult Start ###
07-27 04:03:03.144: DEBUG/Main(712): ### onActivityResult Ended ###


●エミュレータ内部のログ出力をPCで取得する方法


エミュレータ起動以降の全ログを取得するときなどに有効です。(Eclipse上でLogCatの内容をクリアした後でも残っています。)


① PCのコマンドプロンプトからAndroid SDKに含まれるadbコマンドを使用して、エミュレータ上(Linux)のテキストファイルにAndroidで管理しているログ内容を展開します。

 C:\application\android-sdk\tools>adb logcat -d -f cache/20090729_2.txt

※Android SDK 1.5では、ホームディレクトリに書込み権限が無いので、サブディレクトリのcache配下に任意のファイル名で出力しています。


② エミュレータ内にログファイルができていることを確認します。

C:\application\android-sdk\tools>adb shell
# ls -l cache
ls -l cache
-rw------- root     root        31585 2009-07-29 01:37 20090729_2.txt
drwxrwx--- root     root              2009-07-16 10:14 lost+found
# exit
exit

C:\application\android-sdk\tools>

③ PCからエミュレータ内のログファイルを取得します。

 C:\application\android-sdk\tools>adb pull cache/20090729_2.txt .
329 KB/s (31585 bytes in 0.093s)

C:\application\android-sdk\tools>

④ ログファイルの内容の表示した例を示します。

C:\application\android-sdk\tools>type 20090729_2.txt | more
I/DEBUG   (  539): debuggerd: May 14 2009 17:33:50
I/vold    (  538): Android Volume Daemon version 2.0
E/vold    (  538): Error opening switch name path '/sys/class/switch/test2' (No
such file or directory)
E/vold    (  538): Error bootstrapping switch '/sys/class/switch/test2' (m)
E/vold    (  538): Error opening switch name path '/sys/class/switch/test' (No s
uch file or directory)
E/vold    (  538): Error bootstrapping switch '/sys/class/switch/test' (m)
D/vold    (  538): Bootstrapping complete
E/flash_image(  544): can't find recovery partition
D/qemud   (  546): entering main loop
D/qemud   (  546): fdhandler_accept_event: accepting on fd 10
D/qemud   (  546): created client 0xe078 listening on fd 8
D/qemud   (  546): fdhandler_event: disconnect on fd 8
D/qemud   (  546): fdhandler_accept_event: accepting on fd 10
D/qemud   (  546): created client 0xf028 listening on fd 8
D/qemud   (  546): client_fd_receive: attempting registration for service 'gsm'
D/qemud   (  546): client_fd_receive:    -> received channel id 1
D/qemud   (  546): client_registration: registration succeeded for client 1
D/AndroidRuntime(  541):
D/AndroidRuntime(  541): >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<
D/AndroidRuntime(  541): CheckJNI is ON
I/        (  542): ServiceManager: 0xac38
I/AudioFlinger(  542): AudioFlinger's thread ready to run for output 0



■ 参考ホームページ


「Android Developers」http://developer.android.com/intl/ja/index.html
「@IT Androidアプリ作成入門」http://www.atmarkit.co.jp/fjava/index/index_android.html
「Android入門」http://www.javadrive.jp/android/index.html

以上