Android 홈 화면 위젯 개발 완벽 가이드 — App Widget, Jetpack Glance, WorkManager와 데이터 갱신2
Android 앱을 개발하다 보면 사용자가 앱을 실행하지 않고도 주요 정보를 확인할 수 있도록 만들고 싶을 때가 있습니다.
예를 들어 음력 달력 앱이라면 홈 화면에 오늘의 양력·음력 날짜와 다가오는 기념일을 표시할 수 있습니다.
조류 도감 앱이라면 오늘의 새 정보나 최근 관찰한 새를 보여줄 수도 있습니다.
이러한 기능을 구현할 때 사용하는 것이 Android App Widget입니다.
하지만 홈 화면 위젯은 일반적인 Activity나 Fragment와 동작 방식이 다릅니다.
위젯은 앱 프로세스가 실행 중이지 않아도 홈 화면에 표시될 수 있으며, 위젯 화면을 관리하는 주체도 일반적으로 런처와 같은 별도 호스트입니다.
따라서 다음 사항을 이해해야 합니다.
App Widget의 기본 구조
RemoteViews와 Jetpack Glance의 차이
위젯 크기 및 레이아웃 설정
SQLite와 DataStore 데이터 표시
위젯 클릭 시 앱 화면 열기
날짜 변경 시 위젯 갱신
WorkManager를 이용한 백그라운드 갱신
배터리 사용량과 업데이트 주기
Android 버전별 위젯 테스트
이번 글에서는 Kotlin + XML 기반 Android 앱에서 활용할 수 있는 방법을 실제 코드와 함께 살펴보겠습니다.
작성 기준: 2026년 10월. minSdk 24 이상을 가정합니다. AndroidX Glance와 WorkManager는 사용 중인 프로젝트에 호환되는 안정 버전을 적용해야 합니다.
1. Android App Widget이란?
App Widget은 Android 홈 화면이나 위젯을 지원하는 호스트에서 앱의 일부 정보를 보여주는 UI입니다.
대표적인 예는 다음과 같습니다.
날씨 위젯
달력 위젯
시계 위젯
할 일 목록 위젯
음악 재생 위젯
배터리 상태 위젯
일반적인 앱 화면과 달리 위젯은 제한된 UI 구성과 별도의 업데이트 방식으로 동작합니다.
Android 앱
│
├── Activity
├── Fragment
├── ViewModel
├── Repository
└── App Widget
│
▼
홈 화면위젯은 앱의 기능을 홈 화면으로 확장하는 구성 요소라고 생각하면 됩니다.
2. App Widget과 일반 앱 화면의 차이
| 구분 | Activity / Fragment | App Widget |
|---|---|---|
| 표시 위치 | 앱 내부 | 홈 화면 등 위젯 호스트 |
| UI 구성 | View, XML, Compose | RemoteViews 또는 Glance |
| 생명주기 | Activity·Fragment 중심 | 위젯 호스트 및 Provider 중심 |
| 사용자 입력 | 다양한 입력 가능 | 제한된 상호작용 |
| 데이터 갱신 | 화면 상태에 따라 | 별도 갱신 요청 필요 |
| 앱 프로세스 | 화면 실행과 연결 | 항상 실행되어 있지 않음 |
가장 중요한 차이는 위젯이 앱 내부의 일반 View를 그대로 표시하는 방식이 아니라는 것입니다.
기존 XML 레이아웃을 그대로 재사용할 수 없는 경우가 많습니다.
App Widget 구현 방식
3. RemoteViews와 Jetpack Glance
Android 위젯은 크게 두 가지 방식으로 개발할 수 있습니다.
RemoteViews
전통적인 Android 위젯 구현 방식입니다.
XML 레이아웃과 AppWidgetProvider를 사용합니다.
Jetpack Glance
Jetpack Compose와 유사한 선언형 API로 위젯 UI를 작성할 수 있는 라이브러리입니다.
다만 Glance는 일반적인 Compose UI를 그대로 위젯에 표시하는 기술은 아닙니다.
Glance의 UI는 위젯 환경에 맞는 RemoteViews 기반 표현으로 변환됩니다.
| 비교 | RemoteViews | Jetpack Glance |
|---|---|---|
| UI 작성 | XML | Kotlin 선언형 UI |
| 기존 XML 개발자 | 익숙함 | 별도 학습 필요 |
| 상태 표현 | 직접 업데이트 | 상태 기반 작성 가능 |
| 복잡한 레이아웃 | 제약 있음 | 역시 위젯 제약 존재 |
| 신규 프로젝트 | 사용 가능 | 우선 검토할 만함 |
기존 앱이 XML 기반이라도 위젯 부분만 Glance로 개발할 수 있습니다.
4. 어떤 방식을 선택하는 것이 좋을까?
기존 XML 기반 앱에 단순한 위젯을 추가한다면 RemoteViews가 이해하기 쉽습니다.
반면 다음과 같은 상황이라면 Glance를 고려할 수 있습니다.
위젯 UI를 Kotlin으로 작성하고 싶을 때
여러 크기의 위젯을 제공할 때
상태에 따라 위젯 화면이 달라질 때
향후 위젯 기능을 확장할 계획이 있을 때
이번 글에서는 먼저 RemoteViews를 이용한 기본 구조를 설명한 뒤 Glance 구현 방법을 살펴보겠습니다.
RemoteViews 위젯 구현
5. 위젯의 기본 구성 파일
RemoteViews 기반 위젯은 일반적으로 다음 파일을 사용합니다.
app/src/main/
│
├── AndroidManifest.xml
│
├── java/com/example/lunarcalendar/
│ └── widget/
│ └── TodayWidgetProvider.kt
│
└── res/
├── layout/
│ └── widget_today.xml
│
└── xml/
└── today_widget_info.xml각 파일의 역할은 다음과 같습니다.
| 파일 | 역할 |
|---|---|
| TodayWidgetProvider.kt | 위젯 이벤트 처리 |
| widget_today.xml | 위젯 UI |
| today_widget_info.xml | 위젯 크기와 설정 |
| AndroidManifest.xml | 위젯 Provider 등록 |
6. 위젯 레이아웃 만들기
먼저 res/layout/widget_today.xml을 생성합니다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widgetRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical"
android:padding="16dp"
android:background="@drawable/bg_widget">
<TextView
android:id="@+id/textTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="오늘의 날짜"
android:textSize="14sp" />
<TextView
android:id="@+id/textSolarDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="2026년 10월 31일"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textLunarDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="음력 날짜"
android:textSize="14sp" />
</LinearLayout>이 레이아웃은 일반적인 Activity 레이아웃과 비슷해 보이지만 RemoteViews에서 지원하는 View 및 속성의 제약을 받습니다.
따라서 모든 Android View와 커스텀 View를 자유롭게 사용할 수 있다고 생각해서는 안 됩니다.
7. 위젯 배경 만들기
res/drawable/bg_widget.xml을 생성합니다.
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="18dp" />
</shape>간단한 둥근 배경입니다.
다만 실제 위젯의 모서리, 패딩, 크기 조절 동작은 런처와 Android 버전에 따라 차이가 있을 수 있습니다.
Android 12 이상에서는 시스템의 위젯 모서리 처리와도 조화를 이루도록 설계하는 것이 좋습니다.
8. 위젯 정보 XML 작성
res/xml/today_widget_info.xml을 생성합니다.
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="100dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/widget_today"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen" />주요 속성은 다음과 같습니다.
| 속성 | 설명 |
|---|---|
| minWidth | 기본 너비 |
| minHeight | 기본 높이 |
| updatePeriodMillis | 시스템에 요청하는 주기적 업데이트 간격 |
| initialLayout | 최초 위젯 레이아웃 |
| resizeMode | 크기 변경 지원 |
| widgetCategory | 지원하는 위젯 호스트 유형 |
여기서 updatePeriodMillis는 정확한 실행 시각을 보장하는 타이머가 아닙니다.
또한 시스템이 제공하는 주기적 위젯 업데이트에는 최소 간격 제한이 있으며, 일반적으로 30분보다 짧은 주기를 기대해서는 안 됩니다.
9. AppWidgetProvider 구현
TodayWidgetProvider.kt를 생성합니다.
class TodayWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
super.onUpdate(
context,
appWidgetManager,
appWidgetIds
)
appWidgetIds.forEach { widgetId ->
updateWidget(
context,
appWidgetManager,
widgetId
)
}
}
}AppWidgetProvider는 BroadcastReceiver를 기반으로 동작합니다.
위젯이 추가되거나 갱신되는 등의 상황에서 관련 콜백이 호출될 수 있습니다.
10. 위젯 화면 업데이트하기
RemoteViews를 사용해 위젯 내용을 변경합니다.
private fun updateWidget(
context: Context,
manager: AppWidgetManager,
widgetId: Int
) {
val views = RemoteViews(
context.packageName,
R.layout.widget_today
)
val today = LocalDate.now()
views.setTextViewText(
R.id.textSolarDate,
"${today.year}년 " +
"${today.monthValue}월 " +
"${today.dayOfMonth}일"
)
views.setTextViewText(
R.id.textLunarDate,
"음력 날짜 계산 필요"
)
manager.updateAppWidget(
widgetId,
views
)
}이 예제에서는 양력 날짜만 실제로 계산합니다.
음력 날짜는 별도의 변환 로직이 필요합니다.
또한 LocalDate는 Android API 26부터 기본 제공되므로 minSdk 24 프로젝트에서 사용하려면 Java 8+ API desugaring 설정을 확인해야 합니다.
음력 날짜는 검증된 변환 로직이나 기존 앱의 날짜 계산 기능을 재사용하는 것이 좋습니다.
11. AndroidManifest.xml 등록
위젯 Provider를 Manifest에 등록합니다.
<receiver
android:name=".widget.TodayWidgetProvider"
android:exported="true">
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/today_widget_info" />
</receiver>등록이 완료되면 위젯을 지원하는 런처의 위젯 목록에서 앱의 위젯을 찾을 수 있습니다.
실제 표시 여부는 런처의 위젯 지원 상태와 앱 설치 환경에 영향을 받습니다.
위젯 클릭 이벤트
12. 위젯을 누르면 앱 열기
사용자가 위젯을 누르면 앱의 특정 화면으로 이동하도록 만들 수 있습니다.
RemoteViews에서는 PendingIntent를 사용할 수 있습니다.
val intent = Intent(
context,
MainActivity::class.java
)
val pendingIntent =
PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(
R.id.widgetRoot,
pendingIntent
)위 코드는 updateWidget() 안에서 manager.updateAppWidget()을 호출하기 전에 추가하면 됩니다.
이렇게 하면 사용자가 위젯을 누를 때 MainActivity를 열 수 있습니다.
13. 특정 화면으로 이동하기
음력 달력 앱에서 위젯을 누르면 기념일 목록 화면으로 이동하고 싶을 수 있습니다.
이 경우 Intent Extra를 전달할 수 있습니다.
val intent = Intent(
context,
MainActivity::class.java
).apply {
putExtra(
"open_screen",
"anniversary"
)
}MainActivity에서는 전달받은 값을 확인해 원하는 화면으로 이동할 수 있습니다.
val targetScreen =
intent.getStringExtra("open_screen")
if (targetScreen == "anniversary") {
// Navigation Component로
// 기념일 화면 이동
}단, Activity가 이미 실행 중인 경우에는 onNewIntent() 처리 여부도 확인해야 합니다.
또한 외부에서 전달될 수 있는 Intent 값은 검증하는 것이 좋습니다.
Jetpack Glance 구현
14. Jetpack Glance란?
Jetpack Glance는 Compose와 유사한 Kotlin 선언형 문법을 이용해 Android 위젯을 구현할 수 있는 라이브러리입니다.
예를 들어 다음과 같은 형태로 UI를 작성할 수 있습니다.
@Composable
fun TodayWidgetContent() {
Column {
Text("오늘의 날짜")
Text("2026년 10월 31일")
Text("음력 날짜")
}
}다만 위 코드는 Glance 구성 요소의 import와 실제 위젯 컨텍스트가 생략된 개념 예제입니다.
Glance에서는 일반 Compose의 Text, Column과 이름이 비슷한 별도의 API를 사용합니다.
따라서 일반 Compose 코드를 그대로 복사해서 사용할 수는 없습니다.
15. Glance 의존성 추가
build.gradle.kts에 다음과 같이 추가합니다.
dependencies {
implementation(
"androidx.glance:glance-appwidget:<version>"
)
}<version>에는 공식 AndroidX 릴리스에서 확인한 호환 버전을 사용합니다.
Glance를 사용하기 위해 앱 전체 UI를 Compose로 변경할 필요는 없습니다.
기존 XML Activity와 Fragment는 그대로 유지하고 위젯 부분만 Glance로 작성할 수 있습니다.
16. GlanceAppWidget 작성
간단한 위젯을 작성해 보겠습니다.
class TodayGlanceWidget : GlanceAppWidget() {
override suspend fun provideGlance(
context: Context,
id: GlanceId
) {
provideContent {
Column(
modifier = GlanceModifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "오늘의 날짜"
)
Text(
text = "음력 달력"
)
}
}
}
}필요한 import는 AndroidX Glance 패키지에서 가져옵니다.
import androidx.glance.GlanceModifier
import androidx.glance.GlanceId
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.layout.Column
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.padding
import androidx.glance.text.Text
import androidx.glance.unit.dp사용하는 Glance 버전에 따라 import 및 API가 달라질 수 있으므로 IDE 자동완성과 공식 문서를 함께 확인해야 합니다.
17. GlanceAppWidgetReceiver 작성
Glance 위젯도 Receiver가 필요합니다.
class TodayGlanceReceiver :
GlanceAppWidgetReceiver() {
override val glanceAppWidget:
GlanceAppWidget = TodayGlanceWidget()
}그리고 Manifest에 등록합니다.
<receiver
android:name=".widget.TodayGlanceReceiver"
android:exported="true">
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/today_glance_widget_info" />
</receiver>today_glance_widget_info.xml에는 위젯 크기, 초기 레이아웃 등 필요한 메타데이터를 정의해야 합니다.
Glance 위젯도 Android App Widget의 기본 등록 구조를 사용합니다.
18. Glance 위젯 업데이트
Glance 위젯은 다음과 같이 갱신할 수 있습니다.
TodayGlanceWidget().updateAll(context)이 함수는 suspend 함수이므로 Coroutine 등 적절한 비동기 실행 환경에서 호출해야 합니다.
예를 들어 Repository에서 데이터가 변경된 후 다음과 같이 호출할 수 있습니다.
withContext(Dispatchers.IO) {
TodayGlanceWidget()
.updateAll(context)
}단, updateAll()은 위젯 UI를 다시 구성하도록 요청하는 것이며 데이터베이스 변경을 자동 감지하는 것은 아닙니다.
따라서 앱의 데이터 변경 시점과 위젯 갱신 시점을 연결해야 합니다.
위젯과 SQLite 연동
19. SQLite 데이터를 위젯에 표시하기
조류 도감 앱에서 최근 관찰한 새를 위젯에 표시한다고 가정하겠습니다.
예를 들어 다음과 같은 테이블이 있습니다.
CREATE TABLE bird_memos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
is_like INTEGER,
is_watching INTEGER,
memo TEXT,
created_at INTEGER
);최근 관찰 기록을 조회할 수 있습니다.
SELECT name, memo
FROM bird_memos
WHERE is_watching = 1
ORDER BY created_at DESC
LIMIT 1;조회 결과를 위젯에 표시하면 됩니다.
최근 관찰한 새
참새
2026년 10월 30일 관찰하지만 SQLite 조회를 AppWidgetProvider.onUpdate()에서 오래 수행하는 것은 피해야 합니다.
BroadcastReceiver는 짧은 시간 안에 작업을 마쳐야 하기 때문입니다.
20. SQLite 조회는 백그라운드에서 처리하기
SQLite 데이터가 많거나 파일 접근 시간이 길다면 WorkManager 또는 적절한 비동기 작업을 사용해야 합니다.
추천 구조는 다음과 같습니다.
AppWidgetProvider
│
▼
WorkManager
│
▼
WidgetUpdateWorker
│
▼
SQLite Repository
│
▼
RemoteViews 업데이트이렇게 하면 데이터 조회와 위젯 업데이트를 메인 스레드에서 분리할 수 있습니다.
WorkManager 연동
21. WorkManager가 필요한 이유
위젯은 앱이 실행되지 않는 동안에도 갱신이 필요할 수 있습니다.
예를 들어 다음과 같은 상황입니다.
날짜가 변경됨
서버에서 새로운 정보 수신
SQLite 데이터 갱신
기념일 정보 변경
날씨 정보 업데이트
이러한 작업에 WorkManager를 활용할 수 있습니다.
하지만 WorkManager는 정확한 시각에 실행되는 스케줄러가 아닙니다.
시스템의 배터리 최적화, Doze 모드, 작업 제한 등에 따라 실행이 지연될 수 있습니다.
22. WorkManager 의존성 추가
dependencies {
implementation(
"androidx.work:work-runtime-ktx:<version>"
)
}<version>은 AndroidX 공식 릴리스에서 확인한 호환 버전을 사용합니다.
23. WidgetUpdateWorker 구현
다음은 RemoteViews 기반 위젯을 갱신하는 예제입니다.
class WidgetUpdateWorker(
appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(
appContext,
workerParams
) {
override suspend fun doWork(): Result {
val manager =
AppWidgetManager.getInstance(
applicationContext
)
val component = ComponentName(
applicationContext,
TodayWidgetProvider::class.java
)
val widgetIds =
manager.getAppWidgetIds(component)
widgetIds.forEach { widgetId ->
TodayWidgetUpdater.update(
applicationContext,
manager,
widgetId
)
}
return Result.success()
}
}이 예제에서는 위젯 업데이트 로직을 TodayWidgetUpdater라는 별도 객체로 분리했다고 가정합니다.
TodayWidgetUpdater.update()는 앞서 작성한 RemoteViews 갱신 로직을 재사용하도록 구현하면 됩니다.
24. 주기적 작업 등록
WorkManager에서 주기적 작업을 등록할 수 있습니다.
val request =
PeriodicWorkRequestBuilder<
WidgetUpdateWorker
>(
24,
TimeUnit.HOURS
).build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
"today_widget_update",
ExistingPeriodicWorkPolicy.KEEP,
request
)이렇게 하면 대략 24시간 주기로 위젯 갱신 작업을 요청할 수 있습니다.
하지만 매일 자정에 정확하게 실행되는 것은 아닙니다.
또한 WorkManager의 주기적 작업은 최소 15분 간격 제한이 있습니다.
따라서 날짜가 바뀌는 정확한 순간에 UI가 변경되어야 한다면 별도의 설계가 필요합니다.
25. 자정에 정확하게 위젯을 갱신할 수 있을까?
음력 달력 위젯은 날짜가 바뀌면 화면도 변경되어야 합니다.
하지만 WorkManager는 정확한 자정 실행을 보장하지 않습니다.
다음과 같은 방법을 함께 고려할 수 있습니다.
날짜 변경 관련 시스템 브로드캐스트 활용
앱 실행 시 위젯 갱신
사용자가 날짜 또는 기념일을 변경할 때 즉시 갱신
WorkManager를 이용한 보완 갱신
정확한 알람 API를 사용하는 방법도 있지만, 최신 Android에서는 정확한 알람 권한과 정책 제한을 고려해야 합니다.
단순히 날짜 위젯을 갱신하기 위해 특별한 정확한 알람 권한을 요청하는 것은 신중하게 판단해야 합니다.
26. 날짜 변경 브로드캐스트 활용
Android에서는 날짜가 변경될 때 ACTION_DATE_CHANGED 브로드캐스트가 발생할 수 있습니다.
이를 이용해 위젯 갱신을 요청하는 구조를 고려할 수 있습니다.
다만 시스템 브로드캐스트는 Android 버전과 실행 상태에 따라 수신 방식에 제약이 있을 수 있습니다.
따라서 브로드캐스트 하나에만 의존하지 않는 것이 좋습니다.
특히 다음 상황도 고려해야 합니다.
기기 재부팅
시간대 변경
사용자가 날짜를 수동으로 변경
앱이 장시간 실행되지 않음
제조사별 배터리 최적화
날짜 위젯은 여러 갱신 경로를 조합해 안정성을 높이는 것이 좋습니다.
DataStore와 위젯
27. DataStore 설정값 표시하기
음력 달력 앱에서는 다음과 같은 설정값을 저장할 수 있습니다.
show_lunar_date = true
show_anniversary = true
theme_mode = system위젯도 이러한 설정을 반영할 수 있습니다.
예를 들어 사용자가 음력 표시를 비활성화했다면 위젯에서 음력 날짜를 숨길 수 있습니다.
설정 화면
│
▼
DataStore 저장
│
▼
위젯 갱신 요청
│
▼
홈 화면 반영중요한 점은 DataStore 값이 변경되었다고 해서 RemoteViews 위젯이 자동으로 다시 그려지는 것은 아니라는 것입니다.
설정 변경 후 위젯 업데이트를 요청해야 합니다.
28. 설정 변경 후 즉시 갱신하기
예를 들어 사용자가 설정 화면에서 음력 날짜 표시 여부를 변경했다고 가정하겠습니다.
viewModel.updateShowLunarDate(enabled)설정 저장이 완료된 뒤 위젯 갱신을 요청할 수 있습니다.
TodayGlanceWidget()
.updateAll(context)RemoteViews 방식이라면 AppWidgetManager를 이용해 갱신할 수 있습니다.
단, 저장 작업이 비동기적으로 진행되는 경우에는 실제 저장 완료 시점과 갱신 요청 시점을 맞추는 것이 좋습니다.
위젯 크기와 반응형 UI
29. 위젯 크기별 UI 구성
사용자는 홈 화면에서 위젯 크기를 변경할 수 있습니다.
예를 들어 다음과 같은 형태입니다.
작은 위젯
┌─────────────┐
│ 10월 31일 │
│ 음력 날짜 │
└─────────────┘큰 위젯
┌────────────────────────┐
│ 오늘의 날짜 │
│ │
│ 2026년 10월 31일 │
│ 음력 날짜 │
│ │
│ 다가오는 기념일 │
│ 생일 D-3 │
└────────────────────────┘작은 위젯에는 핵심 정보만 표시하고, 큰 위젯에는 추가 정보를 제공하는 방식이 좋습니다.
30. 위젯 크기 변경 감지
RemoteViews 기반 위젯에서는 onAppWidgetOptionsChanged()를 활용할 수 있습니다.
override fun onAppWidgetOptionsChanged(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
newOptions: Bundle
) {
super.onAppWidgetOptionsChanged(
context,
appWidgetManager,
appWidgetId,
newOptions
)
// 크기에 맞는 레이아웃 갱신
}위젯 크기에 따라 표시 항목을 변경할 수 있습니다.
다만 실제 크기 계산과 호스트의 셀 배치 방식은 런처에 따라 달라질 수 있습니다.
31. Glance의 반응형 UI
Glance에서는 위젯 크기에 따라 다른 UI를 표시할 수 있습니다.
예를 들어 SizeMode.Responsive를 사용할 수 있습니다.
override val sizeMode =
SizeMode.Responsive(
setOf(
DpSize(180.dp, 100.dp),
DpSize(280.dp, 180.dp)
)
)이 예제는 두 가지 기준 크기를 선언합니다.
실제 화면에서는 Glance의 크기 관련 API를 이용해 현재 위젯 크기에 맞는 콘텐츠를 선택할 수 있습니다.
다만 모든 런처에서 크기 변경 동작이 동일하지는 않으므로 실제 기기 테스트가 필요합니다.
위젯 성능 최적화
32. 위젯에서 무거운 작업을 피해야 하는 이유
위젯은 홈 화면에 항상 표시될 수 있습니다.
따라서 갱신할 때마다 불필요하게 많은 작업을 수행하면 배터리와 시스템 자원을 소비할 수 있습니다.
특히 다음과 같은 작업은 주의해야 합니다.
매번 전체 SQLite 테이블 조회
불필요한 네트워크 요청
대용량 이미지 디코딩
지나치게 짧은 갱신 주기
동일한 데이터 반복 처리
여러 위젯 인스턴스에 대한 중복 작업
위젯에는 필요한 최소한의 정보만 표시하는 것이 좋습니다.
33. 위젯 데이터 캐싱
예를 들어 조류 도감 위젯에서 오늘의 새를 표시한다고 가정하겠습니다.
매번 서버에서 정보를 가져오는 대신 SQLite에 저장된 데이터를 활용할 수 있습니다.
서버 API
│
▼
Repository
│
▼
SQLite 캐시
│
▼
위젯 표시이렇게 하면 네트워크 연결이 없어도 위젯에 정보를 표시할 수 있습니다.
또한 서버 요청 횟수를 줄일 수 있습니다.
34. 이미지 위젯의 메모리 문제
조류 도감 위젯에서 새 사진을 표시한다면 이미지 크기에 주의해야 합니다.
예를 들어 4000×3000 이미지의 ARGB_8888 Bitmap은 픽셀 데이터만 약 48MB를 사용할 수 있습니다.
따라서 위젯에 원본 이미지를 그대로 전달하는 것은 피하는 것이 좋습니다.
이미지를 위젯 크기에 맞게 축소하고, 필요한 경우 별도 썸네일을 사용하는 것이 좋습니다.
RemoteViews는 일반적인 ImageView와 이미지 로딩 방식이 다르므로 Glide를 사용할 때도 App Widget용 Target이나 적절한 Bitmap 처리 방식을 검토해야 합니다.
35. 위젯이 여러 개 추가된 경우
사용자는 같은 앱의 위젯을 여러 개 홈 화면에 추가할 수 있습니다.
예를 들어 다음과 같은 상황입니다.
홈 화면 1
└── 오늘의 음력 위젯
홈 화면 2
└── 기념일 위젯
홈 화면 3
└── 오늘의 음력 위젯따라서 위젯 갱신 시 특정 위젯 ID 하나만 처리해서는 안 될 수 있습니다.
AppWidgetManager.getAppWidgetIds()를 이용해 현재 등록된 위젯 목록을 확인할 수 있습니다.
또한 위젯별로 다른 설정을 제공한다면 appWidgetId를 기준으로 설정을 구분해야 합니다.
36. 위젯 삭제 시 정리 작업
사용자가 홈 화면에서 위젯을 삭제할 수 있습니다.
이때 AppWidgetProvider.onDeleted()가 호출될 수 있습니다.
override fun onDeleted(
context: Context,
appWidgetIds: IntArray
) {
super.onDeleted(
context,
appWidgetIds
)
// 위젯별 설정 정리
}위젯별로 저장한 설정이 있다면 해당 ID의 데이터를 정리할 수 있습니다.
또한 마지막 위젯이 삭제되었을 때 불필요한 주기적 갱신 작업을 취소하는 것도 고려할 수 있습니다.
Android 버전별 고려사항
37. Android 12 이후 위젯 디자인
Android 12부터 위젯 디자인 관련 기능이 개선되었습니다.
대표적으로 다음 요소를 고려할 수 있습니다.
둥근 모서리
위젯 크기 조절
시스템 테마와 색상
반응형 레이아웃
위젯 미리보기
하지만 Android 12 이상의 기능을 사용하더라도 이전 Android 버전에서 기본 위젯이 정상적으로 표시되도록 구성해야 합니다.
38. Android 15·16에서 주의할 점
최신 Android에서도 위젯의 기본적인 동작 원리는 동일합니다.
다만 백그라운드 작업, 배터리 최적화, 앱 프로세스 실행 제한 등은 계속 고려해야 합니다.
특히 다음 사항이 중요합니다.
위젯 갱신을 위해 앱 프로세스가 항상 실행된다고 가정하지 않기
WorkManager 실행 시각을 정확하게 보장한다고 생각하지 않기
앱이 종료된 상태에서도 표시할 데이터를 유지하기
앱 업데이트 이후 위젯이 정상 동작하는지 확인하기
런처별 크기 및 레이아웃 차이 테스트하기
테스트와 오류 해결
39. 위젯이 목록에 나타나지 않는 경우
다음 사항을 확인합니다.
| 증상 | 확인할 내용 |
|---|---|
| 위젯 목록에 없음 | Manifest Receiver 등록 |
| 위젯 추가 실패 | appwidget-provider XML |
| 빈 화면 표시 | initialLayout 및 RemoteViews |
| 날짜가 변경되지 않음 | 갱신 트리거 |
| 클릭해도 반응 없음 | PendingIntent 설정 |
| 일부 기기에서 레이아웃 깨짐 | 위젯 크기 및 런처 차이 |
| 이미지 표시 실패 | Bitmap 크기와 URI 접근 |
| 앱 업데이트 후 오류 | 리소스 ID 및 위젯 설정 호환성 |
위젯은 앱 내부 화면과 달리 런처 환경의 영향을 많이 받습니다.
따라서 에뮬레이터뿐 아니라 실제 기기에서도 테스트하는 것이 좋습니다.
40. 위젯 테스트 시나리오
다음 상황을 테스트해 보는 것이 좋습니다.
위젯 최초 추가
위젯 여러 개 추가
위젯 크기 변경
위젯 클릭
앱 강제 종료 후 표시
기기 재부팅
날짜 변경
시간대 변경
앱 데이터 변경
SQLite 데이터베이스 업데이트
DataStore 설정 변경
네트워크 연결 해제
배터리 절약 모드
앱 업데이트
위젯 삭제
특히 날짜 위젯이라면 자정 전후와 시간대 변경 상황을 테스트하는 것이 중요합니다.
41. 실제 앱에 적용하는 추천 구조
음력 달력 앱에서는 다음과 같은 구조를 추천합니다.
LunarCalendarApp
│
├── ui/
│ ├── calendar/
│ ├── anniversary/
│ └── settings/
│
├── data/
│ ├── CalendarRepository
│ ├── AnniversaryRepository
│ └── SettingsRepository
│
└── widget/
├── TodayWidgetProvider
├── TodayWidgetUpdater
├── WidgetUpdateWorker
└── TodayGlanceWidget여기서 중요한 것은 앱과 위젯이 동일한 데이터 계층을 활용하도록 설계하는 것입니다.
위젯만을 위한 별도의 데이터베이스를 무조건 만들 필요는 없습니다.
기존 SQLite와 DataStore를 활용하고, 위젯 갱신 로직만 분리하는 것이 관리하기 쉽습니다.
42. Android App Widget 개발 체크리스트
위젯의 목적과 표시 정보 정의
RemoteViews와 Glance 중 구현 방식 선택
위젯 레이아웃 작성
appwidget-provider XML 생성
AppWidgetProvider 또는 Glance Receiver 등록
Manifest 설정 확인
위젯 최초 표시 테스트
위젯 클릭 이벤트 구현
PendingIntent 플래그 확인
SQLite 데이터 조회 구조 분리
DataStore 설정 반영
데이터 변경 시 위젯 갱신
WorkManager 필요성 검토
과도한 주기적 갱신 방지
날짜 변경 및 시간대 변경 처리
위젯 크기별 UI 구성
여러 위젯 인스턴스 지원
위젯 삭제 시 설정 정리
이미지 크기 최적화
앱 종료 및 재부팅 테스트
Android 버전별 테스트
실제 런처에서 레이아웃 검증
43. 마무리
Android App Widget은 사용자가 앱을 실행하지 않고도 주요 정보를 확인할 수 있도록 만드는 유용한 기능입니다.
특히 달력, 날씨, 할 일, 정보 조회 앱처럼 자주 확인하는 데이터가 있는 경우 활용도가 높습니다.
위젯을 개발할 때는 일반적인 Activity와 다른 동작 방식을 이해해야 합니다.
핵심 원칙은 다음과 같습니다.
위젯은 앱 내부의 일반 View와 다르게 동작합니다.
RemoteViews 또는 Jetpack Glance를 이용해 UI를 구성합니다.
기존 XML 앱에서도 Glance를 사용할 수 있습니다.
위젯 갱신은 명시적으로 요청해야 합니다.
SQLite와 DataStore의 기존 데이터 계층을 재사용할 수 있습니다.
WorkManager는 정확한 실행 시각을 보장하지 않습니다.
날짜 위젯은 자정, 시간대 변경, 재부팅을 함께 고려해야 합니다.
위젯에는 필요한 최소한의 데이터만 표시합니다.
여러 위젯 인스턴스와 크기 변경을 지원해야 합니다.
실제 기기와 런처에서 충분히 테스트해야 합니다.
음력 달력 앱이라면 오늘의 날짜와 다가오는 기념일을 표시하는 위젯부터 시작하는 것이 좋습니다.
조류 도감 앱이라면 최근 관찰 기록이나 오늘의 새 정보를 표시하는 위젯으로 확장할 수 있습니다.
Android 위젯 개발의 핵심은 복잡한 화면을 홈 화면에 옮기는 것이 아니라, 사용자가 가장 자주 확인하는 정보를 간단하고 안정적으로 제공하는 것입니다.
참고 자료
Android Developers — App Widgets Overview
https://developer.android.com/develop/ui/views/appwidgets/overviewAndroid Developers — Create a Simple Widget
https://developer.android.com/develop/ui/views/appwidgetsAndroid Developers — Jetpack Glance
https://developer.android.com/develop/ui/compose/glanceAndroid Developers — Glance App Widgets
https://developer.android.com/develop/ui/compose/glance/user-interactionAndroid Developers — WorkManager
https://developer.android.com/topic/libraries/architecture/workmanagerAndroid Developers — Periodic Work
https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started/define-workAndroid Developers — PendingIntent
https://developer.android.com/reference/android/app/PendingIntent
태그: Android, Kotlin, App Widget, Android 위젯, Jetpack Glance, RemoteViews, AppWidgetProvider, WorkManager, SQLite, DataStore, PendingIntent, 홈 화면 위젯, Android 12, Android 15, Android 16, 음력 달력 앱