web-dev-qa-db-ja.com

PendingIntentを使用してアプリウィジェットのOreoでサービスが開始されない

Androidアプリウィジェットを使用しています。PendingIntentオブジェクトを作成し、メソッドRemoteViews#setOnClickPendingIntent()で使用しています。

// The below code is called in the onUpdate(Context, AppWidgetManager, int[]) method of the AppWidgetProvider class.

// Explicit intent
Intent intent = new Intent(context, MyService.class);
intent.setAction(MY_ACTION);
intent.putExtra(EXTRA_KEY, VALUE);

// Create the pending intent
PendingIntent pendingIntent = PendingIntent.getService(context, appWidgetId, intent, 0);

// 'views' is a RemoteViews object provided by onUpdate in the AppWidgetProvider class.
views.setOnClickPendingIntent(R.id.root_layout, pendingIntent);

上記のコードは、Android Oreoの前に期待どおりに動作します。ただし、Android Oreoでは、アプリが最近のPendingIntentsはOreoのバックグラウンド実行制限から除外されていませんか?

テスト目的で、getServicegetForegroundServiceに置き換えましたが、Serviceはまだ開始されていません。どちらの方法でも、ログに次のメッセージが表示されます。

W/ActivityManager: Background start not allowed: service Intent { act=com.example.myapp.MY_ACTION flg=0x10000000 cmp=com.example.myapp/com.example.myapp.MyService bnds=[607,716][833,942] (has extras) } to com.example.myapp/com.example.myapp.MyService from pid=-1 uid=10105 pkg=com.example.myapp

Serviceを使用している場合でも、getForegroundServiceが開始されないのはなぜですか? Android 8.1.0を実行しているNexus 6Pでこれをテストしました。

16
Thomas Vos

ご指摘のとおり、アプリウィジェットはPendingIntentバックグラウンドホワイトリストにはカウントされません。理由はわかりませんが、PendingIntentで始まるNotificationとほぼ同等のようです。おそらく、Notificationがシステムのものであるのに対して、アプリウィジェットはホーム画面のものであるという問題があります。ともかく:

  • getForegroundService()を使用するか、または

  • Intentを上げたくない場合は、明示的なBroadcastReceiverを使用してgetBroadcast()を試し、JobIntentServiceを開始してNotificationを試してください。 (フォアグラウンドサービスに必要)

あなたの症状に基づいて、あなたは私がバグだと思うものをつまずいたように見えます:再起動することなくgetService()からgetForegroundService()に切り替える方法があるはずです。 :-)いくつかの実験を実行しようとしますが、問題を再現できる場合は問題を報告します。

7
CommonsWare

8.0ではバックグラウンドでサービスを開始できなくなりましたが、JobSchedulerを使用して同様の結果を得ることができます。 JobIntentServiceヘルパークラスもあります。これにより、サービスを大幅に変更することなくJobSchedulerに切り替えることができます。また、サービスを指すPendingIntentは使用できませんが、ActivityまたはBroadcastReceiverを指すものは使用できます。

8.0より前に動作するウィジェットがあり、Android 8.0で動作するようにする必要がある場合、次の簡単な手順を実行します。

  1. IntentServiceクラスをJobIntentServiceに変更します
  2. サービスonHandleIntentメソッドの名前をonHandleWorkに変更します(同じパラメーター)
  3. 追加 BIND_JOB_SERVICEマニフェスト内のサービスに対する許可:
    <service Android:name=".widget.MyWidgetService"
           Android:permission="Android.permission.BIND_JOB_SERVICE">
    </service>
  1. このサービスを開始するには、context.startServiceを使用しないでください。代わりにenqueueWork静的メソッドを使用します(JOB_IDは単なる一意の整数定数であり、同じクラスのキューに入れられたすべての作業で同じ値でなければなりません):
    enqueueWork(context, MyWidgetService.class, JOB_ID, intent);
  1. クリックの場合、サービスを指しているpendingIntentをBroadcastReceiverを指すpendingIntentに置き換えます。 AppWidgetProviderのサブクラスはBroadcastReceiver自体であるため、使用することもできます。
    Intent myIntent = new Intent(context, MyAppWidgetProvider.class);
    myIntent .setAction("SOME_UNIQUE_ACTION");
    pendingIntent = PendingIntent.getBroadcast(context, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1. OnReceiveでenqueueWorkを使用してサービスを開始します(PendingIntentactivityを開始していた場合は、そのままにしておきます-Android 8.0+ ):
    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        if (intent.getAction().equals("SOME_UNIQUE_ACTION")) {
            MyWidgetService.enqueueWork(.....);
        }
    }
  1. widgetが古いデバイスで動作することを確認するには、マニフェストにWAKE_LOCK権限があることを確認してください(古いデバイスのJobIntentServiceで使用)。

それでおしまい。このウィジェットは、新しいデバイスと古いデバイスの両方で正常に機能するようになりました。唯一の本当の違いは、8.0デバイスが居眠りモードの場合、ウィジェットをそれほど頻繁に更新しない可能性があることですが、居眠りしている場合はユーザーがすぐにウィジェットを表示できないことを意味するため、問題にはなりませんいずれかの方法。

14
Alexey

Goasycをオプションにできますか? PendingIntentを変更してBroadcastReceiverを起動し、OnRecieveメソッドでgoasync()を呼び出すことができます。次に、生成されるPendingResultを使用して非同期呼び出しを作成できるようになります。それでもサービスを開始できるとは思わないでください。

broadcastReceiverで「goAsync」を使用する方法

2
Yeshia