デベロッパー

デベロッパー

Androidのホーム画面用App Widgetを作成する

Shane Conder
2009年10月30日 / 10:00
 
 

はじめに

 Androidユーザーが初めて体験したApp Widgetは、最初のAndroid携帯に搭載されていた時計や写真フレームなどの基本的なホーム画面コントロールであり、App Widgetは早い段階から利用されていました。しかし、App WidgetのAPIが開発者向けに公開されたのはつい最近のことです。このインターフェースは、従来の携帯電話アプリケーションの枠を超えるようなAndroidアプリケーションの機能を実現するための2つの新しい興味深い方法を開発者にもたらします。開発者はAndroid 1.5でリリースされたApp Widget APIを使用して、簡単なコントロールを作成し、これらのコントロールを表示して使用するための新しいApp Widgetホストを作成できます。

 この記事では、AlarmManagerインターフェースを使用して、ユーザーが設定した時間間隔で更新されるホーム画面用App Widgetを作成する方法について解説します。特に、一連の画像からランダムに選ばれた画像を表示するApp Widgetの作成方法について解説します。表示される画像はユーザーが設定した時間間隔で定期的に変化します。

 簡単なApp Widgetを作成するための手順は次のとおりです。

  1. App Widgetのユーザーインターフェースを提供するRemoteViewを作成します。
  2. AppWidgetProviderインターフェースを実装するActivityにRemoteViewを結合します。
  3. 主要なApp Widgetの設定情報をAndroidのマニフェストファイルに定義します。

App Widgetのプロジェクトの準備

 App Widgetは、基本的には特定のアクションを処理する単純なBroadcastReceiverです。AppWidgetProviderインターフェースは、このようなアクションの処理を単純化する目的で、次のメソッドを実装するためのフレームワークを提供します。

  • onEnabled():最初にApp Widgetが作成されるときに呼び出されます。グローバルな初期設定が必要な場合はここで行ってください。
  • onDisabled()onEnabled()メソッドの逆で、この定義で処理される最新のApp Widgetが削除されるときに呼び出されます。グローバルな後処理が必要な場合はここで行ってください。
  • onUpdate():App WidgetがViewを更新する必要があるときに呼び出されます。ユーザーが最初にウィジェットを作成するときに行われることがあります。
  • onDeleted():App Widgetの特定のインスタンスが削除されたときに呼び出されます。インスタンスの後処理はここで行ってください。
  • onReceive():このメソッドの既定の実装では、BroadcastReceiverのアクションを処理し、上記のメソッドを適切に呼び出します。
 また、App Widgetフレームワークを使用するにおいて、確認済みの欠陥が存在しているため、開発者は特定の問題について明示的に対処する必要があります。詳細については以下の注意を参照ください。

※注意
 App Widgetフレームワークの最新の欠陥に関する詳細情報は、このリンクを参照ください。リンク先のページには、上記の問題(この記事のダウンロード可能なコードでも発生)や、実際に存在しないのにApp Widget IDがonUpdate()メソッドに渡されるという事例に対処するためのコードが解説されています。
 AndroidシステムでApp Widgetを使用するには、標準の<receiver>タグをAndroidのマニフェストファイルに記述します。該当する部分の記述例を次に示します。

<receiver android:name="ImagesWidgetProvider">
   <intent-filter>
   <action
   android:name="android.appwidget.action.APPWIDGET_UPDATE" />
   </intent-filter>
   <meta-data
   android:name="android.appwidget.provider"
   android:resource="@xml/imageswidget_info" />
</receiver>
 通常の<receiver>の定義と異なり、<meta-data>セクションでXMLファイルリソースを参照していることに気付くでしょう。このファイルでは、App Widgetに関する追加データを定義しており、このデータはAppWidgetProviderInfoクラスに対応しています。ここに定義される情報はイミュータブル(immutable)のため、今回のサンプルではupdatePeriodMillisの値を指定しません。というのは、このアプリケーションでは更新間隔をユーザーが変更できるようにしますが、ここでupdatePeriodMillisに値を割り当ててしまうと、この機能を実現できなくなるからです。次のコードはimageswidget_info.xmlファイルの完全な内容を表しています。

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider 
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:minWidth="146dp"
   android:minHeight="146dp"
   android:initialLayout="@layout/widget"
   android:configure=
      "com.mamlambo.imageswidget.ImagesWidgetConfiguration" />
 <appwidget-provider>タグでは、App Widgetのサイズ、使用するデフォルトレイアウト、App Widgetのインスタンス作成時に起動する設定Activityを定義します。ウィジェットをホーム画面にうまく表示させるには、サイズに関する所定のガイドラインを遵守する必要があります。ホーム画面は決まった大きさのセルに分割されています。Googleが参考として提供している基本的な公式では、占有したいセルの数に74を掛けて2を引きます。今回作成するApp Widgetはセル2つ分の高さとセル2つ分の幅を持つ正方形にするので、高さまたは幅のサイズは((74*2)-2)、つまり146となります。

onUpdate()の実装

 何も表示されないApp Widgetはあまり実用的ではありません。幸い、今回作成するApp WidgetのRemoteViewオブジェクトの実装は簡単で、アプリケーションのdrawableリソースディレクトリに格納されている一連の画像を使用します。アプリケーションはこれらの画像リソースをR.drawable.[imagename]として参照します。今回のコードでは画像リソースの名前を格納する配列を作成することで、簡単にどれか1つの画像をランダムに描画できるようにします。次のコード例は、用意されている画像のうちの1つをランダムに描画するonUpdate()の実装を表しています。

@Override
public void onUpdate(Context context,
   AppWidgetManager appWidgetManager,
   int[] appWidgetIds) {
   for (int appWidgetId : appWidgetIds) {
      int imageNum = (new
         java.util.Random().nextInt(IMAGES.length));
      RemoteViews remoteView = new
         RemoteViews(context.getPackageName(),
         R.layout.widget);
      remoteView.setImageViewResource(
         R.id.image, IMAGES[imageNum]);
      appWidgetManager.updateAppWidget(
         appWidgetId, remoteView);
   }
}
 onUpdate()メソッドが最後のパラメータとしてApp Widgetインスタンスのリストを要求していることに注目してください。それぞれのインスタンスは別々に処理する必要があります。App Widgetフレームワークに存在する欠陥のため、提供されるインスタンスのなかには表示不能なものや使用不能なものもあるかもしれません。しかし、今回のサンプルではこの問題を無視しても構いません。今後皆さんが作成する実装では、どのApp Widgetが実際にアクティブなのかを追跡する必要があるかもしれないということを覚えておいてください。

 この記事のダウンロード可能なコードに含まれる簡単なR.layout.widget XMLのレイアウト定義をよく見ると分かりますが、これは基本的には単純なImageViewです。RemoteViewsは、一部の限られた種類のViewオブジェクト、例えばButton、ImageButton、ImageView、TextView、AnalogClock、Chronometer、ProgressBarだけを使用でき、しかもこれらはFrameLayout、LinearLayout、またはRelativeLayout内でのみ使用できます。RemoteViewのアクセスはsetImageViewResource()setTextViewText()のようなメソッドによって制御されるため、RemoteViewは簡素に保つようにしてください。RemoteViewは別のプロセス内にViewを描画することを目的としたものなので、通常のレイアウトよりもアプリケーションからの制御性が低くなっています。

 ここまでで、App Widgetの基本的な説明は完了です。ただし、ユーザーが画像の更新間隔を設定できるようにするには、設定Activityを実装し、RemoteViewの更新スケジュールについての処理を行う必要があります。

App Widgetの設定Activityの実装

 マニフェストファイルにImagesWidgetConfigurationとして定義されている設定Activityは、基本的には他のActivityと同様ですが、次の2つの例外があります。

  1. 起動すると結果が返されるようになっています。従って、実装する際は必ずsetResult()メソッドを呼び出し、適切な結果(RESULT_CANCELEDまたはRESULT_OK)を返す必要があります。
  2. 結果を設定するときには、AppWidgeManager.EXTRA_APPWIDGET_IDで参照される追加情報(extra)にApp Widget IDの値を記述する必要があります。
 次のコード例は、この2つの例外についての対応を示しています。ユーザーがActivityを取り消した場合は、setResult()を呼び出し、既定の結果をRESULT_CANCELEDとして設定しています。

Bundle extras = launchIntent.getExtras();
if (extras != null) {
   appWidgetId = extras.getInt(
      AppWidgetManager.EXTRA_APPWIDGET_ID,
      AppWidgetManager.INVALID_APPWIDGET_ID);
   Intent cancelResultValue = new Intent();
   cancelResultValue.putExtra(
      AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
   setResult(RESULT_CANCELED, cancelResultValue);
}
 この2点の制限を除けば、好きなように設定Activityを実装して構いません。図1は今回のサンプルで使用する単純な設定Activityを表しています。RESULT_CANCELEDが返された場合、App Widgetはユーザーに表示されません。RESULT_OKが返された場合は、App Widgetはユーザーに表示されます。個々のApp Widgetインスタンス用の設定データを保管するには、どの格納メカニズムを使っても構いません。この例では、SharedPreferencesインターフェースを使い、特定のApp Widget IDと一緒に更新間隔を保管します。完全な実装については、この記事のダウンロード可能なコードを参照ください。

図1 設定画面: この単純な設定画面で、ユーザーはApp Widgetの画像表示が変わる時間間隔を設定できる
図1 設定画面: この単純な設定画面で、ユーザーはApp Widgetの画像表示が変わる時間間隔を設定できる

更新スケジュールの実装

 前述したように、AppWidgetProviderInfoクラス内の設定値はイミュータブルです。updateTimeMillisの値はこのクラス内にあるため、この値セットを持つAppWidgetProviderInfoを使って作成されたApp Widgetインスタンスは、指定の頻度で更新されますが、その頻度を変更することはできません。今回のアプリケーションでは、それぞれのApp Widgetインスタンス内で画像が表示される頻度をユーザーに設定させるため、この機能を独自に実装する必要があります。

 AlarmManagerクラスは、この目的に使用できる非常に論理的な更新メカニズムで、繰り返し通知をサポートしています。この通知は単純なPendingIntentオブジェクトのトリガーによって実現されます。

 では、具体的にどのようにしたらよいのでしょうか。まず思いつくのはこんな方法でしょう。まず単純にAppWidgetManager.ACTION_APPWIDGET_UPDATEというaction値を持つIntentオブジェクトを作成し、extras値を特定のApp Widget IDに設定し、その後、AlarmManagerのsetRepeating()メソッドを呼び出して、更新を繰り返し行うようスケジュール設定するという方法です。しかし残念ながら、それではうまくいきません。Androidシステムはaction値とscheme値の両方が一致するIntentを再利用する仕組みになっており、「extras」の値は比較されません。これはつまり、それぞれのApp Widget IDに対応するIntentは実際には同じIntentであるという意味です。幸い、この問題は簡単に解決できます。App Widgetのscheme値を定義し、これを使用して一意のIntentインスタンスを定義すればよいのです。これを実現するコード例を次に示します。

Intent widgetUpdate = new Intent();
widgetUpdate.setAction(
   AppWidgetManager.ACTION_APPWIDGET_UPDATE);
widgetUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, 
   new int[] { appWidgetId });
// make this pending intent unique
widgetUpdate.setData(
   Uri.withAppendedPath(Uri.parse(
   ImagesWidgetProvider.URI_SCHEME + "://widget/id/"), 
   String.valueOf(appWidgetId)));
PendingIntent newPending = PendingIntent.getBroadcast(
    getApplicationContext(), 0, widgetUpdate, 
    PendingIntent.FLAG_UPDATE_CURRENT);
// now schedule it
AlarmManager alarms = (AlarmManager) getApplicationContext().
   getSystemService(Context.ALARM_SERVICE);
alarms.setRepeating(AlarmManager.ELAPSED_REALTIME, 
   SystemClock.elapsedRealtime(), updateRateSeconds * 1000, 
   newPending);
 この記述はImagesWidgetConfiguration Activityの実装コードに含まれています。マニフェストファイルには、この特定のscheme値を扱うための<receiver>ブロックが示されています。これに加えて、AppWidgetProviderインターフェースのonDeleted()メソッド内で繰り返し通知を停止する必要があるでしょう。また、携帯電話を再起動したときに更新スケジュールも同様に再開する必要があります。

 これに対する簡単なソリューションは、AppWidgetProviderのonReceive()メソッド内で、あるロジックを直接利用することです。まず、更新アクションかどうかをチェックします。更新の場合は、scheme値が含まれていないことを確認します。含まれていない場合は、AlarmManagerにスケジュール設定されているPendingIntentは、このアクションを起動しません。その場合は、各App Widget IDに対してプリファレンス値が設定されているかどうかを調べます。プリファレンス値が設定されていない場合は、この更新アクションはApp Widgetが設定される前に受信されたことが分かります。一方、プリファレンス値が設定されている場合は、PendingIntentをスケジュール設定できることが分かります。繰り返しになりますが、このロジック全体はonReceive()メソッド内で実装されています。

 図2に表すように、このスキーマを使用すると、同じ種類のApp Widgetsを複数同時に表示できます。更新頻度はさまざまに設定でき、それぞれに表示される画像は、用意された一連の画像ファイルの中からランダムに選ばれます。

図2 複数のWidgetインスタンス: この図は、同時に起動する2つの異なる最終的なApp Widgetのインスタンスを表す
図2 複数のWidgetインスタンス: この図は、同時に起動する2つの異なる最終的なApp Widgetのインスタンスを表す
 この記事では、Androidのプラットフォーム上で基本的なApp Widgetを作成する方法について解説してきました。ホーム画面に表示されるこのApp Widgetの各インスタンスは、それぞれユーザーが設定したスケジュールで個別に動作しますが、この機能はApp Widgetフレームワークから直接利用できるものではありません。今後の記事では、App Widgetの機能を拡張し、インターネットからダウンロードした画像を使用できるようにする方法や、画面上のインターフェースを使用して画像表示を直接制御できるようにする方法について解説していきたいと思います。

著者紹介

Shane Conder(Shane Conder)
 モバイル技術やウェブ技術を得意とするソフトウェア開発者(現在は小さなモバイルソフトウェア会社に勤務)。先日、Androidプログラミングに関する詳しい解説書『Android Wireless Application Development』を、同じく開発者であるLauren Darceyと共同執筆し、Addison-Wesley(ISBN: 0321627091)から刊行されている。詳しくは彼らのブログを参照のこと。
Lauren Darcey(Lauren Darcey)
 ソフトウェア制作の分野で約20年の経験を持ち、商用モバイルアプリケーションの開発を専門とする開発者。先日、Androidプログラミングに関する詳しい解説書『Android Wireless Application Development』を、同じく開発者であるShane Conderと共同執筆し、Addison-Wesley(ISBN: 0321627091)から刊行されている。詳しくは彼らのブログを参照のこと。
【関連記事】
Google、『Android 2.0』端末用 GPS アプリケーションを発表
Verizon が Motorola 製スマートフォン『DROID』の詳細を発表
Motorola の Android 端末、28日に発表予定―iPhone に対抗
グーグルが「Android 1.6」を発表〜日本でも有料アプリを購入可能に
ドコモ、Android ケータイ「HT-03A」のバージョンアップファイルを提供開始

New Topics

Special Ad

ウマいもの情報てんこ盛り「えん食べ」
ウマいもの情報てんこ盛り「えん食べ」 「えん食べ」は、エンジョイして食べる、エンターテイメントとして食べものを楽しむための、ニュース、コラム、レシピ、動画などを提供します。 てんこ盛りをエンジョイするのは こちらから

Hot Topics

IT Job

Interviews / Specials

Popular

Access Ranking

Partner Sites