Edited at

51歳からのプログラミング 備忘 Notificationの備忘

Notificationが良く分からなかったので、自分用にリファレンスメモ。

コードは手打ちなので、参照されるならリンク先のコードを使ってね。

参考サイト


https://developer.android.com/guide/topics/ui/notifiers/notifications?hl=ja



ちょっと前説


android-Level と通知

Level
通知単位

Android7(API25)以前
通知はアプリ単位

Android8(API26)以降
通知はチャネル単位
全ての通知をチェンネルに割当
1つのアプリに複数のチャンネル(通知)設定
チャンネルIDが必要

Level
重要度の設定

Android7(API25)以前
priority

Android8(API26)以降
importance


通知の互換性

サポートライブラリの通知APIを使う

・ NotificationCompatとそのサブクラス

・ NotificationManagerCompat


ForegroundService


https://developer.android.com/guide/components/services.html#Foreground


常駐させるのに必要

・ システムによる強制終了の対象から外れる

・ 長時間バックグラウンド処理するサービスは通知が必要

・ 通知の削除はサービスの停止かフォアグランド状態の解除。

  サービスをフォアグランドで実行

  startForegroound(通知識別ID,notification)

  通知識別IDは0以外

  サービスをフォアグランドから外す

  stopForeground() parametar:bool値 -> 通知を削除するか

  サービス自体の停止

  サービス自身     stopSelf()

  他のコンポーネント  stopService()

  バインドサービス   unbindService()


通知作成 Android4.0(API14)以降


https://developer.android.com/training/notify-user/build-notification.html



初めに

ここでは、Android Support library のAPIsのNotificationCompatを使ってコーディングしますが、対応しているのはandroid4.0(API14)以降で、互換できない機能もあります。

AndroidStudioで作られるプロジェクトのほとんどが、NotificationCompatを使う必要があるので、”com.android.support:suppport-compat:28.0.0"がbuild.gradleに含まれてるか確認が必要。ただし、他のcom.android.supportグループを使っている場合でも、NotificationCompatを使えるAPIsもあるので、その場合にはbuild.gradleにsupport-compat:28.0.0が含まれてなくてもOK。


基本的な通知項目

・ アイコン(小)

・ タイトル

・ テキストコンテンツ

その他の設定は、>https://developer.android.com/guide/topics/ui/notifiers/notifications.html#Templates


通知するコンテンツの設定

NotificationCompat.Builderオブジェクトを使って、通知するコンテンツとチャンネルを設定します。

コンテンツ
使うメソッド
備考

小さいアイコン
serSmallIcon()
ユーザーが管理できるコンテンツ限定

タイトル   
serContentTitle()

本文
setContentText()

優先度    
setPriority()
Android7.1以前は通知の必要度によって決まり、
Android8.0以降は開発者がチェンネルの優先度を設定する

<参考コード>

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)

.setSmallIcon(R.drawable.notification_icon)
.setContentTitle(textTitle)
.setContentText(textContent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);

API26以上では、NotificationCompat.Builederは、チャンネルIDが必要です。

(API25以下はチャンネルではなくアプリ単位で制御)

初期設定では通知テキストは一行、もっと長くしたいのならsetStyle()メソッドを使う(用法は省略)。もっと長い通知や、画像やメディアを知らせたいのなら、>https://developer.android.com/training/notify-user/expanded.html


通知にチャンネルを設定し、重要度も設定

Android8.0以降で通知できるようにする前に、createNotificationChannel(channnel)メソッドを使って、チャンネルを登録してください。引数のchannelは、NotificationChannnel(Channel_ID,name,importance)のインスタンスです。

以下のコードではSDK_INTでAndroidバージョンを確認して、Android8.0未満のレベルをブロックしながらチャンネルを登録します。

<参考>

private void createNotificationChannel(){

// 通知チャンネルを生成します。
// 通知チャンネルはAPI26以降の機能なので
// この処理はAPI26以降のみとなります

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.channel_name);
String description = getString(R.string.channel_description);
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);

// システムに通知チャンネルを登録
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}

Android8.0以降では、通知を行う前に、通知チャンネルを生成しておく必要があるので、アプリの実行直後に、このコードを実行してください。

既に生成した通知チャンネル(上記のコードではchannel)を再利用して、新しくチャンネルを生成(上記コードではnotificationManager.createNotificationChannele(channel))して通知しようとしても機能しません。ですから通知のたびに上記コードを実行した方が安全です。

NotificationChannelのコンストラクタでは、重要度を指定する必要があるので、NotificationManagerクラスの定数で指定してください。

重要度の定数
備考

IMPORTANCE_HIGH
音あり、ヘッドアップあり

IMPORTANCE_DEFAULT
音あり

IMPORTANCE_LOW
音なし

IMPORTANCE_MIN
音なし、ステータスバー表示なし

通知の重要度によって、表示、音を決定します。重要度はユーザーがシステム設定で変更できます。Android7.1(API25)以前では、Notificationのpriorityで重要度を決めます。

開発者は重要度(API26~;importance/~API25:priority)を設定しなくてはなりませんが、システムでは色々な要因で重要度を変更したり、ユーザーが重要度を変更することがありますので、開発者の期待通りにならないこともあります。

重要度に関してより詳しくは>https://developer.android.com/training/notify-user/channels.html#importance


通知のタップ動作

通常は、通知をタップすると、通知に応じたアクティビティを開きます。

そのために、開発者はPendingIntentオブジェクト(アクティビティが終了しても存在し続けるintent的な)で定義するcontent intentを指定し、setContentIntent()を実行してください。以下は汎用コードです。

<参考>

// 汎用的コード:アクティビティを開くIntent

Intent intent = new Intent(this, AlertDetails.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
// 通知タップで発火するIntentを設定
.setContentIntent(pendingIntent)
.setAutoCancel(true);

以下、コードの補足


setAutoCancel()

タップすると自動的に通知を削除する


setFlag()

通知から開いたアプリの履歴を保持。Flagを使うかどうかは、開始するアクティビティの種類によります。通常のアプリの利用中は、ユーザーはこのアクティビティに移動する必要がないので、アクティビティは、既存のタスクとバックスタックを追加する代わりに、新しいタスクを開始します。

上記のコード(intent.setFlag())では、

Intent.FLAG_ACTIVITY_NEW_TASK   常に新しいタスクで起動

Intent.FLAG_ACTIVITY_CLEAR_TASK  タスクをクリアして起動

として、新しいタスクを起動してます。

アクティビティがアプリの正規の流れにある場合(?正常に稼働?)

アクティビティの開始でバックスタックを生成し、戻る、上げる、ボタン機能を保持させます(?)。

もっと違うnotification intentを作成したければ>https://developer.android.com/training/notify-user/navigation.html


通知の表示

通知の実行は NotificationManagerCompat.notify(notificationId,builder.build())

notificationIDは重複しないID(整数)

builder.build()は、NoticicatonCompat.Builder.build()メソッドの戻り値

<参考>

NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

// notificationIDはユニーク(一意)の整数です。一つ一つの通知に対して開発者が採番します
notificationManager.notify(notificationId, builder.build());

notify()で設定したnotificationIDは、後々アップデートや通知を削除するときに使うので、記録しておくようにして下さい。



Android 8.1(API レベル 27)以降では、アプリは 1 秒間に通知音を複数回鳴らすことはできません。アプリが 1 秒以内に複数の通知を送信した場合、通知はすべて想定どおりに表示されますが、音が鳴るのは最初の通知だけです



ただし、Android は通知の更新時にレート制限も適用します。1 つの通知の更新をあまりにも頻繁に(1 秒未満の間に何度も)送信すると、システムによって一部の更新が破棄されることがあります。


アクションボタンの追加

リマインダーのスムーズ、テキストメッセージ通知など、ユーザーが素早く応答できるように、3つのアクションボタンを提供してます。ユーザーが通知をタップした時は、ボタンアクションを行わないようにしてください。

アクションボタンを生成するには、addAction(icon,string,PendingIntent)

アクティビティを起動する代わりに色々できます。例えば、既に開いているアプリに割り込みさせないで、バックグランドでのブロードキャストレシーブを開始するとか。

<参考>

特定のレシーバーにブロードキャストを送るコード

Intent snoozeIntent = new Intent(this, MyBroadcastReceiver.class);

snoozeIntent.setAction(ACTION_SNOOZE);
snoozeIntent.putExtra(EXTRA_NOTIFICATION_ID, 0);
PendingIntent snoozePendingIntent =
PendingIntent.getBroadcast(this, 0, snoozeIntent, 0);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_snooze, getString(R.string.snooze),
snoozePendingIntent);

BroadvastReceiverのバックグラウンド処理の詳しい使い方は>https://developer.android.com/guide/components/broadcasts.html

ポーズボタンやスキップトラックボタン(次の曲)などから通知を作るには>https://developer.android.com/training/notify-user/expanded.html#media-style


ダイレクトリプライアクション

アクティビティを開かなくても直接通知にテキストを入力できる機能

例えば、通知に対してユーザーに直接メッセージに応答させたり、タスクリストをアップデートしたりできます。

ダイレクトリプライアクションは、テキスト入力欄を通知に加えることで実装します。

システムは、ユーザーの応答メッセージを、通知に対応するアクションのIntentを通じて、アプリケーションに渡します。


リプライボタンの生成

ダイレクトリプライ機能を持った通知の作成

  1.RemoteInput.Builderインスタンスを生成

  2.PendingIntentを生成

  3.addRemoteInput()を使ってRemoteInputオブジェクトをアクションに関連付け

  4.アクションを通知に関連付け、通知を実行

1.RemoteInput.Builderインスタンス生成

  RemoteInput.Builderインスタンスを通知に加えます。

  このクラスのコンストラクタは、システムが使う入力テキストのキー値の文字列を実引数として受け取ります。開発者はキー値から入力テキストを取り出して使います。

// アクションのIntentに渡すキー値(文字列)

private static final String KEY_TEXT_REPLY = "key_text_reply";

String replyLabel = getResources().getString(R.string.reply_label);
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel(replyLabel)
.build();

2.PendingIntent生成

PendeingIntent replyPendingIntent = 

PendingIntent.getBroadcast(
getApplicationContext(),
conversation.getConversationId(),
getMessageReplyIntent(conversation.getConversationId()),
PendingIntent.FLAG_UPDATE_CURRENT
);

* 注意
PendingIntentを再利用する場合、ユーザーは再利用前とは異なる会話をリプライしようとするでしょう。開発者は会話毎に一意(ユニーク)なリクエストコードを発行するか、他の会話のリプライインテントでequals()メソッドを使ったときにtrueを返さないインテントを発行するよにして、他のリプライと明確に区別するよにしてください。

3.addRemoteIntent():RemoteInputオブジェクトとアクションを関連付け

// リプライアクションの作成とリモートインプットの追加

// new NotificationCompat.Action.Builder().addRemoteInput().build()

NotificationCompat.Action action =
new NotificationCompat.Action.Builder(
R.drawable.ic_reply_icon,
getString(R.string.label),
replyPendingIntent)
.addRemoteInput(remoteInput).build();

4.アクションを通知に関連付けて通知実行

// 通知を作成しアクションを追加

Notification newMessageNotification = new Notification.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_message)
.setContentTitle(getString(R.string.title))
.setContentText(getString(R.string.content))
.addAction(action)
.build();

// 通知発行
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, newMessageNotification);

図3(省略してます)に示したように、通知アクションを契機にユーザーに応答を入力させるようにさせます。


リプライからユーザの入力内容を取得する

通知リプライのUIから、ユーザーの入力を受けとるために、

RemoteInput.getResultsFromIntent()を呼びだして、

BroadcastReceiverで受け取ったIntentを通じて取得します。

private CharSequence getMessageText(Intent intent) {

Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
return remoteInput.getCharSequence(KEY_TEXT_REPLY);
}
return null;
}

ユーザー入力テキストの処理後、その通知のIDと同じID(もしタグを使っていたら同じタグ)を使って、NotificationManagerCompat.notify()を呼び出して通知をアップデートしてください。この処理は、ユーザのリプライの受け渡しや処理が正確に行われたかを確認したり、リプライUIを隠すために必要な処理です。

// システムによって、前の通知のやり取りが処理されたことを知らせるための、新しい通知を生成

Notification repliedNotification = new Notification.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_message)
.setContentText(getString(R.string.replied))
.build();

// 通知を発行する
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, repliedNotification);

この新しい通知が機能してる時に、受信したonReceive()メソッドに渡されるコンテキストを使います。

開発者はsetRemoteInputHistory()を使って、リプライを通知ボタンに加えます。もし開発者がメッセージアプリを作成してる場合は、開発者は"messageing-style notificaton"を生成し、ユーザーとのやり取りに新しいメッセージを加えてください。

メッセージアプリ関連の通知についてより詳しくは>https://developer.android.com/training/notify-user/build-notification.html#messaging-best-practices


プログレスバーの追加

ユーザーに処理の進捗を知らせるアニメーション表示(プログレスバー)を通知に追加できます。

処理の進捗が測れる場合に、setProgress(max,progress,false)を使って、インディケーターの"determinaite"フォームを使ってプログレスを表示します。

setProgress()の最初のパラメータは100%表示などの完了を表す指標、2つ目のパラメータは現在どの程度の進捗度なのかを表す指標、最後は、プログレスに"determinate"なプログレスバーを指定します(?)。

処理が進むにつれ、進捗の値の更新でsetProgress(max,progress,false)は絶え間なく呼び出され、通知も発行され続けます。

NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
builder.setContentTitle("Picture Download")
.setContentText("Download in progress")
.setSmallIcon(R.drawable.ic_notification)
.setPriority(NotificationCompat.PRIORITY_LOW);

// 進捗0の最初の通知を発行
int PROGRESS_MAX = 100;
int PROGRESS_CURRENT = 0;
builder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false);
notificationManager.notify(notificationId, builder.build());

// 進捗を追跡します
// 通常、この追跡作業は、変数PROGRESS_CURRENTや通知を更新しながら、
// 進捗を示すためのworkerスレッドで行う必要があります。
// 使うメソッドは、
// builder.setProgress(PROGRESSS_MAX,PROGRESS_CURRENT,fale);
// notificationManager.notify(notifcationId,builder.build());
// 終了時は、通知を更新し、プログレスバーを削除します

builder.setContentText("Download complete")
.setProgress(0,0,false);
notificationManager.notify(notificationId, builder.build());

処理終了時に、プログレスは最大値になっている必要があります。開発者は処理終了時にプログレスバーを表示させないようにするか、削除できます。この場合には、処理が完了したことを知らせる通知文に更新してください。プログレスバーの削除はsetProgress(0,0,false)を使います。

Note:
プログレスバーは、絶えず通知を更新させるので、通常はバックグラウンドサービスでコードを実行する必要があります。

"indeterminate"プログレスバー(進捗度をパーセンテージで表示しないバー)を表示するためには、setProgress(0,0,true)を使います。上記したプログレスバーと同じタイプの表示です(進捗度を示さないアニメーションのプログレスバーを除く)。このプログレスのアニメーションは、setProgress(0,0,fales)を呼ぶまで、また表示を削除するまで続きます。処理が完了したことを示すために、忘れずに通知テキストを変更してください。

Note:
実際にファイルをダウンロードするためには、DownloadManagerを検討する必要があります。これは自身のダウンロードの進捗情報を提供するためのクラスです。

set asystem-wide category

省略

set lock screen bisibility

省略


通知の削除

通知は以下のいずれかが発生するまで表示され続けます。

・ ユーザーが通知を解放する

・ 開発者が通知を生成した際に、setAutoCancel()を呼び出していた状態で、ユーザーが通知をクリックした

・ 開発者が特定の通知IDに対してcancel()メソッドを呼ぶ

  このメソッドはまた、継続中の通知を削除します

・ 開発者がcancelAll()を呼び出す

  このメソッドは以前に発行されたすべての通知を削除します

・ 開発者がsetTimeoutAfter()を使って通知を生成した時に、タイムアウトを設定したら、システムは特定の時間が経過した後に通知をキャンセルします。もし、必要に応じて特定のタイムアウト経過前に通知がキャンセルできます。


メッセージアプリの最適実行

メッセージアプリやチャットアプリの通知を生成するのに、覚えておいてほしいクイックリファレンスに関するベストプラクティスの一覧を使ってください。


Use MessageingStyle

Android7.0(API24)から、メッセージコンテンツのための特別な通知スタイルのテンプレートを提供してます。NotificationCompat.MessagingStyleクラスを使えば、タイトル、追加メッセージ、見栄えなど、色々なレベルの通知を使えるようになります。

次の汎用的なコードはMessageingStyleクラスを使って通知をカスタマイズする方法を紹介します。

Notification notification = new Notification.Builder(this, CHANNEL_ID)

.setStyle(new NotificationCompat.MessagingStyle("Me")
.setConversationTitle("Team lunch")
.addMessage("Hi", timestamp1, null) // Pass in null for user.
.addMessage("What's up?", timestamp2, "Coworker")
.addMessage("Not much", timestamp3, null)
.addMessage("How about lunch?", timestamp4, "Coworker"))
.build();

Android8.0(API26)以降、NotificationCompat.MessagingStyleクラスを使いことで、より多くの折畳みコンテンツフォームを使えるようになりました。また、メッセージ履歴をメッセージ通知に追加することで、会話用のコンテキストを提供するaddHistoricMessage()メソッドを使いこともできるようになりました。

NotificationCompat.MessagingStyleを使う時は・・・

・ 二人以上のチャットグループのタイトルをセットするために、MessageingStyle.setConbersationTitle()を呼ぶ。

  グッドな会話タイトルとは、グループチャットのグループ名とか、特にそのような名前がない時には、会話の参加者リストなどでしょう。会話タイトルがないと、その会話グループは、最も最新のコメント送信者と一対一の会話グループと勘違いされるでしょう。

・ イメージのようなメディアメッセージを含むMessagingStyle.setData()メソッドを使う。image/*パターンのMIMEタイプがサポートされてます。


Use direct reply

Direct Repleでは、ユーザーがインライン返信できます

・ ユーザーがインライン返信した後、MessagingStyle.addMessage()を使って、メッセージスタイルの通知を更新します。通知をキャンセルしたりしません。通知キャンセルを許すと、通知からの多重リプライができるようになってしまいます。

・ Action.WearableExtender.setHintDisplayInlineAction(true)を呼ぶことで、WearOSと矛盾なくインラインリプライを作れます。

・ addHistoricMessage()を使って、通知にメッセージ履歴を追加することで、直接会話リプライにコンテキストを提供します。


Enable smart reply

・ リプライアクションで、setallowGeneratedResponses(true)を呼べば、スマートなリプライを可能にします。このケースでは、通知がWearOSディバイスに渡されることで、スマートリプライが利用可能となります。

スマートリプライの応答は、NotificationCompat.MessageingStyle通知によって提供されるコンテキストを使った"entry on-watch 機械学習モデル"によって生成されます。応答を生成するためのデータが、インターネットを通じてアップロードされることはありません。


Add notification metadata

・ ディバイスがサイレントモードの時に、システムにアプリ通知の処理を行わせるように通知メタデータを割り当てます。