LoginSignup
1
0

More than 1 year has passed since last update.

[Android]アプリアイコンの通知数付バッジを通知を使わず表示する

Posted at

前書き、前提

・そもそもOSの仕様としてAndroidはアプリアイコンのバッジは通知のみで表示されるものだけで、iOSのようにAPIを利用して表示することはできません。裏技的なものになります。

・上記の事柄から全てのメーカー、端末、OSバージョンでこの内容を保証するものではありません。

・OSのアップデートによって実装が壊れる場合があります。

・綿密な調査をしてるわけではないので記事の内容に憶測が含まれます。
またなんでそうなったのか説明できない部分も多いです。

・Androidはホームアプリ(またはランチャーアプリとも今回の記事ではホームアプリと称します)に通知バッジの処理を依存しているためホームアプリ次第では機能しない可能性が高いです。
PixelLauncherとかはバッジに数字をつけることすらできません。
無理なものは無理ですので諦めてください。

・今回の対象はMicrosoft Launcherです。
一番うまくいったのがこれというだけで探せばあるかもしれません。

今回遭遇した解決したいケース

ログイン時にサーバで保持しているメッセージ未読件数をログインAPIレスポンスに添えて返却し、その数を通知バッジとして利用したい。

条件など

ホームアプリ:Microsoft Launcher
ライブラリ:ShortcutBadger (ただし役に立たない場合もある)

端末:Pixel4a(OS11) , Pixel5a(OS13)

記事の本題

問題点①

このライブラリではPixelは記載がありません。
実際に導入して

setBadge.kt
private fun setBadgeCount(count: Int){
        try {
            ShortcutBadger.applyCountOrThrow(applicationContext, count)
            Log.d("ShortcutBadger", "is support device")
        } catch (ex: ShortcutBadgeException) {
            Log.d("ShortcutBadger", "not support device")
        }
    }

だけ記載するとShortcutBadgeExceptionがThrowされます。

ただ、何故か1回目だけはバッジが表示される模様ですが、それ以降更新とかできないので件数や消す術がないので実用に堪えませんでした。
アンインストールしても数がおかしいままなのでMicrosoftLauncher自体に数が保存されてしまっているのだと推測してます。

①の解決策

以下のようにBroadcastClassを作成してレシーバーに登録しておくと成功します。(何故?)

まず、マニフェストにレシーバーを記載します。

AndroidManifest.xml

<receiver android:name=".BadgeBroadCastReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BADGE_COUNT_UPDATE"/>
            </intent-filter>
</receiver>

次にBroadcastReceiverクラスを作成
(何もしない予定なら中身は空でOK)

BadgeBroadCastReceiver.kt
class BadgeBroadCastReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent) {
        Log.d("Badge", "onReceive")
    }
}

次にregisterします。(Applicationクラスとかで良いかと)

MyApplication.kt
var badgeBroadCastReceiver:BadgeBroadCastReceiver? = null
    private fun registerBadgeBroadCastReceiver(){
        badgeBroadCastReceiver = BadgeBroadCastReceiver()
        val filter = IntentFilter("android.intent.action.BADGE_COUNT_UPDATE")
        badgeBroadCastReceiver?.let {
            registerReceiver(it, filter)
        }
    }

以上で改めてsetBadgeCountをコールすると、ShortcutBadger.applyCountOrThrowがThrowしなくなります。
Badgeの件数も同じメソッドで更新できるようになりました。
0をセットすると消せます。(これはどのホームアプリでも一緒のはず)
これで一旦実装完了です。

問題点②

課題はクリアできたように見えましたが、
サーバに件数があるということはPush通知(FCM)のペイロードでもそれが送れるということ...。
要件として同時にPush通知に含まれるペイロードでも通知件数を変更したいということです。

これはNotificationCompat.BuilderのsetNumberがあるのでそのまま入れるだけと思いきや
上記を行うと「通知バッジの数が更新されません」でした...。
(行わない場合はsetNumberは正常に機能します)

どうやらこのBroadcastIntentの方法は通知バッジとは競合するのか通知バッジの仕組みを阻害する様子です。
(ただこの挙動はホームアプリに依存しているようで、
そもそもBroadCastが意味のないホームアプリでは関係ありませんでした)

この解決法は見つからず、結局二者択一のままだったので
onMessageReceivedのところにもsetBadgeCountを使って更新することにしました。
setNumberと両方併記で一応は問題ないはずです。(どちらか無視されるだけなので)

ちなみsetBadgeCountを使わず通知すると件数がおかしくなります。(一番最初に入れた件数に戻ってたり、更新されなかったり)

ライブラリに頼らないプレーンなコード

ライブラリが導入できない場合は以下のコードが使えるかもしれません。
broadcastReceiverなどはそのままです。

SetBadgeBadge.kt
private fun setBadge(context: Context, count: Int) {
        val launcherClassName: String = getLauncherClassName(context) ?: return
        val intent = Intent("android.intent.action.BADGE_COUNT_UPDATE")
        intent.putExtra("badge_count", count)
        intent.putExtra("badge_count_package_name", context.packageName)
        intent.putExtra("badge_count_class_name", launcherClassName)
        context.sendBroadcast(intent)
}


private fun getLauncherClassName(context: Context): String? {
        val intent = Intent(Intent.ACTION_MAIN)
        intent.addCategory(Intent.CATEGORY_LAUNCHER)
        val resolveInfoList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            context.packageManager.queryIntentActivities(
                intent,
                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
            )
        } else {
            context.packageManager.queryIntentActivities(
                intent,
                0)
        }

        for (resolveInfo in resolveInfoList) {
            if (resolveInfo == null) {
                continue
            }
            val pkgName = resolveInfo.activityInfo.applicationInfo.packageName
            if (pkgName.equals(context.packageName, ignoreCase = true)) {
                return resolveInfo.activityInfo.name
            }
        }

        return null
}

※Pixe5a(OS13)ではうまくいきませんでした。(何故...)

メーカー独自製などのホームアプリ(Sony:Xperiaとか)はIntentActionが違うため機能しないぽいです
ライブラリはこのActionを吸収しているだけぽい。
(結局そんなものは事実上無限にあるので、このライブラリは捨てられてしまったわけですが...)
つまりホームアプリのパッケージ名を調べれば狙い撃ちはできそうですが、普通そこまでやらないだろうし調べませんでした。

その他

数字の表現はホームアプリに依存です。
100件超えたら99+になったり、1000件以上は999で止まったり
アプリからはカスタマイズはできないので諦めましょう

感想

通知を利用したバッジ件数表示についてはDeveloperサイトに
setNumberの項目に
this may be displayed as a badge count for Launchers that support badging.
(この値は他のランチャーアプリによってはバッジにも表示されるかもね)

と書いてあるからまぁ理解はできるんですが、(ただ、この書き方だとAndroid API側で対応する気は0ぽいですね...。)
BroardCastで表示する方法は非公式なので基本的に要件としてあがったら拒否したほうが無難です。
setNumberだけやるというなら理解できるけど...
iOSができるからでそのまま進めると大変なことになります。
やる場合は、端末やOS差異から挙動の保証はできないと予め伝えた方がいいでしょう。

もしまた実装の機会があったときの備忘録として残します。(次がないほうがいいですが)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0