AndroidのAppWidgetProviderについて

Androidのホーム画面上にウィジェット表示を提供するAppWidgetProviderについてまとめました。

 

1 AppWidgetProvider

ウィジェットを実装するためのクラスです。

AppWidgetProviderを拡張して独自のウィジェットを実装します。

1.1 Android StudioでAppWidgetProviderを追加する

Android Studioではガイドを利用することができます。

ファイルツリーで右クリックしてウィジェットを追加します。

0001_New_AppWidget.png

新規ウィジェットの設定画面が開きます。

ここでは名前をSampleAppWidgetにして、幅を4、高さを1に設定してます。

0002_Customize_AppWidget.png

ウィジェットのjavaソースコードとlayoutファイルが追加されました。

AndroidManifest.xmlにも設定が追加されています。

0003_FileList_AppWidget.png

インストールしてホーム画面に追加すると以下のようになりました。

0004_Run_AppWidget.png

1.2 AndroidManifest.xml

receiverの値にAppWidgetProviderを拡張したクラスを設定しています。

ガイドを使った場合はandroid:updatePeriodMillisの記述はありません。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hiroom2.sampleappwidget" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <receiver android:name=".SampleAppWidget"
                  android:updatePeriodMillis="0" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/sample_app_widget_info" />
        </receiver>
    </application>

</manifest>

1.3 AppWidgetProviderのメソッド

package com.hiroom2.sampleappwidget;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

/**
 * Implementation of App Widget functionality.
 */
public class SampleAppWidget extends AppWidgetProvider {

  @Override
  public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    // There may be multiple widgets active, so update all of them
    for (int appWidgetId : appWidgetIds) {
      updateAppWidget(context, appWidgetManager, appWidgetId);
    }
  }

  @Override
  public void onEnabled(Context context) {
    // Enter relevant functionality for when the first widget is created
  }

  @Override
  public void onDisabled(Context context) {
    // Enter relevant functionality for when the last widget is disabled
  }

  static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                              int appWidgetId) {
    CharSequence widgetText = context.getString(R.string.appwidget_text);
    // Construct the RemoteViews object
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.sample_app_widget);
    views.setTextViewText(R.id.appwidget_text, widgetText);

    // Instruct the widget manager to update the widget
    appWidgetManager.updateAppWidget(appWidgetId, views);
  }
}

各メソッドが実行されるタイミングは以下のとおりです。

 

メソッド 呼ばれるタイミング
onEnabled ウィジェットがホーム画面に追加された時、端末が
  起動した時に呼ばれます。
onDisabled ウィジェットがホーム画面から削除された時、
  端末がシャットダウンした時に呼ばれます。
onUpdate インスタンスが生成された時に、あるいはあらかじめ
  設定したandroid:updatePeriodMillisで呼ばれます。

 

1.4 android:updatePeriodMillisの最小値は30分

AndroidManifest.xmlでandroid:updatePeriodMillisを設定することで、onUpdateメソッドが周期的に呼ばれるようになります。

 

android:updatePeriodMillisの値を0にする、あるいは記述しない場合はonUpdateは周期的に呼ばれません。

ただし、端末機同時やウィジェット追加時にonUpdateは一度呼ばれます。これはウィジェットの初期化に使うことができます。

 

android:updatePeriodMillisの最小値は30分です(1800000)。つまり、android:updatePeriodMillisを設定してonUpdateメソッドによるウィジェットの更新は30分に1回が限界です。これは電力消費量を抑えるためです。

 

天気予報をインターネット経由で取得して表示するようなウィジェットは1日1回で十分なのでandroid:updatePeriodMillisを用いれば良いでしょう。

もっと頻繁に更新が必要なウィジェットはAlarmやThread等で更新します。

1.5 updateAppWidgetメソッド

ウィジェットを更新するメソッドです。

ホーム画面に表示されているウィジェットのに割り振られたIDを指定する方法とComponentNameにAppWidgetProviderを拡張したクラスを指定する方法があります。

 

AppWidgetProviderを拡張したクラスが提供するウィジェットを2つホーム画面に表示させた場合、2つのIDが存在することになります。

 

IDを指定したupdateAppWidgetメソッドは以下のとおりです。

public void updateAppWidget (int appWidgetId, RemoteViews views)
public void updateAppWidget (int[] appWidgetIds, RemoteViews views)

ComponentNameを用いた場合、AppWidgetが提供する全てのウィジェットを更新します。

public void updateAppWidget (ComponentName provider, RemoteViews views)

1.6 ウィジェットのIDについて

ホーム画面に複数ウィジェットを表示させた場合、onUpdateの引数int[] appWidgetIdsにIDがまとめられるとは限りません。

ホーム画面に割り当てた直後だと別々になっています。

つまりその時はAppWidgetProviderを拡張したクラスのインスタンスが2つ存在し、onUpdateが2回呼ばれることになります。

端末を再起動するとまとめられ、AppWidgetProviderを拡張したクラスのインスタンスは一つとなります。

 

このように、IDを意識する必要がある場合は実装が面倒になります。

2 ServiceでupdateAppWidgetを実行するコード

onUpdateでupdateAppWidgetを実行するServiceを起動します。

android:updatePeriodMillisを設定していないので、onUpdateは一度だけ呼ばれます。

しかし、先ほど述べたように新たにホーム画面にウィジェットを追加すると再起動までに別のインスタンスが生成される為、Serviceが起動済みかをチェックします。

package com.hiroom2.sampleappwidget;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;

/**
 * Implementation of App Widget functionality.
 */
public class SampleAppWidget extends AppWidgetProvider {

  private Thread mThread = null;

  @Override
  public void onUpdate(Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
    if (!SampleService.isStarted())
      context.startService(new Intent(context, SampleService.class));
  }

  @Override
  public void onEnabled(Context context) {
    // Do nothing.
  }

  @Override
  public void onDisabled(Context context) {
    // Do nothing.
  }
}

onStartCommandでComponentName経由でupdateAppWidgetを実行するThreadを生成します。ThreadではTextViewをスレッドが循環した回数を表示します。

package com.hiroom2.sampleappwidget;

import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.widget.RemoteViews;

public class SampleService extends Service {

  private static boolean gStarted = false;

  private AppWidgetManager mAppWidgetManager;
  private ComponentName mComponentName;
  private RemoteViews mRemoteViews;
  private Thread mThread;

  public static boolean isStarted() {
    return gStarted;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    mAppWidgetManager = AppWidgetManager.getInstance(this);
    mComponentName = new ComponentName(this, SampleAppWidget.class);
    mRemoteViews = new RemoteViews(getPackageName(), R.layout.sample_app_widget);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    mThread = new Thread(new Runnable() {
      @Override
      public void run() {
        for (int i = 0; ; ++i) {
          mRemoteViews.setTextViewText(R.id.appwidget_text, "" + i);
          mAppWidgetManager.updateAppWidget(mComponentName, mRemoteViews);
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    });
    mThread.start();
    gStarted = true;
    return super.onStartCommand(intent, flags, startId);
  }

  @Override
  public void onDestroy() {
    mThread.interrupt();
    mThread = null;
    super.onDestroy();
  }

  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
}

実行結果は以下のようになりました。ホーム画面にウィジェットが二つある場合は両方更新されます。

0005_updateAppWidget.png