連載
» 2009年09月28日 00時00分 UPDATE

Androidで動く携帯Javaアプリ作成入門(10):Androidのホーム画面に常駐するアプリを作るには (3/3)

[緒方聡,株式会社イーフロー]
前のページへ 1|2|3       

AppWidgetを指定した間隔ごとに実行するには

 AppWidgetを指定した間隔ごとに実行するには、android:updatePeriodMillisを用いる処理方法があります。サンプルの「SlideShow」というウィジェットがこの方法を用いています。

図8 指定期間ごとにイメージを再表示 図8 指定期間ごとにイメージを再表示

 ソースコードは、以下のようになっています。

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()メソッドが呼び出されるたびに、このクラスのインスタンスが再生成されるためです。

AppWidgetでサービスを用いるには

 通常は、コールバックメソッドではこういった処理は行わずに、サービスを起動して、サービス内で処理を行うようにします。サンプルの「WhatTimeIsItNow」というウィジェットがサービスを用いる最も簡単な例です。

図9 秒刻みのデジタル時計 図9 秒刻みのデジタル時計

 ソースコードは、以下のようになっています。

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秒刻みのデジタル時計を表示するウィジェットですが、こんなウィジェットを作るのはお勧めできません。

  1. 1秒ごとにonUpdate()メソッドが呼び出される
  2. バックグラウンドでも(ウィジェットが隠れていても)1秒ごとにonUpdate()メソッドが呼び出される
  3. ほかのアプリの動作まで重くなる
  4. 電池の消耗が激しい

 特に3.と4.は大きな問題です。

 グーグルが公開しているサンプルは、1日1回だけインターネットに「今日の言葉」を取りにいってそれを表示するウィジェットです。確かに1日1回ぐらいであれば、まったく問題にならないので、私もandroid:updatePeriodMillisを用いるのであれば、多くても1時間に1回ぐらいの更新頻度に収めるのがよいのではないかと思います。

 android:updatePeriodMillis以外の方法としてAlarmManagerを用いる方法もあるので、ここからは、AlarmManagerの使い方を説明します。

AppWidgetを指定した時間に実行するには

 android:updatePeriodMillisをAlarmManagerに置き換えるメリットはいくつかあります。

  • android:updatePeriodMillisと同じことができる(コーディングは必要)
  • android:updatePeriodMillisと異なり期間を調整可能(コーディングだから)

 要するに、設定ファイルに期間を指定すると、コーディングは必要ないけど細かい動作は変更できず、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.RTCAlarmManager.ELAPSED_REALTIMEのどちらかの定数を使用するのが望ましいです。これらを使用すると、デバイスをwake upしません。

 このサンプルでは1時間おきにしていますが、例えば真夜中は頻度を減らしたり、逆に真夜中だから頻度を増やしたり、ということが、自分でコーディングしているからこそ可能になります。

 なお、自分で自分にIntentを時刻指定で送信しているので、受信時に次の自分のためのIntentの準備を忘れないようにしてください。

 今回、この連載で初めて出てきたPendingIntentというものは、Intentを文字通りペンディングするための便利な入れ物です。次に説明するクリックを用いた処理方法でも、このPendingIntentを使用します。

コラム 「タイミングを見計らってIntentを発信するPendingIntentとは」

PendingIntentは、Intentをタイミングを見計らって発信する便利なツールです。以下のような使い方が可能です。

  • 時刻を指定してIntentを発信
  • イベントハンドラの代わりにIntentを発信

また、PendingIntentの作り方によって、Intentの飛び先をあらかじめ決められます。

メソッド 説明
getActivity(…) Activityを起動するPendingIntentの取得
getBroadcast(…) ブロードキャストを投げるPendingIntentの取得
getService(…) Serviceを起動するPendingIntentの取得
表3 PendingIntentのメソッド

処理対象があらかじめActivityやServiceに絞り込めている場合、これらを使用するのが効果的です。今回のサンプルではsetBroadcast()メソッドとsetService()メソッドを使用しているので、ソースコード参照して使い方を習得してください。


AppWidgetをクリック時に実行するには

 サンプルの「ClickSample」というウィジェットが、ウィジェットでクリックイベントを拾う例です。

図10 ウィジェットでクリックイベントを拾うサンプル 図10 ウィジェットでクリックイベントを拾うサンプル

 テキスト部分またはボタンをクリックすると、現在時刻を表示するサンプルです。

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を使う際の2つの注意点

 AppWidgetは開発者にとって制限があるものの、ホームスクリーンに常駐できるというメリットが開発者にとっても、エンドユーザーにとっても魅力的です。AppWidgetの設定画面をActivityで作成したり、PendingIntentを使用してActivityと連携させたり、サービスを使い捨てないようにしたりすることで、さらにウィジェットがパワフルになると思います。

 最後に、本記事執筆中に気が付いた点を紹介して終わりにしようと思います。

ウィジェット起動時に空きスペースがない場合

 ホームスクリーンにセルの空きがない状態でウィジェットを起動してしまったら、以下のようなメッセージは出ますが、その後表示がされないまま、ずっと裏で動いていることになります。

図11 空きスペースがない場合の処理 図11 空きスペースがない場合の処理

 この状態になると、エンドユーザーはこの表示されないウィジェットを削除できず、またプログラム的にも表示させるようにすることはできません。このメッセージが出るのは、最初のonUpdate()メソッドが呼び出される前なので、「プログラム的に事前にチェックする」ということもできません。端末を再起動しても表示されない状態で起動したままです。さらに悪いことに、エンドユーザーは表示されていない状態でウィジェットが起動していることに気が付かないでしょう。

 Android 1.5時点での、唯一この状態を打開する策は、該当のウィジェットをアンインストールすることだけです。

ほかのウィジェットの不具合の影響を受ける可能性がある

 例えば、「Foo」というウィジェットがonUpdate()メソッドでNullPointerExceptionなどの例外などを発生させてしまった場合、「Bar」というウィジェットの動作が不安定になってしまうことがあるようです。

 この場合、メッセージが出て、どのウィジェットに問題があるかはエンドユーザーが分かるかもしれないので(メッセージが不親切なので、推測しなければならない)、事態は空きスペースがないときより幾分ましです。

 開発時に自分のウィジェットで例外を発生させてしまった場合、やはりその後の開発に支障をきたしてしまうことがあります。そうなった場合は、いったん該当のウィジェットを削除して、エミュレータを再起動すれば修復します。


「Androidで動く携帯Javaアプリ作成入門」バックナンバー
前のページへ 1|2|3       

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。