こんにちは。
何について困っているのか
AndroidのWidgetだけ開発してみた或る日のことでございます。できあがったウィジェットを自分のAndroid 6.0の端末にインストールしてご満悦。
ところがふと「そういえば、Android 8.0変更点:マニフェスト ファイルからブロードキャスト レシーバーを削除せよってあったなぁ」と思い出し、さすればいかなることにぞと、作ったウィジェットをAndroid 8.0エミュレーターにインストールしてみたのでございます。
残念。動かず。
その理由は分かっている
私の作ったウィジェットは、こういう要件があって、こういう実装をしているから、Oreoで動かないのは、うすうす気づいていたんです。要は、この記事は、以下の2本の私のQiita記事の続編です。
要件
ウィジェットはこんなかんじのです。
このブラウザのアイコンをクリックしたら、ブラウザアプリを起動させます。
あと、このブラウザのアイコン以外のところをクリックしたら、DBから「申込対象〇月分」と「Web受付期間」を再検索して表示したいです。
開発環境は以下の通りです。Javaオンリーです。
Android Studio 3.0.1
Build #AI-171.4443003, built on November 10, 2017
JRE: 1.8.0_152-release-915-b01 amd64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
実装
ウィジェットのプログラム(AppWidgetProviderのサブクラス)は以下のように実装しました。
public class NewAppWidget extends AppWidgetProvider {
private static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
// まずこれが大事!
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
// 自分自身に向けたIntentとなる
Intent intent = new Intent("jp.co.casareal.toslovewidget.action.BROWSER");
// ブロードキャストIntentに仕立て上げる
PendingIntent pi = PendingIntent.getBroadcast(context, appWidgetId, intent , PendingIntent.FLAG_UPDATE_CURRENT);
// <ImageView>のandroid:id属性を宛に、クリックイベントの設定
views.setOnClickPendingIntent(R.id.browser, pi);
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent); // これがないとダメっぽい
switch (intent.getAction()) {
case "jp.co.casareal.toslovewidget.action.BROWSER": // ブラウザー起動
Uri uri = Uri.parse("http://www.its-kenpo.or.jp/");
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
// Activity以外からstartActivityするときにはこれをしないといけない
browserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(browserIntent);
break;
}
}
// 後略
もちろん、マニフェストにこの独自Action"jp.co.casareal.toslovewidget.action.BROWSER"
は以下のように登録しています。
<receiver
android:name=".TosloveWidget"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="jp.co.casareal.toslovewidget.action.BROWSER" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/toslove_widget_info" />
</receiver>
表示はする、けどクリックしても反応なし
LogCatを見ると、
BroadcastQueue: Background execution not allowed
という旨のWarningログが出力されます。アプリ(ウィジェット)は落ちはしませんが、上述の私の作ったウィジェットの、ブラウザのアイコンをクリックしても(処理してくれないので)反応しません。
対策を講じて、Oreoでも動くようになった!
結論を言えば、Oreoでも動くように改造できました。
マニフェストから独自Actionを除去
Oreoですから、まずはマニフェストから以下の1文を泣く泣く削除します。
<action android:name="jp.co.casareal.toslovewidget.action.BROWSER" />
あ、でも、ウィジェットそのものの登録(要素)は残しておきます。
ウィジェットクラス
前記事のそれを大幅に書き換えました。
public class TosloveWidget extends AppWidgetProvider {
/**
* ITS保養施設申込受付期間
*/
public static final String URL_ITS_TOSLOVE_MOUSHIKOMI = "http://www.its-kenpo.or.jp/shisetsu/hoyou/chokuei/moushikomi.html";
/**
* DBから「申込対象〇月分」と「Web受付期間」を再検索して表示Action(この独自Actionは、マニフェストには登録しないこと)
*/
public static final String ACTION_UPDATE = "jp.co.casareal.toslovewidget.action.UPDATE";
// 複数のメソッドでも使用するのでここで宣言
private RemoteViews views;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
// Construct the RemoteViews object ウィジェットレイアウトの初期化
views = new RemoteViews(context.getPackageName(), R.layout.toslove_widget);
// ▼ クリックイベントを登録 -------------------------------------------- ▼
// (1)ブラウザー起動
Uri uri = Uri.parse(URL_ITS_TOSLOVE_MOUSHIKOMI);
Intent i1 = new Intent(Intent.ACTION_VIEW, uri);
PendingIntent p1 = PendingIntent.getActivity(context, appWidgetId, i1, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.browser, p1);
// (2)DBから「申込対象〇月分」と「Web受付期間」を再検索して表示
Intent i2 = new Intent(context, getClass());
i2.setAction(ACTION_UPDATE);
PendingIntent p2 = PendingIntent.getBroadcast(context, appWidgetId, i2, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.layout_main, p2);
// ▲ クリックイベントを登録 -------------------------------------------- ▲
// DBから「申込対象〇月分」と「Web受付期間」を再検索して表示
update(context);
}
}
/**
* クリックイベントのブロードキャストIntentを受信
*
* @param context
* @param intent
*/
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
switch (intent.getAction()) {
case Intent.ACTION_BOOT_COMPLETED: // ブート起動時(このActionはホワイトリスト)
case ACTION_UPDATE: // ITSサイトからデータ再取得
update(context);
break;
}
}
/**
* DBから「申込対象〇月分」と「Web受付期間」を再検索して表示
*/
private void update(Context context) {
// RemoteViewsオブジェクトを操作したりする処理など
}
}
修正点1:クリックしたらブラウザを起動させたい
前回のときは、回りくどいことをしていました。それは、こういう手順を踏んでいたからです。
- 独自ActionのIntentを、ブロードキャスト。
- それをレシーヴするべくのonReceiveメソッド内で、
Intent i1 = new Intent(Intent.ACTION_VIEW, uri);
でブラウザ起動をさせていた。
修正点2:クリックしたらなにかしらの処理をしたい
『「申込対象〇月分」と「Web受付期間」を再検索して表示』をさせるためのクリックイベントについてです。
ここは、自クラス内だけで完結するブロードキャストインテントの送受信にするべく、Intent i2 = new Intent(context, getClass());
としました。
修正点3:固執していたことを、捨てた
Android Studioのウィジェット作成ウィザードでお膳立てしてくれるAppWidgetProviderサブクラスには、以下の通りです。
public class NewAppWidget extends AppWidgetProvider {
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.new_app_widget);
views.setTextViewText(R.id.appwidget_text, widgetText);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@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);
}
}
// 後略
}
onUpdateメソッドよりも、「さぁ、ここの中をお書きなさい!」と言わんばかりに先頭立ってupdateAppWidgetという名のメソッドが仁王立ちしています。
私はこのupdateAppWidgetを捨てました。しばらく粘って頑張ったのですが、捨てました。断捨離。
たしかに、ウィジェットは複数ホームに置くことができるので、appWidgetIdsというint配列でIDが割り振られ、その分の対応をコーディングするとなると、ステップ数が嵩みそうではあります。
なら、updateAppWidgetメソッドに処理を委譲しようというココロは理解できるのですが、 updateAppWidgetメソッドは、static なんです。となると、フィールド変数もそれにつられてstaticにするということになりかねません。
左右「『ウィジェットクラスの任意のメンバー(フィールドとメソッド)には盲目的にすべてstaticを付けろ』というのもカッコ悪いかなぁ」だなんてヘンにカッコつけてみたりして、でも一方で「誰もやらないことやってる俺カッケー」と言うのもカッコ悪いよなぁ。と煩悶。
ということで、なんでもかんでもstaticを付ける、ということも、捨て(ることができ)ました。
さいごに
ということで、Android 8.0(Oreo)でも、そしてOreo未満でも、動作するウィジェットになりました。当記事の題材はウィジェットを扱っていますが、今後「暗黙的ブロードキャストの禁止」が面倒くさい程度まで圧し掛かってきそうな氣がします。
以上です。