2014年11月6日木曜日

Android メッセージと通知

Androidのメッセージングと通知についてのメモ


ざっと全体を把握し、Tipsとして使いやすく編集。

●Intent

Androidのメッセージで最も頻繁に使われる。やりとりするデータのまとまり。

送信者:Contextがメッセージとして送る仕組みをもつ。

受信者:Activity、Service、BroadcastReceiver

●Intentオブジェクト


□目的

メッセージを送信した相手に、処理を実行してもらう。

□Action 処理内容の記述

実行してほしい処理を示す。

・Activity用の代表的な処理内容

ACTION_VIEW
ACTION_MAIN
ACTION_SEND
ACTION_SENDTO
ACTION_EDIT
ACTION_PICK
ACTION_DELETE
ACTION_INSERT
ACTION_SEARCH
ACTION_CALL


・Broadcast用

ACTION_BOOT_COMPLETED
ACTION_SHUTDOWN
ACTION_PACKAGE_ADDED
ACTION_PACKAGE_REPLACE
ACTION_PACKAGE_REMOVED


□Category 処理すべき対象に期待する属性


CATEGORY_DEFAULT
CATEGORY_LAUNCHER
CATEGORY_HOME
CATEGORY_PREFERENCE
CATEGORY_BROWSABLE


□Data Actionの対象となるデータURI


ex)
ACTION_VIEWとしてDataを渡す → Dataを表示する


□Type Dataの種類を表すMIMEタイプ



□Component 期待するActionを実行するコンポーネント

すなわち、Intentっを送る対象のコンポーネント

・明示的Intent
Componentを明示する

・暗黙的Intent
Componentを明示しない


□Extras Intentを送る対象にわたす追加情報


KeyとValueのペアをBundleに追加してわたす。


□Flag

Activityの起動方法をシステムに通知するための情報


●Intent Filter


暗黙的インテントをうけとるコンポーネントが、Intentをハンドリグ可能かどうかを宣言するための仕組み
Androidフレームワークは、IntentFileterをもとにして暗黙的Intentの対象となるコンポーネントをリストアップし、
ユーザに選択肢を提供する。

□暗黙的Intentのハンドリング基準


・Action、Data、category の3つ

→ AndroidManifest で宣言が可能

<activity>の要素に<intent-filter>要素を追加する。

ex) 起動Activity

<intent-filter>
    <!-- アプリのメイン(入り口)となる Activity を起動するときの Action を受け取る -->
    <!-- Intent クラスに定義されている ACTION_MAIN 定数の実態は "android.intent.action.MAIN" という文字列 -->
    <action android:name="android.intent.action.MAIN" />
    <!-- ランチャーからの起動のものを受け取る -->
    <!-- Intent クラスに定義されている CATEGORY_LAUNCHER 定数の実態は "android.intent.category.LAUNCHER" という文字列 -->
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

ex) 画像共有Activity

 <intent-filter>
    <!-- ACTION_SEND または ACTION_SEND_MULTIPLE のいずれかを受け取る -->
    <!-- Intent に設定可能な Action は 1 つだけなので、<intent-filter> に Action を複数宣言すると -->
    <!-- その中からいずれかに該当するものを受け取る、という意味になる -->
    <action android:name="android.intent.action.SEND" />
    <action android:name="android.intent.action.SEND_MULTIPLE" />
    <!-- 暗黙的 Intent を扱う際に必須のカテゴリ -->
    <!-- システムは、Activity の起動に暗黙的 Intent を発行すると、 -->
    <!-- このカテゴリが付与されているものとして扱うため、Activity で暗黙的 Intent を受け取りたい場合は -->
    <!-- 必ずこのカテゴリを <intent-filter> に宣言しておく -->
    <!-- 複数のカテゴリを <intent-filter> に宣言した場合は、 -->
    <!-- 全てのカテゴリにマッチするもののみを受け取る、という意味になる -->
    <category android:name="android.intent.category.DEFAULT" />
    <!-- Data の種類の制限 -->
    <!-- MIME タイプのほか、URI のスキームを制限することもできる -->
    <data android:mimeType="image/jpeg" />
</intent-filter>


●Intentをつかった1対1のメッセージング


□Activityの起動


ex)
Intent intent = new Intent(this, NextActivity.class);
// Intent を Context に渡して、メッセージを送る
// この場合、NextActivity クラスにメッセージが送られ、NextActivity が立ち上がる
startActivity(intent);


□Activityを起動して、処理結果を期待する


ex) Intent送信
 startActivityForResult(new Intent(this, SubActivity.class), SubActivity.REQUEST_CODE_HOGE);


ex) 結果の受信
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // super.onActivityResult(int, int, Intent) の呼び出しは、条件に関係なくすること
    // Fragment から startActivityForResult(Intent, int) した場合の戻りの判定ができなくなってしまう
    super.onActivityResult(requestCode, resultCode, data);
    // requestCode には、startActivityForResult(Intent, int) の第 2 引数で指定したものが来る
    // resultCode には、呼び出し先で setResult(int, Intent) をコールした時の第 1 引数が来る
    // data には、呼び出し先で setResult(int, Intent) をコールした時の第 2 引数が来る
    switch (requestCode) {
        case SubActivity.REQEUST_CODE_HOGE:
             // REQUEST_CODE_HOGE の戻りが来た時の処理
             return;
        default:
             // 知らない requestCode の戻りが来た時の処理
             return;
    }
}

ex) Intent受信

public class SubActivity extends Activity {
    public static final int REQUEST_CODE_HOGE = 1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 何かする
        // 呼び出し元に返す Intent オブジェクトをセットする
        // 第 1 引数には、RESULT_OK または RESULT_CANCELLED、あるいは、RESULT_FIRST_USER を起点にした独自の int 型定数を使う
        setResult(RESULT_OK, new Intent());
        finish();
    }
}


□Serviceを起動するIntent

Serviceを呼び出す。

ex)
// サービスのバインド要求
bindService(new Intent(this, MyService.class), mServiceConnection, Context.BIND_AUTO_CREATE);

// サービスの開始要求
startService(new Intent(this, MyIntentService.class));


●Intentを用いた、1対多のメッセージング

ブロードキャスト:アプリ内外に問わず、Intentを全体へ投げかける仕組み。Androidでは端末の状態を全アプリに通知するための仕組みとして利用している。

□BroadcastReceiver

ブロードキャストされるIntentを受信するためのコンポーネント

【注意】宣言の仕方によって、使われ方が異なる。


□BroadcastReceiverのライフサイクル


Intentを受け取るためのコールバックメソッド onReceive をオーバーライドする。
BroadcastReceiverのライフサイクル = オーバーライドした onReceive の実行されている間

ex)
public class MyBroadcastReceiver extends BroadcastReceiver {
    // Broadcast された Intent を受け取るコールバック
    @Override
    public void onReceive(Context context, Intent intent) {
    }
}

□BroadcastReceiverプロセスの優先順位


最も優先順位の高いフォアグラウンドプロセス

・BroadcastReceiverのライフサイクルが終了したプロセス = 動作している他のコンポーネントがもつ優先順位となる
・BroadcastReceiverのみがプロセス上動作している場合、BroadcastReceiverのライフサイクルが終了すると、空プロセスとなり、システムが優先してkillする。

どちらの場合も、BroadcastReceiverを実行したプロセスはシステムによって終了可能で、プロセス上で非同期処理を実行した場合、処理が終わってもどる前に、
プロセスがkillされる可能性がある。よって、BroadcastReceiverのプロセスは非同期処理に向いていない。

非同期処理には、Service と呼ばれる仕組みを利用する。

また、BroadcastReceiverの中でダイアログを表示することも推奨されない。これも、ライフサイクルがダイアログのイベント実行より先に終了し、プロセスがkillされるからである。
ダイアログは、Notification と呼ばれる仕組みを利用する。


□Static BroadcastReceiver(静的ブロードキャスト)

AndroidManifestに宣言されるBroadcastReceiverでありアプリがインストールされている間はずっとBroadcastReceiverが動作し、ブロードキャストするIntentの監視を続ける。
ただし、Honeycomb以降は、1度でも起動することが条件である。

ex) AndroidManifest <application>要素の子として登録する
<receiver
    android:name="jp.mixi.sample.MyBroadcastReceiver">
    <intent-filter>
    </intent-filter>
</receiver>

ex) 受信コード

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    }
}

□Dynamic BroadcastReceiver(動的ブロードキャスト)


ActivityなどのContextで動的に登録/解除が行われるBroadcastReceiver。

・ActivityのContext
ActivityのContextの中で動的に登録する場合、解除もActivityのContextの中で実施する必要がある。
どのライフサイクルのContextで登録を行うかも大事。

「ActivityのContextで動的に登録 → ActivityのContextで解除する」のようにセットで行う必要がある。もし解除がされていない場合、
システムが自動で登録を解除し、適切に解除を行う指示をエラーログに出力する。

ex)
public class MyActivity extends Activity {
    private BroadcastReceiver mMyReceiver = new MyBroadcastReceiver();
    @Override
    protected void onStart() {
        super.onStart();
        // ACTION_PACKAGE_ADDED の Action を通す IntentFilter オブジェクトを作成
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        // MyBroadcastReceiver オブジェクトを、指定した IntentFilter オブジェクトで、Activity の Context に登録
        registerReceiver(mMyReceiver, filter);
    }
    @Override
    protected void onStop() {
        // MyBroadcastReceiver オブジェクトの登録を、Activity の Context 上から解除する
        // Activity のライフサイクルが終わりに向かうコールバックの中で実装する
        unregisterReceiver(mMyReceiver);
        super.onStop();
    }
}


・ApplicationのContext
アプリケーションの中でグローバルなスコープでBroadcastReceiverが動作する。
解除を忘れても、システムは自動で登録を解除しないので、リークの原因となる。


□Intentのブロードキャスト


・ブロードキャストの送信

ex)

Context#sendBroadcast(Intent)

ex) 定義したActionやCategoryで受け付けてくれるすべてのBroadcastReceiverがIntentを受信する。

public class MyActivity extends Activity {
    // 自分で独自の Action を定義することも可能
    public static final String ACTION_HOGEHOGE = "jp.mixi.sample.android.intent.action.HOGEHOGE";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // do something...
        // Intent のブロードキャスト
        Intent intent = new Intent();
        intent.setAction(ACTION_HOGEHOGE);
        sendBroadcast(intent);
    }
}


□LocalBroadcastManager アプリ内全体へのブロードキャスト

他のアプリに知られたくない、自プロセス内※の閉じたBroadcastを実現する。
※LocalBroadcastManagerはマルチプロセスをサポートしない。
・Privateデータを送信しても安心
・エクスプロイト対策
・軽量に利用可能

ex)
// LocalBroadcastManager は Support Package に含まれている
import android.support.v4.content.LocalBroadcastManager;
public class MainActivity extends Activity {
    public static final String TAG = MainActivity.class.getSimpleName();
    public static final String ACTION_HOGEHOGE = "jp.mixi.sample.android.intent.action.HOGEHOGE";
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.v(TAG, "local broadcast received.");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    protected void onStart() {
        super.onStart();
        // この Activity の Context の中での、Local な Broadcast を管理する為の LocalBroadcastManager オブジェクト
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
        manager.registerReceiver(mReceiver, new IntentFilter(ACTION_HOGEHOGE));
    }
    @Override
    protected void onStop() {
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
        manager.unregisterReceiver(mReceiver);
        super.onStop();
    }
    // ボタンなどのクリックハンドラ
    public void onHogeClick(View v) {
        // Local な Broadcast として Intent を投げる
        // 通常の sendBroadcast(Intent) メソッドと違い、この仕組で投げた Intent は他のアプリ(プロセス)では拾うことが出来ない
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
        manager.sendBroadcast(new Intent(ACTION_HOGEHOGE));
    }
}


□Broadcastのバーミッション

Manifestにパーミッションを設定することで、パーミッションを得ているアプリのみがブロードキャストを受信できるようにする。
この場合、パーミッションをもっていれば、他のアプリでも受信が可能。

●Intentへの付加情報


□Intent Extras

いくつかのプリミティブ型と文字列、コレクション、Parcelebleオブジェクト(シリアライズ)を付加。


ex) Intent送信側

public class MyActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // do something...
        List<String> list = new ArrayList<String>();
        list.add("fuga");
        list.add("piyo");
        Intent intent = new Intent(this, NewActivity.class);
        intent.putExtra("StringExtraKey", "hogehoge");  // 文字列の場合
        intent.putExtra("BooleanExtraKey", true); // boolean の場合
        intent.putExtra("IntegerExtraKey", 10); // int の場合
        intent.putStringArrayListExtra("StringArrayListExtraKey", list); // ArrayList<String> の場合
        startActivity(intent);
    }
}

ex) Intent受信側

public class NewActivity extends Activity {
    @Override
    protected void onCreate(savedInstanceState) {
        super.onCreate(savedInstanceState);
        // do something...
        // この Activity への Intent オブジェクトを取得する
        Intent received = getIntent();
        String stringExtra = received.getStringExtra("StringExtraKey"); // 文字列の Extra を取り出す
        boolean booleanExtra = received.getBooleanExtra("BooleanExtraKey"); // boolean の Extra を取り出す
        int integerExtra = received.getIntExtra("IntegerExtraKey"); // int の Extra を取り出す
        List<String> listExtra = received.getStringArrayListExtra("StringArrayListExtraKey"); // ArrayList<String> の Extra を取り出す
    }
}


●Notification

Anroidのステータスバーにアプリからのお知らせを知らせる仕組み。

Notificationの種類

□ Normal View

・通知のアイコン
・アプリのタイトル
・詳細メッセージ
・情報量
・小さいアイコン
・通知表示した時間

□ Big View (JellyBean以降)

・通知のアイコン
・アプリのタイトル
・詳細メッセージ
・情報量
・小さいアイコン
・通知表示した時間
・詳細表示View:スタイル(Bit Picture Style、Big Text Style、Inbox Style)

●Notificationの仕組み


通知表示用の窓口:NotificationManager

窓口が受け取るもの:Notificationオブジェクト

Notificationオブジェクトの生成:NotificationCompat.Builderを使う。(直接のインスタンス生成は不具合のもと)

Notificationの必要条件:アプリのアイコン、通知のタイトル、詳細メッセージの3つを保持

□PendingIntent

通知では、仕組みをシステム(この場合Androidシステム)にあわせる。PendingIntentを使うと、Intentの送信タイミングの遅延、Intentオブジェクトのハンドリングを委譲することが可能。

ex)

public class MyActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // do something...
        // Intent の準備。明示的 Intent でも、暗黙的 Intent でもどちらでも構わない
        Intent intent = new Intent(this, SubActivity.class);
        // PendingIntent オブジェクトの生成。このオブジェクトを他のアプリに渡すことで、引数に渡した Intent の送信を委ねることができる
        // PendingIntent は、Intent の送信先のコンポーネントの種類によって使い分けること
        PendingIntent activityIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }
}

PendingIntentオブジェクトは、遅延させるIntentオブジェクトのターゲットしているコンポーネントによって、利用するメソッドを変える。

・ActivityがターゲットのPendingIntentを作成する
PendingIntent#getActivity(Context, int, Intent, int)


・ActivityがターゲットのPendingIntentを作成するが、複数(配列でわたす)のActivityを扱う:Honeycomb以降
PendingIntent#getActivity(Context, int, Intent[], int)

・BroadCast用のIntentをもつPendingIntentオブジェクトを作成する
PendingIntent#getBroadcast(Context, int, Intent, int)

・ServiceがターゲットのPendingIntentオブジェクトを作成
PendingIntent#getService(Context, int, Intent, int)

全てのメソッドで第2引数にint型をrequireしている。これは、PendingIntentを一意に認識するためのIndexの役割をもつ。

第4引数のint型は、PendingIntentオブジェクトを受け取る側が、どのように扱うかを支持するもの。
以下の定数から選択する。

・既存の古いPendingIntentをキャンセルして、新しいPendingIntentを作成する場合
PendingIntent.FLAG_CANCEL_CURRENT

・既存のPendingIntentを使う(既存が無い場合はnull)
PendingIntent.FLAG_NO_CREATE

・ただ一度PendingIntentを使用する場合
PendingIntent.FLAG_ONE_SHOT

・既存のPendingIntentのExtrasのみ入れ替える
PendingIntent.FLAG_UPDATE_CURRENT


●NotificationCompat.Builder

Notificationオブジェクトの作成

ex)
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, SubActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        Notification notification = builder
                // 通知の日時
                .setWhen(System.currentTimeMillis())
                // 通知のタイトル
                .setContentTitle("通知だヨ!")
                // 通知の詳細メッセージ
                .setContentText("通知の詳しい内容をここに書きます。")
                // 通知のアイコン
                .setSmallIcon(R.drawable.ic_launcher)
                // 通知を表示した瞬間、通知バーに表示するショートメッセージ
                .setTicker("通知だヨ!")
                // 通知をタップした時に使う PendingIntent
                .setContentIntent(pendingIntent)
                // この通知が未だ表示されていない時だけ、音やバイブレーション、ショートメッセージの表示を行う
                .setOnlyAlertOnce(true)
                // タップしたら消えるようにする
                .setAutoCancel(true)
                .build();
    }
}

●作成したNotificationオブジェクトの通知


ex)
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Notification notification = /* 通知オブジェクトの作成(省略) */
        // 直接インスタンス化せず、Context を経由してインスタンスを取得する
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        // 通知の種類に応じて id を割当てることが出来る。
        // id の異なる通知は違うものとして扱われる。
        manager.notify(0, notification);
    }
}

【参考】https://github.com/mixi-inc/AndroidTraining/wiki/2.04.-%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0%E3%81%A8%E9%80%9A%E7%9F%A5#Intent

0 件のコメント:

コメントを投稿