しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

Android14の通知について調べる

今更ですがAndroid14で通知周りの変更があったので調べてみました。

まずは普通に通知を送ってみます。

下のように新規にプロジェクトを作成します。

まずは下のようにAndroidManifest.xmlに通知に関する行を追加します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application>
        <!-- 略 -->
    </application>
</manifest>

次は通知の許可を求める処理を追加します。
Android13からこの処理が必須になりました。

class MainActivity : ComponentActivity() {
    private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
        Log.i("MainActivity", "requestPermissionLauncher: $it")
    }

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MyApplicationTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Button({
                        if (ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
                            requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
                        }
                    }) {
                        Text("TEST")
                    }
                }
            }
        }
    }
}

ボタンを押すと下のように通知の許可を求めるアラートが出るようになりました。
アラートのボタンをタップするとregisterForActivityResultの処理が呼ばれるので必要に応じて処理を追加します。

次に通知を送る処理を書いていきます。

通知はブロードキャストを利用して送ります。
指定した時間にブロードキャストインテントを送りそれをアプリで受け取るという流れになります。

ブロードキャストインテントを受け取って通知を出す処理は下の通りです。
通知のアイコンやタイトルなど決めて鳴らしています。

class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val builder = NotificationCompat.Builder(context, "ChannelId")
            .setSmallIcon(androidx.core.R.drawable.notification_bg_normal)
            .setContentTitle("通知タイトル")
            .setContentText("通知の本文")
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setAutoCancel(true)
        NotificationManagerCompat.from(context).apply {
            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
                return
            }
            notify(0, builder.build())
        }
    }
}

次はレシーバーを作成したらそのレシーバーをAndroidManifest.xmlに追加します。
こうする事でブロードキャストインテントを受け取れるようになります。
外部からの呼び出しはないのでreceiverタグのandroid:exportedはfalseにしておきます。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.CalendarAndroid">

        <receiver android:name=".AlarmReceiver"
            android:exported="false" />
    </application>
</manifest>

次は指定の時間にブロードキャストインテントを送る処理を追加します。

送信処理は下の通りです。
NotificationChannelを作る必要があるので、NotificationChannelがなければ作る処理を入れています。

val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

if (notificationManager?.getNotificationChannel("ChannelId") == null) {
    val channel = NotificationChannel("ChannelId", "テスト通知", NotificationManager.IMPORTANCE_DEFAULT).apply {
        description = "テスト通知の説明文です"
    }
    notificationManager?.createNotificationChannel(channel)
}

val notificationIntent = Intent(this, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
alarmManager.set(AlarmManager.RTC_WAKEUP, Date().time + 3000, pendingIntent)

これで通知が届くようになりました。

続けてレシーバーでの通知を作るNotificationCompat.Builderクラスのメソッドについても見ていきます。

setAutoCancelは通知タップした時の挙動に関するメソッドです。
trueをセットすると、通知タップ時に通知が自動で消えるようになります。

setAutoCancel(true)

setContentIntentでは通知タップ時に開くActivityを指定しています。
FLAG_CANCEL_CURRENTなどのフラグを入れる事で既に動いているActivityをどうするかの指定をする事ができます。

setContentIntent(
    PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)
)

ブロードキャストインテントを送る処理についても見ていきます。

AlarmManagerのsetの第1引数ですがAlarmManager.RTC_WAKEUPとAlarmManager.RTCを指定できます。
RTC_WAKEUPを入れるとデバイスがスリープ時にも起こす事ができます。
実際に試した所、AlarmManager.RTCはスリープ解除した時に通知が鳴りAlarmManager.RTC_WEKEUPではスリープ中でも通知が鳴りました。

alarmManager.set(AlarmManager.RTC_WAKEUP, Date().time + 3000, pendingIntent)

それとalarmManager.setメソッドを使っている時はRTC_WAKEUPを使っていても時々スリープ中に通知が鳴らないようです。
確実に鳴らしたい時はsetExactメソッドを使うほうが良さそうです。

alarmManager.setExact(AlarmManager.RTC_WAKEUP, Date().time + 3000, pendingIntent)

setExactメソッドを使う時はSCHEDULE_EXACT_ALARMとUSE_EXACT_ALARMの権限が必要になるのでAndroidManifest.xmlに追加しておきます。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
    <uses-permission android:name="android.permission.USE_EXACT_ALARM" />

    <application>
        <!-- 略 -->
    </application>
</manifest>

最近の権限周りについても少し調べてみました。
Android13からはSCHEDULE_EXACT_ALARM権限が多くの端末で自動付与されなくなりました。
代わりにUSE_EXACT_ALARMという権限を入れる必要があるようです。

ただこちらはカレンダーやアラームなどの一部アプリでしか許可されていないようでした、それ以外で使うためにはユーザーから許可をもらう必要があるみたいです。
ユーザーは設定アプリの「アプリ」の「特別なアプリアクセス」の「アラームとリマインダー」から設定する事ができます。

参考URL

【Androidで通知を出すために】通知の権限回りを整える

AlarmManagerでRTC_WAKEUPを指定しても時々スリープ中に作動しない

正確なアラームのスケジュールはデフォルトで拒否される  |  Android Developers