🦔

android AWS S3からImageUrlを発行してPush通知に画像を乗せるとクラッシュした話

2023/06/14に公開

発生内容

タイトルままなのですが

AWS S3のImageUrlを使ってPush通知に乗せると

UnknownHostExceptionが発生してクラッシュします。

Caused by java.net.UnknownHostException: Unable to resolve host "hogehoge.jp": No address associated with hostname
       at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:156)
       at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
       at java.net.InetAddress.getAllByName(InetAddress.java:1152)
       at com.android.okhttp.Dns$1.lookup(Dns.java:41)
       at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:178)
       at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:144)
       at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:86)
       at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:192)
       at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:144)
       at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:106)
       at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:400)
       at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:333)
       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:483)
       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:429)
       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:560)
       at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
       at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
       at com.google.firebase.perf.network.InstrURLConnectionBase.getInputStream(InstrURLConnectionBase.java:7)
       at com.google.firebase.perf.network.InstrHttpsURLConnection.getInputStream(InstrHttpsURLConnection.java:2)
       at com.google.firebase.perf.network.FirebasePerfUrlConnection.openStream(FirebasePerfUrlConnection.java:26)
       at com.google.firebase.perf.network.FirebasePerfUrlConnection.openStream(FirebasePerfUrlConnection.java:14)
       at jp.arsaga.app.notification.PushNotificationService.getBitmapFromUrl(PushNotificationService.java:20)
       at jp.arsaga.app.notification.PushNotificationService.notificationCompatBuilder(PushNotificationService.java:39)
       at jp.arsaga.app.notification.PushNotificationService.receiveOnBackGround(PushNotificationService.java:312)
       at jp.co.arsaga.extensions.gateway.PushNotification$AbstractService.handleIntent(PushNotification.java:11)
       at com.google.firebase.messaging.EnhancedIntentService.lambda$processIntent$0(EnhancedIntentService.java:1)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
       at com.google.android.gms.common.util.concurrent.zza.run(zza.java:6)
       at java.lang.Thread.run(Thread.java:919)


原因

正直なところよくわからないのですが

恐らくandroid内部で使われているokhttpの例外に入ってクラッシュしてるっぽいです

https://github.com/square/okhttp/issues/6903

この方は内部のokhttpにパッチを当てて解決していますが

正直内部のコードは触りたくないのであまりこの方法はやりたくないです.......

  • そもそもこの方法でいいのかも謎
  • 発生するのもIPv6に限らずIPv4で発生することも確認
  • ただ確実にクラッシュするわけでもなく上手くいく時もある
    • しかしクラッシュはかなりの高頻度で発生するため何かしらの対策はするべき


解決方法

これで解決できているかは正直怪しいのですが

画像セットのタイミングを直接NotificationCompat.Builderに渡すのではなく

先にimageUrlからBitmapを作成し

それをNotificationCompat.Builderに渡すようにしたところ

クラッシュすることは無くなりました(恐らく)


サンプルコード

        val image = getBitmapFromUrl(bundle.getString(PushNotificationKeyType.PinpointNotificationImageUrl.keyName))
        //通知の実施
        notificationManager.notify(
            senderId,
            notificationCompatBuilder(
                this,
                channelId,
                bundle.getString(PushNotificationKeyType.PinpointNotificationTitle.keyName),
                bundle.getString(PushNotificationKeyType.PinpointNotificationBody.keyName),
                pendingIntent,
                image
            ).build()
        )

    private fun notificationCompatBuilder(
        context: Context,
        channelId: String,
        title: String?,
        message: String?,
        pendingIntent: PendingIntent?,
        image: Bitmap?
    ) = NotificationCompat.Builder(context, channelId)
        .setContentTitle(title)
        .setContentText(message)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        .setContentIntent(pendingIntent)
        .setAutoCancel(true)
        .setSmallIcon(R.drawable.hogehoge)
        .setStyle(
            NotificationCompat.BigPictureStyle()
                .bigPicture(image)
        )
	
	 private fun getBitmapFromUrl(url: String?): Bitmap? =
        if (!url.isNullOrBlank()) URL(url).openStream().use {
            try {
                BitmapFactory.decodeStream(it)
            } catch (e: Exception) {
                return@use null
            }
        } else null

元のPush実装はこちらを参照してください

https://zenn.dev/apple_nktn/articles/4d0ee27e0af041


結論

NotificationCompat.Builderの中でimageURlにアクセスしてあれこれするとバグるっぽいです

なので先にBitmap化してから渡そうねということですね(恐らく)


参考記事

https://github.com/square/okhttp/issues/6903

https://github.com/firebase/firebase-android-sdk/issues/3942

https://stackoverflow.com/questions/65701884/getting-issue-while-loading-image-in-push-notification-service-unable-to-resolv

Arsaga Developers Blog

Discussion