[Android] 간단하게 만드는 주기적으로 업데이트하는 위젯 만들기

안드로이드의 위젯은 주기적으로 onUpdate를 호출 할 수 있습니다. 다음과 같이 설정할 경우 30분마다 한번씩 위젯을 갱신할 수 있습니다.

android:updatePeriodMillis=”1800000″

하지만 위의 방법은 구글측에서 제한을 두어 최소 30분 이하의 갱신을 수행할 수 없습니다. 이하의 값을 설정할 경우 자동으로 30분 갱신으로 등록이 됩니다. 0을 등록할 경우 슬립모드에 들어갔다가 화면이 켜질경우에 업데이트를 수행하게 됩니다.

이때문에 더 짧은 주기의 업데이트를 수행하기 위해서는 AlarmManager를 활용하여 수동으로 위젯을 업데이트 하는 방법밖에 없어 보입니다. 다행이도 안드로이드에는 다음과 같은 위젯을 업데이트 하기 위한 인텐트 필터가 존재합니다.

android.appwidget.action.APPWIDGET_UPDATE

저 인텐트를 주기적으로 호출해 주기만 하면 문제가 해결될 수 있을것 같군요. 위젯을 사용하기 위한 방법까지 포함해서 하나하나 정리해 보도록 하겠습니다.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="kr.pe.theeye.widget"
  android:versionCode="1"
  android:versionName="1.0">

  <uses-sdk android:minSdkVersion="7" />

  <application
  android:icon="@drawable/icon"
  android:label="@string/app_name">

  <receiver android:name="HelloWidgetProvider" android:label="위젯테스트">
    <intent-filter>
      <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data
      android:name="android.appwidget.provider"
      android:resource="@xml/hellowidgetproviderinfo" />
  </receiver>

  </application>
</manifest>

위에서 중요한 부분은 android:name에 AppWidgetProvider를 상속받은 클래스의 클래스명을 적어줘야 한다는 것입니다. android:label에는 위젯을 추가할때에 표시될 위젯의 이름이 표시됩니다. 메타데이터에는 위젯의 기본적인 정보가 적혀있는 XML의 위치를 정의 합니다.

hellowidgetproviderinfo.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/hellowidget_layout"
android:minWidth="220dp"
android:minHeight="72dp"/>

여기서는 위젯의 크기라던가 레이아웃XML파일의 위치를 정의할 수 있습니다. 레이아웃의 크기는 하나의 셀에 74픽셀을 사용하게 됩니다. 하지만 [여기]를 참고해 보면 위젯의 사이즈는 총 크기에서 2픽셀을 빼라고((number of cells * 74) – 2) 되어있군요.

hellowidget_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:background="#fff">
<TextView 
            android:id="@+id/widgettext"
   android:layout_width="fill_parent" 
   android:layout_height="wrap_content"
   android:gravity="center"
   android:text="@string/hello"/>
</LinearLayout>

위의 디자인이 제가 테스트로 사용하기 위한 위젯 디자인입니다. 단순하게 글짜만 바꿀 수 있도록 TextView하나를 사용하였습니다.

HelloWidgetProvider.java

/**
 * 위젯의 상태를 주기적으로 갱신하는 예제
 * 
 * @author Eye
 * @since 2011.04.01
 */
public class HelloWidgetProvider extends AppWidgetProvider
{
  private static final String TAG = "HelloWidgetProvider";
  private static final int WIDGET_UPDATE_INTERVAL = 5000;
  private static PendingIntent mSender;
  private static AlarmManager mManager;

  /* (non-Javadoc)
   * @see android.appwidget.AppWidgetProvider#onReceive(android.content.Context, android.content.Intent)
   */
  @Override
  public void onReceive(Context context, Intent intent)
  {
    super.onReceive(context, intent);

    String action = intent.getAction();
    // 위젯 업데이트 인텐트를 수신했을 때
    if(action.equals("android.appwidget.action.APPWIDGET_UPDATE"))
    {
      Log.w(TAG, "android.appwidget.action.APPWIDGET_UPDATE");
      removePreviousAlarm();

      long firstTime = System.currentTimeMillis() + WIDGET_UPDATE_INTERVAL;
      mSender = PendingIntent.getBroadcast(context, 0, intent, 0);
      mManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
      mManager.set(AlarmManager.RTC, firstTime, mSender);
    }
    // 위젯 제거 인텐트를 수신했을 때
    else if(action.equals("android.appwidget.action.APPWIDGET_DISABLED"))
    {
      Log.w(TAG, "android.appwidget.action.APPWIDGET_DISABLED");
      removePreviousAlarm();
    }
  }

  /* (non-Javadoc)
   * @see android.appwidget.AppWidgetProvider#onUpdate(android.content.Context, android.appwidget.AppWidgetManager, int[])
   */
  @Override
  public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
  {
    // 현재 클래스로 등록된 모든 위젯의 리스트를 가져옴
    appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, getClass()));
    super.onUpdate(context, appWidgetManager, appWidgetIds);

    final int N = appWidgetIds.length;
    for(int i = 0 ; i < N ; i++)
    {
      int appWidgetId = appWidgetIds[i];
      updateAppWidget(context, appWidgetManager, appWidgetId);

      Toast.makeText(context, "onUpdate(): [" + String.valueOf(i) + "] " + String.valueOf(appWidgetId), Toast.LENGTH_SHORT).show();
    }
  }

  /**
   * 위젯의 형태를 업데이트합니다.
   * 
   * @param context 컨텍스트
   * @param appWidgetManager 위젯 메니저
   * @param appWidgetId 업데이트할 위젯 아이디
   */
  public static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId)
  {
    Date now = new Date();
    RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.hellowidget_layout);
    updateViews.setTextViewText(R.id.widgettext, "[" + String.valueOf(appWidgetId) + "]" + now.toLocaleString());
    appWidgetManager.updateAppWidget(appWidgetId, updateViews);
  }

  /**
   * 예약되어있는 알람을 취소합니다.
   */
  public void removePreviousAlarm()
  {
    if(mManager != null && mSender != null)
    {
      mSender.cancel();
      mManager.cancel(mSender);
    }
  }
}

여기서 참고해볼점은 위젯이 등록될 때 알람을 등록하고 위젯을 삭제할때 알람을 취소시킨다는것입니다. 또한 다음의 코드를 이용하여 위젯의 숫자와 상관없이 동일하게 갱신할 수 있도록 할 수 있습니다.

int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, getClass()));

위의 코드를 사용하면 현재의 WidgetProvider를 사용한 모든 홈스크린에 등록된 위젯들의 ID를 얻어올 수 있습니다.
사용자 삽입 이미지
[샘플코드 다운로드]