IT 프로그래밍-Android

[안드로이드] receiver 사용 시 주의할 점

godsangin 2022. 2. 7. 12:06
반응형

안녕하세요. 오늘은 제가 AppWidgetProvider를 사용하면서 겪은 고충을 공유하고자 합니다.

 

일반적인 broadcast receiver를 사용할 때에도 이러한 문제가 발생할 수 있는데요

제가 겪은 문제는 다음과 같습니다.

저는 앱 위젯을 사용하여 흡연주기를 체크하는 앱을 개발하고 있었습니다. 기본적으로 타이머의 기능을 수행하려다 보니 위젯에 progressbar를 두고, 버튼을 만들어 버튼 클릭 시 progressbar를 초기화하는 기능을 만들었습니다.

문제는 버튼 클릭 이벤트와 1분단위로 위젯을 초기화하는 이벤트 총 두가지 broadcast를 송수신하는 부분에서 발생하였습니다.

 

아래는 제가 작성한 AppWidgetProvider입니다.

class WidgetProvider:AppWidgetProvider() {
    private val MY_ACTION = "android.action.MY_ACTION"
    private val MY_UPDATE_ACTION = "android.action.MY_UPDATE_ACTION"


    fun smoke(context:Context?):PendingIntent{
        val intent = Intent(context, WidgetProvider::class.java)
        intent.putExtra("action", MY_ACTION)
        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    }

    fun addViews(context:Context?):RemoteViews{
        val views = RemoteViews(context?.packageName, R.layout.item_widget)
        views.setOnClickPendingIntent(R.id.smoke_bt, smoke(context))
        val pref = context?.getSharedPreferences("timer", Activity.MODE_PRIVATE)
        val lastSmokeTime = pref?.getLong("smoke", (-1).toLong())
        val timer = pref?.getInt("timer", -1)
        var progress = System.currentTimeMillis() - (lastSmokeTime ?: 0.toLong())
        progress /= 60000
        views.setProgressBar(R.id.progress_bar, timer ?: 100, progress.toInt(), false)
        return views
    }

    override fun onReceive(context: Context?, intent: Intent?) {
        val pref = context?.getSharedPreferences("timer", Activity.MODE_PRIVATE)
        val e = pref?.edit()
        val timer = pref?.getInt("timer", -1) ?: 60
        val lastSmokeTime = pref?.getLong("smoke", (-1).toLong()) ?: (0).toLong()
        val newTime = System.currentTimeMillis()
        val period = ((newTime - lastSmokeTime) / 1000 / 60).toInt()

        if(intent?.getStringExtra("action").equals(MY_ACTION)){
            intent?.putExtra("action", MY_UPDATE_ACTION)
            e?.putLong("smoke", newTime)
            e?.commit()
            CoroutineScope(Dispatchers.IO).launch{
                val appDatabase = Room.databaseBuilder(context!!, AppDatabase::class.java, "smoketimer.db")
                    .build()
                appDatabase.getSmokeDao().insert(Smoke(0, 0, newTime, period))
            }
            alarmBroadcastReceiver(context, SystemClock.elapsedRealtime() + (timer * 60 * 1000))
            //add history

        }
        else{

        }
        //바꿈 원래 조건 없음
        if(period < timer){
        }
        makeAlarmUpdate(context)
        val views: RemoteViews = addViews(context)
        val appWidgetManager = AppWidgetManager.getInstance(context)
        appWidgetManager?.updateAppWidget(ComponentName(context!!, WidgetProvider::class.java), views)
    }

    override fun onUpdate(
        context: Context?,
        appWidgetManager: AppWidgetManager?,
        appWidgetIds: IntArray?
    ) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
            appWidgetIds?.forEach { appWidgetId ->
                val views: RemoteViews = addViews(context)
                appWidgetManager?.updateAppWidget(appWidgetId, views)
            }
    }

    fun makeAlarmUpdate(context:Context?){
        val alarmManager = context?.getSystemService(AppCompatActivity.ALARM_SERVICE) as AlarmManager
        val alarmIntent = Intent(context, WidgetProvider::class.java)
        alarmIntent.putExtra("action", MY_UPDATE_ACTION)
        val pendingIntent = PendingIntent.getBroadcast(context,
            0,
            alarmIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + (1000*60),
            pendingIntent)

    }

    fun alarmBroadcastReceiver(context:Context?, time:Long){
        context?.apply{
            val alarmManager = getSystemService(AppCompatActivity.ALARM_SERVICE) as AlarmManager
            val intent = Intent(applicationContext, AlarmBroadcastReceiver::class.java)
            val pendingIntent = PendingIntent.getBroadcast(this,
                AlarmBroadcastReceiver.NOTIFICATION_ID,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
            alarmManager.set(AlarmManager.ELAPSED_REALTIME,
                time,
                pendingIntent)
        }

    }
}

smoke라는 PendingIntent를 만들어 버튼역할을 하는 smoke_bt에 바인드하고, onReceive함수에서 smoke 이벤트에 따른 반응과 평소에 앱 위젯을 업데이트하기 위한 반응을 작성하였습니다.

 

위젯은 1분에 한번씩 초기화할 수 있도록 알람매니저를 통해 새로운 PendingIntent를 BroadcastReceiver를 사용하여 발생시키도록 하였습니다.

 

결과는 어땠을까요 ?

그렇습니다. 저는 1분에 한번씩 담배를 피우는 골초가 되어있었습니다.

1분에 한번 위젯을 업데이트하라고 했더니 smoke_bt를 실행한 효과를 발생시킨 이것은 바로 PendingIntent의 문제로 발생하였습니다.

smoke함수를 다시 보시죠

fun smoke(context:Context?):PendingIntent{
        val intent = Intent(context, WidgetProvider::class.java)
        intent.putExtra("action", MY_ACTION)
        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    }

그리고 알람매니저를 통해 앱을 업데이트하는 부분을 다시 볼까요 ?

fun makeAlarmUpdate(context:Context?){
        val alarmManager = context?.getSystemService(AppCompatActivity.ALARM_SERVICE) as AlarmManager
        val alarmIntent = Intent(context, WidgetProvider::class.java)
        alarmIntent.putExtra("action", MY_UPDATE_ACTION)
        val pendingIntent = PendingIntent.getBroadcast(context,
            0,
            alarmIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + (1000*60),
            pendingIntent)

    }

자 찾으셨나요 ?

두 이벤트의 PendingIntent는 별개의 것인데 두 PendingIntent의 requestCode가 동일하게 0으로 셋팅된 것이 아닙니까 ?!!

게다가 flag는 UPDATE_CURRENT라니...1분마다 한번씩 smoke evet의 PendingIntent를 UPDATE하니 계속해서 Smoke DB에 그 결과가 저장되고 있던겁니다 !!

 

그래서 저는 알람인텐트의 request코드를 다음과 같이 변경하여 문제를 해결할 수 있었습니다.

val pendingIntent = PendingIntent.getBroadcast(context,
            1000,
            alarmIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )

이렇게 간단한 문제를 방치하고 있었다니, 저는 정말 구제불능인가봅니다...(ㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎ)

 

혹시나 저와 같은 문제를 맞이하고 계신 분이 있다면 얼른 requestcode를 변경해보도록 하십시오. 이런 문제는 어디서 문제가 생긴건지 파악하기 힘들어 며칠을 고생하더라구요...클린코드의 중요성을 또한번 느낍니다 ㅜㅜ

 

자 그럼 도움이 되셨길 바라며 저는 이만 물러가도록 하겠습니다.