2020/03/09 - [Android/Widget] - 안드로이드 위젯 개발하기 (1)

2020/03/10 - [Android/Widget] - 안드로이드 위젯 개발하기 (2)

2020/03/13 - [Android/Widget] - 안드로이드 위젯 개발하기 (3)

 

누군가가 내가 만든 앱에 View 를 만들고 제멋대로 동작하게 만들어 놓았다고 생각해본다면, 매우 짜증나는 일일 것입니다.

위젯이란 것도 결국 내가 남의 앱위에 View 를 만들고 동작하도록 만드는 것이기 때문에, 위젯을 만들어 사용하는데는 매우 제한적인 방법을 사용해야 합니다.

 

단순히 TextView 의 Text 를 수정하는 것도 아래와 같이 별도의 API 를 사용해야 합니다.

        val remoteViews = RemoteViews(
            "com.tistory.develop_branch.widget",
            R.layout.widget_layout
        )
        remoteViews.setTextViewText(R.id.widget_text, "setTextViewText")

 

안드로이드 위젯의 경우, 사용할 수 있는 View 의 종류도 정해져 있습니다.

처음 위젯을 개발할 때, 가장 쉽게 부딪히는 오류가 이런 View 를 사용하지 않아서 입니다. 상속한 View 조차 사용하면 안됩니다.

위젯에서 사용할 수 있는 View 의 종류는 여기를 확인하세요.

 

이런 제약사항들은 RemoteView 에 대한 문서를 읽어보면, 좀 더 자세히 알 수 있습니다.

 

이 제약사항들 중 아마도 가장 중요한 것은 setOnClickPendingIntent 일 것입니다.

여기서 PendingIntent 에 대한 개발자 문서도 한 번 읽어보고 넘어가는 것이 좋습니다.

 

A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. This means that, even if its owning application's process is killed, the PendingIntent itself will remain usable from other processes that have been given it. If the creating application later re-retrieves the same kind of PendingIntent (same operation, same Intent action, data, categories, and components, and same flags), it will receive a PendingIntent representing the same token if that is still valid, and can thus call cancel() to remove it.

 

PendingIntent 는 시스템에 의해 유지되는 레퍼런스 토큰이라는 말이 나옵니다.

같은 intent action, data, category, component, flag 를 가지고 있다면 같은 PendingIntent 를 사용하게 된다는 의미로, PendingIntent를 생성할 때 사용하는 Intent 에 다른 Extra 값을 지정한다고 해서 다른 PendingIntent 가 생성되지는 않습니다.

그래서, widget Click 시 다른 동작을 지정하고자 할 때는 Extra 값이 아닌, Action 혹은 Data 를 변경해야 합니다.

이를 테면, 보통 A 라는 Activity 를 실행하기 위해 putExtra("extra",1), putExtra("extra",2) 로 구분해서 사용했다고 해서, PendingIntent 에 동일하게 적용하면 낭패를 보기 쉽습니다.

만약, PendingIntent 적용하고자 한다면, extra 값이 아닌, action = "com.tistory.develop_branch.widget.1", action = "com.tistory.develop_branch.widget.2" 와 같이 action 을 변경해서 사용해야 합니다.

 

 

2020/03/09 - [Android/Widget] - 안드로이드 위젯 개발하기 (1)

2020/03/10 - [Android/Widget] - 안드로이드 위젯 개발하기 (2)

2020/03/13 - [Android/Widget] - 안드로이드 위젯 개발하기 (3)

'Android Widget' 카테고리의 다른 글

안드로이드 위젯 개발하기 (3)  (0) 2020.03.13
안드로이드 위젯 개발하기 (2)  (0) 2020.03.10
안드로이드 위젯 개발하기 (1)  (0) 2020.03.09

 

2020/03/09 - [Android/Widget] - 안드로이드 위젯 개발하기 (1)

2020/03/10 - [Android/Widget] - 안드로이드 위젯 개발하기 (2)

 

이전 글에서는 WidgetProvider 를 BroadcastReceiver 를 통해 구현한 것이었습니다.

class WidgetProvider : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
    }
}

 

BroadcastReceiver 를 사용한 것은 Widget 을 개발하는데 있어 기본적인 동작을 확인하기 위함이었습니다.

개발자 문서에도 기술되어 있지만, WidgetProvider 는 BroadcastReceiver 보다는 AppWidgetProvider 로 구현하는 것이 좋습니다.

이유는 AppWidgetProvider 소스 코드에 다 나와있지만, 간단히 설명드리면,

위젯 설치 전후 받을 수 있는 모든 Intent Action 과 extra 에 대해 BroadcastReceiver 를 잘 풀어놓은 것이기 때문입니다.

이를 테면, ACTION_APPWIDGET_UPDATE action 은 onUpdate 함수에, ACTION_APPWIDGET_DELETED action 은  onDeleted 함수를 호출 하도록 되어 있습니다. 자세한 것은 AppWidgetProvder.java 소스 코드를 참조하세요.

 

class WidgetProvider : AppWidgetProvider() {
    override fun onUpdate(context: Context?, manager: AppWidgetManager?, widgetIds: IntArray?) {
        val remoteViews = RemoteViews(
            "com.tistory.develop_branch.widget",
            R.layout.widget_layout
        )
        widgetIds?.forEach {
            manager?.updateAppWidget(it, remoteViews)
        }
    }
	override fun onAppWidgetOptionsChanged(
		context: Context?, appWidgetManager: AppWidgetManager?,
		appWidgetId: Int, newOptions: Bundle?
	) {
	}

	override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {}

	override fun onEnabled(context: Context?) {}

	override fun onDisabled(context: Context?) {}

	override fun onRestored(
		context: Context?,
		oldWidgetIds: IntArray?,
		newWidgetIds: IntArray?
	) {
	}    
}

 

여기서 잠시 맨 처음 글에서 언급하였던 <receiver> 태그에 대해서 다시 이야기해 보죠.

<receiver android:name=".WidgetProvider" >
	<intent-filter>
	<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
	</intent-filter>
	<meta-data 
    	android:name="android.appwidget.provider"
    	android:resource="@xml/widget_info"/>
</receiver>

 

AndroidManifest.xml 파일에 기술해 놓았던 <intent-filter> 는 "android.appwidget.action.APPWIDGET_UPDATE" action 뿐이었습니다.

하지만, 알고보면 AppWidgetProvider 는 APPWIDGET_UPDATE 뿐만아니라, APPWIDGET_DELETED, APPWIDGET_ENABLED, APPWIDGET_DISABLED, APPWIDGET_OPTION_CHANGED, APPWIDGET_RESTORED action 도 받아서 처리하고 있습니다.

이는 시스템에서 두 가지 값 APPWIDGET_UPDATE action intent filter 와 "android.appwidget.provider" meta-data 를 가지고 있는 경우에 대해서 명시적인  Intent 를 보내주고 있다는 의미입니다.

다시 말하면, APPWIDGET_DELETED 를 intent-filter 로 추가하지 않아도, 해당 이벤트를 수신할 수 있고, 설령 추가하지 않는다하더라도 항상 이벤트를 수신하는데는 문제가 없다는 얘기입니다.

 

덧붙임 1.

AndroidManifest.xml 파일에서 android:exported=false 를 추가해놓을 경우, Intent 를 받지 못한다는 경우가 종종 있는 것 같습니다.

물론, 저는 겪어 보지 않았습니다만, 스택오버플로우에서 종종 볼 수 있는 질문입니다.

 

 

 

'Android Widget' 카테고리의 다른 글

안드로이드 위젯 개발하기 (4)  (0) 2020.07.27
안드로이드 위젯 개발하기 (2)  (0) 2020.03.10
안드로이드 위젯 개발하기 (1)  (0) 2020.03.09

이전 글 :  2020/03/09 - [Android/Widget] - 안드로이드 위젯 개발하기 (1)

 

이전 글에서는 가장 기초적인 내용에 대해서만 기술했습니다.
이 글에서는 위젯 개발에서 WidgetInfo 에 대해서만, 좀 더 깊숙히 들어가 보도록 하겠습니다.

이전 글에서 WidgetInfo 가 가지는 속성 값 중 가장 중요한 값은 minWidth 와 minHeight 였습니다.
안드로이드는 이 값을 기준으로 위젯의 크기가 1x1 사이즈인지 2x2 사이즈인지를 구별합니다.
기준이 되는 값은 아래와 같습니다. (출처 : 앱 위젯 디자인 가이드 라인)

셀 개수(열 또는 행) 사용 가능한 크기(dp)(minWidth 또는 minHeight)
1 40dp
2 110dp
3 180dp
4 250dp
n 70 × n − 30

이런 식으로 min 값만을 제공하는 이유는 단말마다 홈 크린에서 제공할 수 있는 1x1 위젯의 공간 크기가 제각각 다르기 때문입니다.
그래서, 시스템이 보장해줄 수 있는 최소 값을 제공하고, 그에 따라 위젯 레이아웃을 구성하도록 하는 것입니다.
1x1 위젯의 최소값은 40dp-40dp 이지만, 실제로 대부분의 안드로이드 단말들은 1개의 셀당 크기가 이보다 큰 것이 일반적입니다.

홈 화면에서 셀당 크기를 변경할 수도 있습니다.

 

그럼, 이제 각각의 속성 값에 대해서 좀 더 자세히 알아보도록 하겠습니다.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
	android:minWidth="40dp"
	android:minHeight="40dp"
	android:updatePeriodMillis="86400000"
	android:previewImage="@drawable/preview"
	android:initialLayout="@layout/example_appwidget"
	android:configure="com.example.android.ExampleAppWidgetConfigure"
	android:resizeMode="horizontal|vertical"
	/>

updatePeriodMillis
: 위젯은 매우 수동적인 녀석입니다. 시스템에서 어쩌다 한 번씩 '업데이트 하라구!!!' 라는 메시지를 주어야 그제서야 위젯을 업데이트 합니다. 이 값은 '업데이트 하라구!!!' 라는 메시지를 얼마나 자주 보내주는지에 대한 것입니다. 예제에도 있지만, 보통 24시간(=86400초) 정도로 업데이트 할 것을 예상하고 만든 것으로 보입니다. (아마도 구글 형님들은 그런 의도로 만들었을 거라는 제 추측입니다. )
위젯 업데이트가 배터리 소모에 상당한 영향을 준다는 얘기도 있었고, 개발자 문서에서는 30분내에는 두 번이상 메시지를 보내지 않는다고 기술되어 있습니다. 즉, 최소값이 30분이라는 것으로 판단됩니다만, 위젯 업데이트 시간에 대해 구현을 진행하다 보면 보통 이런 업데이트 주기에 상관없이 앱에서 위젯을 업데이트하기 때문에 값을 별로 신경쓰지 않게 됩니다.

initialLayout
: 초기 레이아웃 값입니다. 보통 AppWidgetManger 로 RemoteView 를 업데이트 해주면 별 상관없는 값이긴 합니다만, 부팅 직후 처럼 아직 업데이트가 진행되지 않은 상황에서 위젯에 "Problem loading widget" 와 같은 메시지가 표시되는 것을 방지해줍니다.
보통 실제 데이터와 거의 유사한 화면을 추가해 놓는데, (그렇게 해 놓는 것이 예쁘긴 합니다.) 이 초기 화면을 너무 실제와 비슷하면, 간혹 개발 중에 오류가 발생하더라도 실수로 지나치는 경우가 종종 발생합니다.

previewImage
: 설정하지 않을 경우, 이전 글에 포함되어 있는 그림 처럼 기본 런처 아이콘이 사용됩니다.

configure
: 위젯 설정 Activity 를 지정(?) 할 수 있습니다. 보통 위젯을 홈 화면에 설치하기 직전에 나오는 설정 화면입니다.
이 설정 Activity 에 대해서는 후속 글에서 더 이야기 할 것들이 있습니다.

resizeMode
: 위젯을 상하좌우로 늘릴 수 있는지 옵션을 설정해 주는 속성입니다.
위에서 기술했다 시피 최소값을 기준으로 위젯을 만들기는 하지만, 대부분 이 최소값보다는 큰 사이즈를 감안해서 위젯을 만들게 됩니다.
그런 관점에서 2x2 위젯을 4x4 로 늘린다고 크게 문제 없도록 상대적인 값으로 잘 디자인한다면, 사용자 입장에서 꽤나 괜찮은 옵션일 것입니다.

 

덧붙임 1.

WidgetInfo 의 minWidth, minHeight 값만으로는 부족함을 많이 느낄 때가 많습니다.

상대적인 레이아웃의 배치를 고려한다고 하더라도, 위젯의 크기가 실제로 어느 정도 되는것인지 가늠이 되지 않기 때문입니다.

fun getWidgetSize(context : Context, appWidgetId : Int){
	val manager = AppWidgetManager.getInstance(context)
	val options = manager.getAppWidgetOptions(appWidgetId)
	val minWidth = options.getInt("appWidgetMinWidth") // dp
	val minHeight = options.getInt("appWidgetMinHeight") // dp
	val maxWidth = options.getInt("appWidgetMaxWidth") // dp
	val maxHeight = options.getInt("appWidgetMaxHeight") // dp
}

코드상에서는 Context 와 appWidgetId(Int) 값만 있으면, 아래 코드를 통해 위젯의 실제 사이즈를 가늠해 볼 수 있습니다.

비록 실제적인 값은 아니지만, min/max 값으로 Preview 화면을 미리 가늠해 볼 수 있기 때문입니다.

위젯 개발 혹은 디자인이 까다로운 이유는 이런 레이아웃의 width, height 값을 가늠하기만 할 뿐, 특정하기가 어렵기 때문입니다.

 

덧붙임 2.

WidgetInfo 파일에서 minWidth/minHeight = 40dp 혹은 110dp 가 아니라 70dp 로 설정하면 어떻게 될까요?

어떤 단말에서는 1x1 으로 나올 수도 있지만, 어떤 단말에서는 2x2 으로 또 어떤 단말에서는 1x2 로 나올 수도 있습니다.

그렇기 때문에, 위 개발자 문서에서 가이드한 40/110/.../70xN-30 의 값은 NxM 위젯이라는 것을 정의하는 수준일 뿐,

실제 위젯 뷰의 사이즈를 확정하기도 어렵습니다.

'Android Widget' 카테고리의 다른 글

안드로이드 위젯 개발하기 (4)  (0) 2020.07.27
안드로이드 위젯 개발하기 (3)  (0) 2020.03.13
안드로이드 위젯 개발하기 (1)  (0) 2020.03.09

안드로이드 위젯 개발은 생각만큼 개발하기 쉬운 것은 아닙니다.

감히 안드로이드 개발을 하면서 가장 지저분한(?) 아이템 중 하나라 감히 말씀드리고 싶네요.

언뜻 보기에는 그렇게 대단해 보이지 않는 녀석같지만, 기능을 하나하나 추가하다 보면, 의외로 난관에 부딪히는 경우가 많습니다.

 

일단, 그런 예외적인 상황들에 앞서, 개발자 사이트에서 제공해 주는 문서를 한 번 읽어보겠습니다.

https://developer.android.com/guide/topics/appwidgets?hl=ko

 

앱 위젯 빌드  |  Android 개발자  |  Android Developers

앱 위젯은 다른 애플리케이션(예: 홈 화면)에 삽입되어 주기적인 업데이트를 받을 수 있는 소형 애플리케이션 뷰입니다. 이러한 뷰는 사용자 인터페이스에서 위젯이라고 하며 앱 위젯 공급자를 사용하여 게시할 수 있습니다.

developer.android.com

문서에서는 AppWidgetProviderInfo, AppWidgetProvider, 그리고, 초기 레이아웃 세 가지가 필요하다고 기술되어 있습니다.
이 중에서 가장 먼저 선행되어야하는 것은 WidgetInfo xml 파일과 APPWIDGET_UPDATE action 을 받을 수 있는 BroadcastReceiver 입니다.

 

1. AppWidetProviderInfo(Widget Info) 는 res/xml 디렉토리에 추가해주어야 하는 xml 파일입니다.

개발자 사이트 문서에는 더 많은 속성 값들과 설명들이 나열되어 있습니다. 이 중 가장 중요한 값은 아래에 기술된 minWidth, minHeight 값입니다. 이 두 개의 속성 값이 없을 경우, 위젯 추가 화면에 표시되지 않습니다.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider 
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:minWidth="40dp"
	android:minHeight="40dp">
</appwidget-provider>

 

2. 그리고, BroadcastReceiver 파일을 추가한 뒤, AndroidManifest.xml 파일에 정보를 추가해 줍니다.
여기까지 진행 하면 그림과 같이 기본 아이콘으로 설정된 1x1 위젯이 위젯 추가 화면에서 메뉴가 나오기 시작합니다.

class WidgetProvider : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
    }
}

 

 

<receiver android:name=".WidgetProvider" >
	<intent-filter>
	<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
	</intent-filter>
	<meta-data 
    	android:name="android.appwidget.provider"
    	android:resource="@xml/widget_info"/>
</receiver>
    

 

 

물론, 여기까지 추가할 경우, 실제 위젯을 홈 화면에 띄울 수 있는 것은 아닙니다.
코드만 놓고 보아도 표시할 기본 레이아웃이 지정되어 있지 않기 때문에, 위젯 추가 화면에 표시만 될 뿐, 추가할 수 있는 위젯은 없는 셈입니다.

개발자 문서와는 약간 다르게 구현한 이유는, 안드로이드라는 시스템이 위젯을 인식하기 위해서는 최소한 어떠한 값들이 있어야 하는가를 조금이나마 가늠해보기 위한 것입니다.
핵심은 AndroidManifest.xml 에 기술된 <receiver> 와 <meta-data> 태그 안의 resource 파일입니다.
또 이 resource 파일에서 가장 중요한 값은 minWidth 와 minHeight 라는 것입니다.

 

3. 마지막으로 BroadcastReceiver - onReceive() 에 AppWidgetManager 코드를 추가해 주시면, 홈스크린에 추가되는 위젯을 만들 수 있습니다.

class WidgetProvider : BroadcastReceiver() {
	override fun onReceive(context: Context?, intent: Intent?) {
		if(intent == null){
			return
		}

		val widgetManager = AppWidgetManager.getInstance(context)
		when (intent.action) {
			AppWidgetManager.ACTION_APPWIDGET_UPDATE -> {
				val remoteViews = RemoteViews(
					"com.tistory.develop_branch.widget",
					R.layout.widget_layout
				)
				intent.extras?.let {
					val appWidgetIds: IntArray? =
						intent.extras!!.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS)
					appWidgetIds?.forEach {
						widgetManager.updateAppWidget(it,remoteViews)
					}
				}
			}
		}
	}
}

 

여기까지 진행된 소스는 여기를 참고하세요.

 

2020/03/10 - [Android/Widget] - 안드로이드 위젯 개발하기 (2)

 

'Android Widget' 카테고리의 다른 글

안드로이드 위젯 개발하기 (4)  (0) 2020.07.27
안드로이드 위젯 개발하기 (3)  (0) 2020.03.13
안드로이드 위젯 개발하기 (2)  (0) 2020.03.10