[Android] 위젯 만들기 #2

이제일·2022년 11월 5일
0

Android

목록 보기
11/15

해당 포스팅에선 위젯에서 각 뷰 마다의 다른 인텐트 설정,AlramManager를 이용한 업데이트, 앱 내부에서 위젯 업데이트 등을 다루겠습니다.

위젯과 관련한 기본 설명은 아래 링크를 참조해주세요.

PendingIntent 다르게 하기


위와 같이 각 아이콘마다 인텐트에 다른 값을 넘겨주기 위해 아래와 같이 PendingIntent를 설정할 경우
Flage에 따라 각각 독립적으로 넘길 수 없었다.

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
    	...
 			setOnClickPendingIntent(R.id.containerThanks,
                Intent(context, MainActivity::class.java)
                    .putExtra("data", 0)
                    .let {
                        PendingIntent.getActivity(context, 0, it, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
                    }
            )
            setOnClickPendingIntent(R.id.containerSave,
                Intent(context, MainActivity::class.java)
                    .putExtra("data", 1)
                    .let {
                        PendingIntent.getActivity(context, 0, it, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
                    }
            )
		...
	}

이에 경우 action을 추가할 경우 독립적으로 Intent를 넘겨줄 수 있었다.

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
    	...
 			setOnClickPendingIntent(R.id.containerThanks,
                Intent(context, MainActivity::class.java)
                    .apply {
                        action = "thanks"
                        putExtra("data", 0)
                    }.let {
                        PendingIntent.getActivity(context, 0, it, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
                    }
            )
            setOnClickPendingIntent(R.id.containerSave,
                Intent(context, MainActivity::class.java)
                    .apply {
                        action = "save"
                        putExtra("data", 1)
                    }.let {
                        PendingIntent.getActivity(context, 0, it, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
                    }
            )
		...
	}

앱 내부에서 위젯 업데이트하기

AppWidgetProvider를 구현하고 브로드캐스트 리시버를 설정했다면,
onReceive()를 통해 위젯의 활동이 메서드로 연결된다

override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
}

이를 통해 앱 내부에서 앱을 업데이트 할 일이 있을 경우 브로드캐스트를 통해 onUpdate를 호출할 수도 있지만 이는 비싼 작업이기에 따로 액션을 만들어서 넘겨준다

먼저 매니페스트 파일에 해당 액션을 받을 수 있도록 intent-filter에 추가한다.

    <receiver android:name="ExampleAppWidgetProvider" >
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            <!-- 추가 -->
            <action android:name="android.action.APPWIDGET_UPDATE_COUNT" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
                   android:resource="@xml/example_appwidget_info" />
    </receiver>

쓰기 쉽도록 해당 상수를 정의하고

class ExampleAppWidgetProvider : AppWidgetProvider() {
	companion object{
    	const val ACTION_UPDATE_COUNT = "android.action.APPWIDGET_UPDATE_COUNT"
	}
    ...

원하는 Intent를 담아서 브로드캐스트 한다.

context.sendBroadcast(
	Intent(ExampleAppWidgetProvider.ACTION_UPDATE_COUNT).apply {
    	component = ComponentName(context, ExampleAppWidgetProvider::class.java)
    	putExtra("data", widgetData)
    }
)

해당 브로드캐스트는 아래의 onReceive()를 통해 액션이 전달된다.

class ExampleAppWidgetProvider : AppWidgetProvider() {
	...
    
    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        when(intent.action){
            ACTION_UPDATE_COUNT ->{
            	// TODO update count
            }
		}
	}
    
}

자정마다 위젯 업데이트하기

예를 들어 하루마다 날짜가 넘어가도록 위젯이 업데이트 돼야한다면
AppWidgetProviderInfo.xml에서 정의한 updatePeriodMillis로는 위젯이 시작한 시점이기 때문에 이는 0으로 설정하고
AlarmManager를 이용해 자정마다 브로드캐스틀 날려 자동으로 업데이트 할 수 있다.

해당 시나리오에선 onEnabled()에서 알람 등록을, onDisabled()에서 알람 해지를 하면 되겠다.


        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.getBroadcast(
                context,
                1,
                Intent(context, ExampleAppWidgetProvider::class.java).apply { action = AppWidgetManager.ACTION_APPWIDGET_UPDATE },
                PendingIntent.FLAG_MUTABLE
            )
        } else {
            PendingIntent.getBroadcast(
                context,
                1,
                Intent(context, ExampleAppWidgetProvider::class.java).apply { action = AppWidgetManager.ACTION_APPWIDGET_UPDATE },
                PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
            )
        }

        val calendar: Calendar = Calendar.getInstance().apply {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, 0)
            set(Calendar.MINUTE,0)
            set(Calendar.SECOND, 0)
        }
        alarmManager.setInexactRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            pendingIntent
        )

다른 시각을 원할 경우 calendar 설정을 바꾸면 된다.
AlarmManager에 대한 참조 문서

AlarmManager의 경우 재부팅 시 예약이 날아가기 때문에
android.intent.action.BOOT_COMPLETED 액션 리시버를 등록해서 재 예약 해야 한다.
자세한 내용

etc

그 외 겪었던 장애상황

ImageView - Tint 설정

ImageView에는 Tint속성이 있어 xml상에서 색상을 덮어씌울 수 있는데
Widget의 initialLayout의 레이아웃에는 적용이 되지 않았다

그래서 위젯 등록시 ColorFilter를 코드로 설정해주어 해결할 수 있다
ColorFilter를 RemoteViews에서 사용 시 다음과 같이 setInt()로 설정 할 수 있다.

val remoteViews = RemoteViews(context.packageName, layout).apply {
	setInt(R.id.widgetImg,"setColorFilter",  ContextCompat.getColor(context, R.color.color_primary))
}

appWidgetManager.updateAppWidget(appWidgetIds, remoteViews)

해당 질문은 스택오버플로우에도 나와있다.

통신 장애 - 절전모드

안드로이드에서 절전모드를 실행시키면 백그라운드 네트워크를 제한하기에
위젯 생성 시 API 호출로 데이터를 가져오는 경우 TimeOut 에러가 날 수 있다.

이로 인해 새로 고침 버튼을 따로 두거나 JobScheduler등을 이용해 처리해야 한다.

참조

레퍼런스

profile
세상 제일 이제일

0개의 댓글