woshidan's blog

あいとゆうきとITと、とっておきの話。

mixi Android training 第四回分についてメモ1

なんていうか、これを見てひよっているうちに実習日記みたいなのしか書けなくなっていたのであえて書きましょうぞ。

AndroidStudioでgitを使う感覚に慣れたほうがよさそうです。

http://mixi-inc.github.io/AndroidTraining/fundamentals/2.04.messaging-and-notification.html#Intent%20Extras

長くなりすぎたので、課題は次エントリ

実習

AndroidStudio/practice/fundamentals/4th/MessagingAndNotification/build.gradleを開き、 以下の問題に取り組んでください。

Intent

課題1

画面に複数のボタンが配置されています。各ボタンのクリックイベントを拾う処理の中で、コメントに記述された Activity を呼び出すコードを書いてください。

Github上のmasterのコードを拾ってみて。

package jp.mixi.practice.messagingandnotification;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

/**
 * Created by suino on 2015/02/25.
 */
public class IntentActivity1 extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_1);

        View button1 = findViewById(R.id.CallActivity1);
        View button2 = findViewById(R.id.CallActivity2);
        View button3 = findViewById(R.id.CallActivity3);

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO ここに、NewActivity1 を呼び出す処理を書く

            }
        });
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO ここに、NewActivity2 を呼び出す処理を書く
                // TODO NewActivity2 は、toast_message をキーとした Extra のデータを必要としているので、適宜 Intent に含めること

            }
        });
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO ここに、NewActivity3 を呼び出す処理を書く
                // TODO Intent に、Intent.FLAG_ACTIVITY_NO_HISTORY という flag をセットするとどうなるかレポートすること

            }
        });
    }

}

こう、更新が止まっていたのは、このプロジェクト内にNewActivity1, NewActivity2, NewActivity3が見つからなかったので、些細なことですが、ひよっこなのでやる気が無くなってました。。

(愚痴愚痴言うならPRを出せ、みたいなことは思ってgit pushは押したのですが、403だったのでごめんなさい。。)

でも、講義の内容がIntentの受け取り側と送信側の両方が入っていたからまあやったほうがいいでしょう、ということで。

各Activityに対するコメントを整理してみます。

  • NewActivity1
    • TODO ここに、NewActivity1 を呼び出す処理を書く
  • NewActivity2
    • TODO ここに、NewActivity2 を呼び出す処理を書く
    • TODO NewActivity2 は、toast_message をキーとした Extra のデータを必要としているので、適宜 Intent に含めること
  • NewActivity3
    • TODO ここに、NewActivity3 を呼び出す処理を書く
    • TODO Intent に、Intent.FLAG_ACTIVITY_NO_HISTORY という flag をセットするとどうなるかレポートすること

ひとまず、NewActivity1はとりあえず呼び出されればいいことになります。

新規Activityの作成をすればいいので、New > Activity > Blank Activityを選択して作成します。 (メニューからやらないと、xmlなどを手動で作る必要があるので面倒くさいです)

それで、単純に次のActivityのクラスを指定してIntentをstartしてやればいいと思うのですが、 ViewのリスナからActivityを参照するのってどうしたらいいか、少し戸惑って解答を見たら

// ViewからViewが置かれているIntentActivity1を参照する
IntentActivity1.this

とあったので、文法的に少しよく分からないけれどほっとする。 Intentのコンストラクタの1つ目の引数は呼び出し元のActivityのインスタンスなのですが、2つ目の引数は次に呼び出すActivityのクラスなので注意して書いて1つめは終了です。

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // TODO ここに、NewActivity1 を呼び出す処理を書く
        Intent intent = new Intent(IntentActivity1.this, NewActivity.class);
        startActivity(intent);
    }
});

2つめ。

  • NewActivity2
    • TODO ここに、NewActivity2 を呼び出す処理を書く
    • TODO NewActivity2 は、toast_message をキーとした Extra のデータを必要としているので、適宜 Intent に含めること

ということで、まず、button2のクリックイベントのレシーバの中では、toast_messageという文字列をキーとした、Intentを送信すれば良いことがまず分かります。

button2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // TODO ここに、NewActivity2 を呼び出す処理を書く
        Intent intent = new Intent(IntentActivity1.this, NewActivity2.class);
        intent.putExtra("toast_message", "button2が押されたよ");
        startActivity(intent);
        // TODO NewActivity2 は、toast_message をキーとした Extra のデータを必要としているので、適宜 Intent に含めること

    }
});

課題の2つめ自体はこれで終了なのですが、せっかくだからNewActivityの方もきちんと書きます。 NewActivity2のほうですが、受け取り側の関数で、toastを使って文字を表示させたいですよね。

Intentを受け取って起動された時、onCreate()getIntent()を利用すると、送られたIntentの内容を取得する事が出来ます。 IntentからExtras(付加情報)を取得する時のメソッドは受け取る変数の型にあわせる必要があることに注意して、

// NewActivity2内
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_new_activity2);
    Intent intent = getIntent();
    String toast_message = intent.getStringExtra("toast_message");
    Toast toast = Toast.makeText(this, toast_message, 50);
    toast.show();
}

課題の3つ目はというと、

  • NewActivity3
    • TODO ここに、NewActivity3 を呼び出す処理を書く
    • TODO Intent に、Intent.FLAG_ACTIVITY_NO_HISTORY という flag をセットするとどうなるかレポートすること

なんていうか、Intentのフラグを管理する処理をNewActivity3側で書かないといけないんかい(笑)! みたいな、気持ちなのですが、とりあえず、フラグのセットの仕方から。

参考: http://blog.goo.ne.jp/retis07/e/629b3c62509323ca12e946aa2e33a7bc

intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

という風になります。これは、もしかして、こちらでフラグを設定するだけで、フラグを元に Androidフレームワーク側が判断する奴かな。

どういう処理が想定されているのか、さすがに分からないので、NewActivity3の解答を見てきた。

// NewActivity3.java
package jp.mixi.practice.messagingandnotification;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

/**
 * Created by suino on 2015/02/26.
 */
public class NewActivity3 extends Activity {

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

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

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // この先のActivityから戻るときにNewActivity3がスキップされることを確認するため、NewActivity2に遷移する
                Intent intent = new Intent(NewActivity3.this, NewActivity2.class);
                intent.putExtra(NewActivity2.EXTRA_TOAST_MESSAGE_KEY, "NewActivity3から来たよ!");
                startActivity(intent);
            }
        });
    }
}

なるほどー。

|---------------|
|IntentActivity1|
|---------------|
   |       A
   V       |
|---------------|
| NewActivity3  |
|---------------|
   |       A
   V       |
|---------------|
| NewActivity2  |
|---------------|

という動きを想定しているわけだが、Intent.FLAG_ACTIVITY_NO_HISTORYをIntentにセットする事で、

|---------------|
|IntentActivity1|<__
|---------------|   |
   |                |
   V                |
|---------------|   |
| NewActivity3  |   |
|---------------|   |
   |       _________|
   V       |
|---------------|
| NewActivity2  |
|---------------|

という動きになることを想定しているっぽい。 実際試してみて、そうだった!

Intentにフラグをセットする事で遷移の様子をちょっと変えられるみたいです。

課題2

画面に複数のボタンが配置されています。各ボタンのクリックイベントを拾う処理の中で、コメントに記述された Action を実行させるコードを書き、そのIntentオブジェクトを受け取るためのBroadcastReceiverを作成し、AndroidManifest に記述してください。

// TODO それぞれ、Broadcast を受け取ったら Log.v(String, String) を利用して、ログ出力にどの Action を受信したかを表示する処理を書くこと
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // TODO ここに、ACTION_FIRST を呼び出す処理を書く

    }
});
button2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // TODO ここに、ACTION_SECOND を呼び出す処理を書く

    }
});
button3.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // TODO ここに、ACTION_THIRD を呼び出す処理を書く

    }
});

ACTION_FIRSTあたりは、String型の変数で、これを独自のアクションの識別子としてIntentにセットして Broadcastを送信すればいいわけです。

ところで、独自アクションを受け取るためのBroadcastReceiverってどう書けば良かったのだろう。

とりあえず普通に書いちゃえ。

<receiver
    android:name=".MyReceiver"
    android:enabled="true"
    android:exported="true" >
    <intent-filter>
        <!-- BroadcastReceiverには必要かは分からないが、Activityで暗黙的Intentを受信する際必要 -->
        <category android:name="android.intent.category.DEFAULT" />

        <action android:name="jp.mixi.practice.messagingandnotification.intent.action.FIRST"/>
        <action android:name="jp.mixi.practice.messagingandnotification.intent.action.SECOND"/>
        <action android:name="jp.mixi.practice.messagingandnotification.intent.action.THIRD"/>
    </intent-filter>
</receiver>

IntentをBroadcastする処理を書きます。Broadcastの場合は、送信元のActivityの情報が要らないみたいです。

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // TODO ここに、ACTION_FIRST を呼び出す処理を書く
        Intent intent = new Intent();
        intent.setAction(ACTION_FIRST);
        sendBroadcast(intent);

    }
});

これを三つ書いてから、BroadcastRecevierを書きます。 そういえば、課題とは全然関係ないのですが、AndroidStudioだとTODO とコメントの最初に書くと その行をハイライトしてくれるんですね。

public class MyReceiver extends BroadcastReceiver {
    public MyReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO それぞれ、Broadcast を受け取ったら Log.v(String, String) を利用して、
        // TODO ログ出力にどの Action を受信したかを表示する処理を書くこと
        Log.v("[context]", context.toString()); // 気になったのでContextも出力してみた
        Log.v("[Intent]", intent.toString());
        Log.v("[Intent Action]", intent.getAction());
    }
}

Broadcastしてみた結果。

// ACTION_FIRST
04-05 19:04:48.253    2419-2419/jp.mixi.practice.messagingandnotification V/[context]﹕ android.app.ReceiverRestrictedContext@28163794
04-05 19:04:48.253    2419-2419/jp.mixi.practice.messagingandnotification V/[Intent]﹕ Intent { act=jp.mixi.practice.messagingandnotification.intent.action.FIRST flg=0x10 cmp=jp.mixi.practice.messagingandnotification/.MyReceiver }
04-05 19:04:48.253    2419-2419/jp.mixi.practice.messagingandnotification V/[Intent Action]﹕ jp.mixi.practice.messagingandnotification.intent.action.FIRST
// ACTION_SECOND
04-05 19:05:39.219    2419-2419/jp.mixi.practice.messagingandnotification V/[context]﹕ android.app.ReceiverRestrictedContext@28163794
04-05 19:05:39.219    2419-2419/jp.mixi.practice.messagingandnotification V/[Intent]﹕ Intent { act=jp.mixi.practice.messagingandnotification.intent.action.SECOND flg=0x10 cmp=jp.mixi.practice.messagingandnotification/.MyReceiver }
04-05 19:05:39.219    2419-2419/jp.mixi.practice.messagingandnotification V/[Intent Action]﹕ jp.mixi.practice.messagingandnotification.intent.action.SECOND
// ACTION_THIRD
04-05 19:05:48.785    2419-2419/jp.mixi.practice.messagingandnotification V/[context]﹕ android.app.ReceiverRestrictedContext@28163794
04-05 19:05:48.785    2419-2419/jp.mixi.practice.messagingandnotification V/[Intent]﹕ Intent { act=jp.mixi.practice.messagingandnotification.intent.action.THIRD flg=0x10 cmp=jp.mixi.practice.messagingandnotification/.MyReceiver }
04-05 19:05:48.785    2419-2419/jp.mixi.practice.messagingandnotification V/[Intent Action]﹕ jp.mixi.practice.messagingandnotification.intent.action.THIRD

Contextは3つとも共通で、前の画面から呼ばれたという事なのでしょうか。。

Notification

その1

アイコン、タイトル、詳細メッセージを含む通知を表示してください。

// NotificationActivity.java

public class NotificationActivity extends Activity {

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

        // TODO ここで通知を表示する
    }
}

これは、とりあえず、その。Notificationを作成するクラスを書いたらいいのかな、ということにします。 Notificationを送信するには、Intentを作成したあと、それをPendingIntent.getActivityなどを使って 通知がタップされるまでIntentが実行されないように、PendingIntentにさらに包みます。

そのあと、そのPendingIntentを持った、Notificationを作成すればよさそうです。

たぶん、次の課題で新しいActivityを立ち上げるあたりの指定をやるから、とりあえず書いてみます。

public class NotificationActivity extends Activity {

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

        // TODO ここで通知を表示する
        Intent intent = new Intent(this, MainActivity.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("通知ダヨ!")
                .setSmallIcon(R.drawable.ic_launcher)
                .setContentText("通知の詳しい内容をここに書きます")
                .setContentIntent(pendingIntent) // 通知をタップしたときに使うPendingIntent
                .setAutoCancel(true) // タップしたら消えるようにします
                .setOnlyAlertOnce(true)
                .build();
    }
}

該当アクティビティを立ち上げてもNo Notificationsだったので、焦っているところです。

というのは、単なるど忘れでして、Intentを作成してPendingIntentで包んだあと、PendingIntentを持ったNotificationを組み立てたら、さらにそのNotificationをNotificationManagerを使って送信する必要があるのでした。

/* Notification の作成後 */
// 直接インスタンス化せず、Context を経由してインスタンスを取得する
  NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 通知の種類に応じて id を割り当てることが出来る。
// id の異なる通知は違うものとして扱われる。
  manager.notify(0, notification);

よっしゃよっしゃ。

その2

アイコン、タイトル、詳細メッセージを含む通知を表示し、通知をタップしたら MainActivity ではない新しい Activity を立ち上げるようにしてください(プロジェクトは実習 1 と同じもので良い)。

これは、最初のIntentかPendingIntentのフラグだと思う。とりあえず最初のIntentの部分で、表示するActivityをいじりました。

Intent intent = new Intent(this, MainActivity.class);
|
v
Intent intent = new Intent(this, NewActivity1.class);