블루투스 서비스에서 PendingIntent 사용 시 발생하는 문제 및 해결 방법
최근에 Flutter 앱을 디버깅 모드로 빌드하는 과정에서 다음과 같은 에러를 경험했음:
E/AndroidRuntime(11350): FATAL EXCEPTION: main
E/AndroidRuntime(11350): Process: com.sxr.sdk.ble.keepfit.client, PID: 11350
E/AndroidRuntime(11350): java.lang.RuntimeException: Unable to create service com.apposter.smart_device.BleService: java.lang.IllegalArgumentException: com.sxr.sdk.ble.keepfit.client: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
E/AndroidRuntime(11350): Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
이 에러는 Android 12(S) 이상을 타겟으로 하는 앱에서 PendingIntent
를 생성할 때 FLAG_IMMUTABLE
또는 FLAG_MUTABLE
을 지정하지 않아 발생함. 이 문제를 해결하기 위해 PendingIntent
를 생성하는 코드를 수정해야 함.
문제 상황
Flutter 앱에서 BleService를 사용하는 과정에서 다음과 같은 코드를 포함하고 있었음:
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
Android 12(S) 이상을 타겟으로 하는 경우, 위 코드에서는 FLAG_IMMUTABLE
또는 FLAG_MUTABLE
을 추가로 지정해야 함. 그렇지 않으면 앱이 실행 중에 크래시가 발생함.
해결 방법
아래는 BleService.kt
파일에서 PendingIntent
를 생성하는 부분을 수정한 코드임:
package com.apposter.smart_device
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import com.sxr.sdk.ble.keepfit.service.BluetoothLeService
class BleService : BluetoothLeService() {
companion object {
const val ECG_SWITCH = "ECG_SWITCH"
const val ECG_VALUE = "ECG_VALUE"
const val CHANNEL_ID = "BRingServiceChannel"
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
val notification: Notification =
NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("B.RING")
.setContentText("연결됨")
.setSmallIcon(R.drawable.ic_fluent_smartwatch_24_regular)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel =
NotificationChannel(
CHANNEL_ID,
"B.RING Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}
override fun onDestroy() {
stopForeground(true)
super.onDestroy()
}
}
PendingIntent.getActivity
호출 시 PendingIntent.FLAG_IMMUTABLE
플래그를 추가하여 문제를 해결함. 만약 FLAG_MUTABLE
이 필요한 경우에는 PendingIntent.FLAG_MUTABLE
을 사용하면 됨.
FLAG_IMMUTABLE를 추가해야 하는 이유
Android 12(S) 이상에서는 보안과 안정성을 강화하기 위해 PendingIntent
생성 시 FLAG_IMMUTABLE
또는 FLAG_MUTABLE
을 명시적으로 지정해야 함. 이는 PendingIntent
의 내용을 변경할 수 없도록 하거나 변경 가능하도록 명시적으로 설정하여, 무단 변경으로 인한 보안 문제를 방지하기 위함임.
FLAG_IMMUTABLE
: 생성된PendingIntent
의 내용을 변경할 수 없도록 설정함. 대부분의 경우에 이 플래그를 사용함.FLAG_MUTABLE
: 특정 상황에서PendingIntent
의 내용을 변경할 수 있도록 허용함. 예를 들어, 인라인 응답이나 버블과 같은 기능을 사용해야 하는 경우에만 사용함.
이 수정 후, 앱을 다시 빌드하고 실행하면 에러 없이 정상적으로 동작하는 것을 확인할 수 있음.
결론
Android 12(S) 이상을 타겟으로 하는 앱에서 PendingIntent
를 생성할 때는 반드시 FLAG_IMMUTABLE
또는 FLAG_MUTABLE
을 지정해야 함. 이를 통해 발생할 수 있는 런타임 에러를 방지할 수 있음. 코드에서 PendingIntent
를 생성하는 부분을 확인하고, 필요한 플래그를 추가하여 문제를 해결하도록 함.