AppWidgetを指定した間隔ごとに実行するには、android:updatePeriodMillisを用いる処理方法があります。サンプルの「SlideShow」というウィジェットがこの方法を用いています。
ソースコードは、以下のようになっています。
public class SlideShow extends AppWidgetProvider { static int index = 0; int[] images = { R.drawable.g0, R.drawable.g1, R.drawable.g2, R.drawable.g3, R.drawable.g4, R.drawable.g5, R.drawable.g6, R.drawable.g7, }; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.slide_show); remoteViews.setImageViewResource(R.id.ImageView01, images[index]); index = ++index % images.length; ComponentName thisWidget = new ComponentName(context, SlideShow.class); appWidgetManager.updateAppWidget(thisWidget, remoteViews); }
まず、必要なのがAppWidgetProviderクラスを継承して作らなければならないということです。そして、繰り返し呼び出されるonUpdate()メソッドをオーバーライドする必要があります。
やっていることは、onUpdate()メソッドが呼び出されるたびに、リソース内の画像をRemoteViesクラスを経由して順番に設定しています。画像を指定するindexがなぜstaticかというと、onUpdate()メソッドが呼び出されるたびに、このクラスのインスタンスが再生成されるためです。
通常は、コールバックメソッドではこういった処理は行わずに、サービスを起動して、サービス内で処理を行うようにします。サンプルの「WhatTimeIsItNow」というウィジェットがサービスを用いる最も簡単な例です。
ソースコードは、以下のようになっています。
public class WhatTimeIsItNow extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Intent intent = new Intent(context, MyService.class); context.startService(intent); } public static class MyService extends Service { @Override public void onStart(Intent intent, int startId) { RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.what_time_is_it_now); remoteViews.setTextViewText(R.id.TextView01, new Date().toLocaleString()); ComponentName thisWidget = new ComponentName(this, WhatTimeIsItNow.class); AppWidgetManager manager = AppWidgetManager.getInstance(this); manager.updateAppWidget(thisWidget, remoteViews); } @Override public IBinder onBind(Intent intent) { return null; } } }
onUpdate()メソッドはサービスを起動するだけ、起動されたサービスは実行後即終了します。処理が軽くても重くても、常にこのようにサービスを起動して処理をするのが望ましいです。このサービスはAndroidManifest.xmlに登録されていなければなりません。サービスの登録方法は、連載第7回の「サービスを使用するための設定」を参照してください。
さて、WhatTimeIsItNowは、ホームスクリーンに1秒刻みのデジタル時計を表示するウィジェットですが、こんなウィジェットを作るのはお勧めできません。
特に3.と4.は大きな問題です。
グーグルが公開しているサンプルは、1日1回だけインターネットに「今日の言葉」を取りにいってそれを表示するウィジェットです。確かに1日1回ぐらいであれば、まったく問題にならないので、私もandroid:updatePeriodMillisを用いるのであれば、多くても1時間に1回ぐらいの更新頻度に収めるのがよいのではないかと思います。
android:updatePeriodMillis以外の方法としてAlarmManagerを用いる方法もあるので、ここからは、AlarmManagerの使い方を説明します。
android:updatePeriodMillisをAlarmManagerに置き換えるメリットはいくつかあります。
要するに、設定ファイルに期間を指定すると、コーディングは必要ないけど細かい動作は変更できず、AlarmManagerを使用すると、コーディングは必要だけど細かく動作を設定できる、ということです。
サンプルの「AlarmManagerSample」というウィジェットが、AlarmManagerを使用する例です。このウィジェットは1時間に1度、0分0秒ちょうどに表示を更新する時報アプリです。
詳しくはソースファイル全体を見ていただくとして、ここではAlarmManagerを使用している個所にフォーカスして説明します。
private void setAlarm(Context context) { Intent alarmIntent = new Intent(context, AlarmManagerSample.class); alarmIntent.setAction(ACTION_START_MY_ALARM); PendingIntent operation = PendingIntent.getBroadcast(context, 0, alarmIntent, 0); AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); long now = System.currentTimeMillis() + 1; // + 1 は確実に未来時刻になるようにする保険 long oneHourAfter = now + interval - now % (interval); am.set(AlarmManager.RTC, oneHourAfter, operation); }
3行目のACTION_START_MY_ALARMは、自分で勝手に定義したアクションです。自分で送って自分で受け取るアクションなので、パッケージ名やクラス名を内部に含み、ユニークになるようにしています。
作成したIntentから「PendingIntent」を作成し、それをAlarmManagerに時刻とともに設定しています。AlarmManager#set(int, long, PendingIntent)メソッドの第1引数には、この場合はAlarmManager.RTCかAlarmManager.ELAPSED_REALTIMEのどちらかの定数を使用するのが望ましいです。これらを使用すると、デバイスをwake upしません。
このサンプルでは1時間おきにしていますが、例えば真夜中は頻度を減らしたり、逆に真夜中だから頻度を増やしたり、ということが、自分でコーディングしているからこそ可能になります。
なお、自分で自分にIntentを時刻指定で送信しているので、受信時に次の自分のためのIntentの準備を忘れないようにしてください。
今回、この連載で初めて出てきたPendingIntentというものは、Intentを文字通りペンディングするための便利な入れ物です。次に説明するクリックを用いた処理方法でも、このPendingIntentを使用します。
PendingIntentは、Intentをタイミングを見計らって発信する便利なツールです。以下のような使い方が可能です。
また、PendingIntentの作り方によって、Intentの飛び先をあらかじめ決められます。
メソッド | 説明 |
---|---|
getActivity(…) | Activityを起動するPendingIntentの取得 |
getBroadcast(…) | ブロードキャストを投げるPendingIntentの取得 |
getService(…) | Serviceを起動するPendingIntentの取得 |
表3 PendingIntentのメソッド |
処理対象があらかじめActivityやServiceに絞り込めている場合、これらを使用するのが効果的です。今回のサンプルではsetBroadcast()メソッドとsetService()メソッドを使用しているので、ソースコード参照して使い方を習得してください。
サンプルの「ClickSample」というウィジェットが、ウィジェットでクリックイベントを拾う例です。
テキスト部分またはボタンをクリックすると、現在時刻を表示するサンプルです。
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.click_sample); Intent clickIntent = new Intent(); clickIntent.setAction(ACTION_MY_CLICK); PendingIntent pendingIntent = PendingIntent.getService(this, 0, clickIntent, 0); remoteViews.setOnClickPendingIntent(R.id.TextView01, pendingIntent); remoteViews.setOnClickPendingIntent(R.id.Button01, pendingIntent);
上記コードは、RemoteViews経由でGUIコンポーネントにPendingIntentを設定している個所です。
まず、RemoteViewsを作成し、次にIntentを作成します。作成したIntentにはアクションを設定していますが、これはサービスでフィルタするためにユニークな文字列が必要です。パッケージ名やクラス名を組み合わせるのがよいでしょう。この文字列の内容でAndroidManifest.xmlにもフィルタを設定します。
次に、PendingIntentを作成します。作成したPendingIntentを、任意のGUIコンポーネントに設定すれば完了です。
これだけで、設定したGUIコンポーネントをクリックすれば、サービスにIntentが飛んできて、Service#onStart()メソッドが呼び出されます。
AppWidgetは開発者にとって制限があるものの、ホームスクリーンに常駐できるというメリットが開発者にとっても、エンドユーザーにとっても魅力的です。AppWidgetの設定画面をActivityで作成したり、PendingIntentを使用してActivityと連携させたり、サービスを使い捨てないようにしたりすることで、さらにウィジェットがパワフルになると思います。
最後に、本記事執筆中に気が付いた点を紹介して終わりにしようと思います。
ホームスクリーンにセルの空きがない状態でウィジェットを起動してしまったら、以下のようなメッセージは出ますが、その後表示がされないまま、ずっと裏で動いていることになります。
この状態になると、エンドユーザーはこの表示されないウィジェットを削除できず、またプログラム的にも表示させるようにすることはできません。このメッセージが出るのは、最初のonUpdate()メソッドが呼び出される前なので、「プログラム的に事前にチェックする」ということもできません。端末を再起動しても表示されない状態で起動したままです。さらに悪いことに、エンドユーザーは表示されていない状態でウィジェットが起動していることに気が付かないでしょう。
Android 1.5時点での、唯一この状態を打開する策は、該当のウィジェットをアンインストールすることだけです。
例えば、「Foo」というウィジェットがonUpdate()メソッドでNullPointerExceptionなどの例外などを発生させてしまった場合、「Bar」というウィジェットの動作が不安定になってしまうことがあるようです。
この場合、メッセージが出て、どのウィジェットに問題があるかはエンドユーザーが分かるかもしれないので(メッセージが不親切なので、推測しなければならない)、事態は空きスペースがないときより幾分ましです。
開発時に自分のウィジェットで例外を発生させてしまった場合、やはりその後の開発に支障をきたしてしまうことがあります。そうなった場合は、いったん該当のウィジェットを削除して、エミュレータを再起動すれば修復します。
Copyright © ITmedia, Inc. All Rights Reserved.