블루투스 서비스에서 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를 생성하는 부분을 확인하고, 필요한 플래그를 추가하여 문제를 해결하도록 함.