アプリ ウィジェット ホストを作成する

ほとんどの Android デバイスで利用できる Android のホーム画面で、ユーザーはアプリ ウィジェットを埋め込んでコンテンツにすばやくアクセスできます。ホーム画面に代わるアプリやこれに類するアプリを開発する場合も、AppWidgetHost を実装することで、ユーザーはアプリ ウィジェットを埋め込むことができるようになります。ほとんどのアプリでホストが必要になることはありませんが、独自のホストを作成する場合は、ホストが暗黙的に同意することになる契約上の義務を理解することが重要です。

このドキュメントでは、カスタムの AppWidgetHost を実装することに伴う責任に重点を置いて説明します。AppWidgetHost の実装方法を示す例としては、Android ホーム画面のランチャーのソースコードをご覧ください。

カスタムの AppWidgetHost の実装に関連する主なクラスと概念の概要は以下のとおりです。

  • アプリ ウィジェット ホスト - AppWidgetHost はホーム画面など、UI にアプリ ウィジェットを埋め込もうとするアプリのために、AppWidget サービスとのインタラクションを提供します。AppWidgetHost はホスト自身のパッケージ内で一意の ID を持つ必要があります。この ID は、ホストのすべてのユーザー間で保持されます。ID は通常、アプリ内で割り当てるハードコードされた値です。
  • アプリ ウィジェット ID - 各アプリ ウィジェット インスタンスに、バインド時に一意の ID が割り当てられます(bindAppWidgetIdIfAllowed() を参照、詳しくはアプリ ウィジェットをバインドするで説明)。一意の ID はホストが allocateAppWidgetId() を使用して取得します。この ID はウィジェットの存続期間中、つまりホストから削除されるまで保持されます。ホスト固有の状態(ウィジェットのサイズや場所など)は、ホスティング パッケージによって保持され、アプリ ウィジェット ID に関連付けられる必要があります。
  • アプリ ウィジェット ホスト ビュー - AppWidgetHostView は、表示する必要のあるときにウィジェットをラップするフレームと考えることができます。ウィジェットがホストによってインフレートされるたびにアプリ ウィジェットが AppWidgetHostView に割り当てられます。
  • オプション バンドル - AppWidgetHost は、オプション バンドルを使用して、ウィジェットの表示形態(サイズ範囲、ロック画面またはホーム画面に表示するかどうかなど)に関する情報を AppWidgetProvider に伝達します。この情報によって AppWidgetProvider は、表示の形態と場所に応じてウィジェットのコンテンツや外観をカスタマイズできるようになります。アプリ ウィジェットのバンドルを変更するには updateAppWidgetOptions()updateAppWidgetSize() を使用します。どちらのメソッドも、AppWidgetProvider へのコールバックを起動します。

アプリ ウィジェットをバインドする

ユーザーがアプリ ウィジェットをホストに追加すると、「バインディング」と呼ばれるプロセスが発生します。バインディングとは、特定のアプリ ウィジェット ID を特定のホストと特定の AppWidgetProvider に関連付けることです。バインディングには、アプリを実行している Android のバージョンによって、さまざまな方法があります。

Android 4.0 以前でアプリ ウィジェットをバインドする

Android 4.0 以前を搭載したデバイスでは、ユーザーがウィジェットを選択できるシステム アクティビティを通じてアプリ ウィジェットを追加します。ここで暗黙的に権限チェックが行われます。具体的には、ユーザーはアプリ ウィジェットを追加することで、アプリ ウィジェットをホストに追加する権限をアプリに暗黙的に付与することになります。当初のランチャーから、この手法の例を下に示します。このスニペットでは、ユーザーの操作を受けてイベント ハンドラがリクエスト コード REQUEST_PICK_APPWIDGET を使用して startActivityForResult() を起動します。

Kotlin

val REQUEST_CREATE_APPWIDGET = 5
val REQUEST_PICK_APPWIDGET = 9
...
override fun onClick(dialog: DialogInterface?, which: Int) {
    when (which) {
        ...
        AddAdapter.ITEM_APPWIDGET -> {
            ...
            val appWidgetId: Int = appWidgetHost.allocateAppWidgetId()
            val pickIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK).apply {
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
            }
            ...
            startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET)
        }
        ...
    }
}

Java

private static final int REQUEST_CREATE_APPWIDGET = 5;
private static final int REQUEST_PICK_APPWIDGET = 9;
...
public void onClick(DialogInterface dialog, int which) {
    switch (which) {
    ...
        case AddAdapter.ITEM_APPWIDGET: {
            ...
            int appWidgetId =
                    Launcher.this.appWidgetHost.allocateAppWidgetId();
            Intent pickIntent =
                    new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
            pickIntent.putExtra
                    (AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            ...
            startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
            break;
    }
    ...
}

システム アクティビティが終了すると、ユーザーが選択したアプリ ウィジェットがアクティビティに返されます。以下の例では、アクティビティがアプリ ウィジェットを追加する addAppWidget() を呼び出して応答します。

Kotlin

class Launcher : Activity(), View.OnClickListener, View.OnLongClickListener {
    ...
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        waitingFroResult = false

        if (resultCode == RESULT_OK && addItemCellInfo != null) {
            when (requestCode) {
                ...
                REQUEST_PICK_APPWIDGET -> addAppWidget(data)
                REQUEST_CREATE_APPWIDGET ->
                    completeAddAppWidget(data, addItemCellInfo, !desktopLocked)
                ...
            }
        }
        ...
    }
}

Java

public final class Launcher extends Activity
        implements View.OnClickListener, OnLongClickListener {
    ...
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        waitingForResult = false;

        if (resultCode == RESULT_OK && addItemCellInfo != null) {
            switch (requestCode) {
                ...
                case REQUEST_PICK_APPWIDGET:
                    addAppWidget(data);
                    break;
                case REQUEST_CREATE_APPWIDGET:
                    completeAddAppWidget(data, addItemCellInfo, !desktopLocked);
                    break;
                }
        }
        ...
    }
}

addAppWidget() メソッドは、アプリ ウィジェットを追加する前に構成を行う必要があるかどうかを確認します。

Kotlin

fun addAppWidget(data: Intent?) {
    if (data != null) {
        val appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)

        val customWidget = data.getStringExtra(EXTRA_CUSTOM_WIDGET)
        val appWidget: AppWidgetProviderInfo? = appWidgetManager.getAppWidgetInfo(appWidgetId)

        appWidget?.configure?.apply {
            // Launch over to configure widget, if needed.
            val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE)
            intent.component = this
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
            startActivityForResult(intent, REQUEST_CREATE_APPWIDGET)
        } ?: run {
            // Otherwise, finish adding the widget.
        }
    }
}

Java

void addAppWidget(Intent data) {
    int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);

    String customWidget = data.getStringExtra(EXTRA_CUSTOM_WIDGET);
    AppWidgetProviderInfo appWidget =
            appWidgetManager.getAppWidgetInfo(appWidgetId);

    if (appWidget.configure != null) {
        // Launch over to configure widget, if needed.
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
        intent.setComponent(appWidget.configure);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        startActivityForResult(intent, REQUEST_CREATE_APPWIDGET);
    } else {
        // Otherwise, finish adding the widget.
    }
}

構成について詳しくは、アプリ ウィジェットの構成アクティビティを作成するをご覧ください。

アプリ ウィジェットの準備ができたら、次は実際にアプリ ウィジェットをワークスペースに追加する作業を行います。当初のランチャーでは、この作業を行う際に completeAddAppWidget() というメソッドを使用します。

Android 4.1 以降でアプリ ウィジェットをバインドする

Android 4.1 には、バインディング プロセスを効率化するための API が追加されています。 また、これらの API により、ホストがバインディングのためのカスタム UI を提供することもできます。この効率化されたプロセスを使用するには、アプリのマニフェストで BIND_APPWIDGET 権限を宣言する必要があります。

<uses-permission android:name="android.permission.BIND_APPWIDGET" />

ただしこれで終わりではありません。ユーザーは実行時に、アプリ ウィジェットをホストに追加する権限を、アプリに対して明示的に付与する必要があります。アプリにアプリ ウィジェットを追加する権限があるかどうかを確認するには、bindAppWidgetIdIfAllowed() メソッドを使用します。bindAppWidgetIdIfAllowed()false が返される場合は、アプリでユーザーに権限を付与することを求めるダイアログを表示する必要があります(「許可」または今後のアプリ ウィジェットのすべての追加に適用する場合は「常に許可」)。以下のスニペットは、このダイアログの表示方法に関する例です。

Kotlin

val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName)
    // This is the options bundle discussed above
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options)
}
startActivityForResult(intent, REQUEST_BIND_APPWIDGET)

Java

Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
// This is the options bundle discussed above
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
startActivityForResult(intent, REQUEST_BIND_APPWIDGET);

ホストは、ユーザーが構成を必要とするアプリ ウィジェットを追加したかどうかも確認する必要があります。このトピックについて詳しくは、アプリ ウィジェットの構成アクティビティを作成するをご覧ください。

ホストの責任

ウィジェットのデベロッパーは、AppWidgetProviderInfo メタデータを使用して、ウィジェットのさまざまな構成項目を指定できます。こうした構成オプションについて詳しくは以下で説明しますが、ホストはこれをウィジェット プロバイダに関連付けられた AppWidgetProviderInfo オブジェクトから取得できます。

対象とする Android のバージョンに関係なく、すべてのホストに以下の責任があります。

  • ウィジェットを追加する際に、前述のようにウィジェット ID を割り当てる必要があります。 ウィジェットがホストから削除されたときにも、deleteAppWidgetId() を呼び出してウィジェット ID の割り当てを解除する必要があります。
  • 構成アクティビティからアプリ ウィジェットを更新するで説明されているように、ウィジェットを追加する際は必ずウィジェットの構成アクティビティ(存在する場合)を起動するようにしてください。これは、多くのアプリ ウィジェットで適切な表示のために必要なステップです。
  • すべてのアプリ ウィジェットで、AppWidgetProviderInfo メタデータの定義に沿って(android:minWidthandroid:minHeight を使用して)幅と高さの最小値を dp で指定します。ウィジェットがここで指定した dp 以上でレイアウトされるようにします。たとえば、多くのホストでは、アイコンやウィジェットをグリッド形式で配置します。この場合、デフォルトではホストは minWidthminHeight の制約を満たす最小の数のセルを使用してアプリ ウィジェットを追加する必要があります。

上記の要件に加えて、特定のプラットフォーム バージョンでホストに新しい責任を課す機能が導入されています。

対象とするバージョン

ホストの実装方法は、対象とする Android のバージョンによって異なります。このセクションで説明する機能の多くは、3.0 以降で導入されたものです。次に例を示します。

  • Android 3.0(API レベル 11)では、ウィジェットの自動進行動作が導入されました。
  • Android 3.1(API レベル 12)では、ウィジェットのサイズ変更機能が導入されました。
  • Android 4.0(API レベル 15)では、ホストにパディングを管理する責任を課すパディング ポリシーが変更されました。
  • Android 4.1(API レベル 16)では、ウィジェットのインスタンスがホストされる環境に関する詳細情報をウィジェット プロバイダが取得できるようにする API が追加されました。
  • Android 4.2(API レベル 17)では、オプション バンドルと bindAppWidgetIdIfAllowed() メソッドが導入されました。また、ロック画面ウィジェットも導入されました。

以前のデバイスを対象とする場合は、例として当初のランチャーを参照してください。

以降のセクションで、ホストに新しい責任を課す機能についてさらに詳しく説明します。

Android 3.0

Android 3.0(API レベル 11)では、ウィジェットで autoAdvanceViewId() を指定する機能が導入されました。 このビュー ID は、Advanceable のインスタンス(StackViewAdapterViewFlipper など)を指すように設定する必要があります。これは、このビューでホストは自身が適切と判断する間隔で advance() を呼び出す必要があることを示します。ここでホストはウィジェットを進行させるのが適切かどうかを考慮します。たとえば、別のページに表示されている場合や画面がオフになっている場合には、ウィジェットを進行させるのが妥当ではないとの判断をすることが考えられます。

Android 3.1

Android 3.1(API レベル 12)では、ウィジェットのサイズ変更機能が導入されました。 ウィジェットは、AppWidgetProviderInfo メタデータ内の android:resizeMode 属性を使用してサイズ変更が可能であることを指定し、縦または横方向、あるいは両方のサイズ変更の可否を指定できます。Android 4.0(API レベル 14)では、ウィジェットで android:minResizeWidth または android:minResizeHeight、あるいは両方を指定することもできるようになりました。

ウィジェットで指定されたようにウィジェットの縦または横方向、あるいは両方向のサイズ変更ができるようにすることはホストの責任です。サイズ変更可能として指定されたウィジェットは、任意のサイズに拡大できますが、縮小する場合は android:minResizeWidthandroid:minResizeHeight で指定された値を下回らないようにする必要があります。実装例については、Launcher2AppWidgetResizeFrame をご覧ください。

Android 4.0

Android 4.0(API レベル 15)では、ホストにパディングを管理する責任を課すパディング ポリシーが変更されました。4.0 以降、アプリ ウィジェットには独自のパディングが含まれなくなりました。代わりに、現在の画面の特性に基づいてウィジェットごとにパディングが追加されます。その結果、グリッド形式でウィジェットを一様に一貫性のある形で表示できるようになります。 アプリ ウィジェットをホストするアプリで使用できるよう、このプラットフォームには getDefaultPaddingForWidget() メソッドが用意されています。アプリはこのメソッドを呼び出してシステムが定義したパディングを取得し、これをウィジェットに割り当てるセルの数を計算するときに考慮に入れることができます。

Android 4.1

Android 4.1(API レベル 16)では、ウィジェットのインスタンスがホストされる環境に関する詳細情報をウィジェット プロバイダが取得できるようにする API が追加されました。具体的には、ウィジェットが表示されるサイズをホストからウィジェット プロバイダに伝えます。このサイズ情報を提供することはホストの責任です。

ホストはこの情報を updateAppWidgetSize() を介して伝えます。サイズについては、幅と高さの最小値と最大値を dp で指定します。 固定サイズではなく範囲を指定するのは、ウィジェットの幅と高さは向きによって変わることがあるためです。向きが変わるたびにホストがすべてのウィジェットを更新しなければならなくなると、システムに深刻な遅延が発生しかねないため、避けることが望ましいでしょう。これらの値は、ウィジェットが配置されたときに 1 回、以後ウィジェットのサイズが変更されたとき、1 回の起動中にランチャーが最初にウィジェットをインフレートしたとき(値は次回の起動時までは保持されないため)に更新する必要があります。

Android 4.2

Android 4.2(API レベル 17)では、バインド時にオプション バンドルを指定する機能が導入されました。最初の更新時に AppWidgetProvider に対してオプション データへのアクセス権が即時に付与されることから、これはアプリ ウィジェットのオプション(サイズなど)を指定する方法としては理想的です。これには bindAppWidgetIdIfAllowed() メソッドを使用します。このトピックについて詳しくは、アプリ ウィジェットをバインドするをご覧ください。

Android 4.2 では、ロック画面ウィジェットも導入されました。ウィジェットをロック画面でホストする場合、ホストはアプリ ウィジェットのオプション バンドル内でこの情報を指定する必要があります(AppWidgetProvider がこの情報を使用して、ウィジェットのスタイルを適切に設定できるようになります)。ウィジェットをロック画面ウィジェットとして指定するには、updateAppWidgetOptions() を使用し、フィールド OPTION_APPWIDGET_HOST_CATEGORY を追加して値を WIDGET_CATEGORY_KEYGUARD に設定します。このオプションはデフォルトで WIDGET_CATEGORY_HOME_SCREEN となるため、ホーム画面ホストについては明示的に設定する必要はありません。

ホストがアプリにとって適切なアプリ ウィジェットのみを追加するようにします。たとえば、ホストがホーム画面である場合は、AppWidgetProviderInfo メタデータ内の android:widgetCategory 属性にフラグ WIDGET_CATEGORY_HOME_SCREEN が含まれるようにします。同様に、ロック画面では、フィールドに WIDGET_CATEGORY_KEYGUARD フラグが含まれるようにします。このトピックについて詳しくは、アプリ ウィジェットをロック画面で有効にするをご覧ください。