web-dev-qa-db-ja.com

バックグラウンド実行では、インテントBOOT_COMPLETEDを受信できません

Android Oreoバックグラウンド実行制限について読みました。BOOT_COMPLETEDブロードキャストは影響を受けないと明確に述べていますが、Androidオレオ。

まず、SDK 27に対してコンパイルします。次に、マニフェストファイル内でレシーバーを宣言しました。

    <uses-permission Android:name="Android.permission.RECEIVE_BOOT_COMPLETED"/>
    <receiver
        Android:name="helpers.StartDetectionAtBoot"
        Android:label="StartDetectionAtBoot"
        Android:enabled="true"
        Android:exported="true">
        <intent-filter>
            <category Android:name="Android.intent.category.DEFAULT"/>

            <action Android:name="Android.intent.action.MY_PACKAGE_REPLACED"/>

            <action Android:name="Android.intent.action.BOOT_COMPLETED"/>
            <action Android:name="Android.intent.action.QUICKBOOT_POWERON"/>
            <!--For HTC devices-->
            <action Android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
            <!--For MIUI devices-->
            <action Android:name="Android.intent.action.REBOOT"/>
        </intent-filter>
    </receiver>

次に、レシーバーの実装があります。これも簡単です。

public class StartDetectionAtBoot extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("test", "test");

        Intent intent0 = new Intent( context, ActivityRecognitionService.class );
        PendingIntent pendingIntent = PendingIntent.getService(context, 111, intent0, PendingIntent.FLAG_UPDATE_CURRENT);
        ActivityRecognitionClient activityRecognitionClient = ActivityRecognition.getClient(context);
        activityRecognitionClient.requestActivityUpdates(5000, pendingIntent);
    }
}

onReceiveメソッドは呼び出されず、常にAndroid Oreoデバイス/エミュレーターでlogcatエラーが発生します:

W/BroadcastQueue:バックグラウンド実行は許可されていません:受信インテント{act = Android.intent.action.BOOT_COMPLETED flg = 0x400010}

他の答えを読んで、彼らはマニフェストに明示的な意図を登録するときにいくつかの問題があると言ったが、これはBOOT_COMPLETEDの場合ではない。

どちらも this は受信者がまったく呼び出されないので助けになりません。

実行時にブロードキャストインテントを登録し、動作させる(エミュレータ上で、adbシェルからインテントを起動する)が、それを行う正しい方法がわからない:

registerReceiver(new StartDetectionAtBoot(), new IntentFilter(Intent.ACTION_BOOT_COMPLETED));

これに関する既知のバグはありますか?

12
fillobotto

解決策は、私がすでに行った2つの試みの組み合わせでした。

最初に、スティッキー通知を使用してフォアグラウンドサービスを開始する必要がありました(ダミーのサービスであってもよいでしょう)。

public class StartDetectionAtBoot extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Intent intent1 = new Intent(context.getApplicationContext(), DummyService.class);
            context.startForegroundService(intent1);
        }

        Intent intent0 = new Intent( context, ActivityRecognitionService.class );
        PendingIntent pendingIntent = PendingIntent.getService(context, 111, intent0, PendingIntent.FLAG_UPDATE_CURRENT);
        ActivityRecognitionClient activityRecognitionClient = ActivityRecognition.getClient(context);
        activityRecognitionClient.requestActivityUpdates(5000, pendingIntent);
    }
}

もちろん、開始するサービス内には、通知を作成してonCreateを呼び出すstartForegroundメソッドが必要です。

第二に、Android Studioでキャッシュの無効化を行い、エミュレーターのインスタンスも消去しました。ソリューションのこの部分はnecessary最初の部分はまだ機能しなかったので、私にとっては。

0
fillobotto

必要な暗黙的インテントのコードでレシーバーを自己登録することは、実際にそのインテントの受信を開始する正しい方法です。これにはサービスは必要ありません(ほとんどの場合、以下を参照してください...)。ただし、テスト中に混乱せず、以前の実装を壊さないために、次のことに注意する必要があります。

  1. 自己登録は、アプリケーションの実行ごとに1回実行する必要があります。 Java/Kotlinアプリケーションデータ:静的フィールドライフごとに1回。したがって、1つの静的ブールフィールドを使用すると、自己登録が必要かどうか(再起動後または= Androidシステムは後でアプリを強制終了しました...)またはそうではありません(このコミットから作業コードを引用しています: https://github.com/andstatus/todoagenda/commit/74ffc1495f2c4bebe5c43aab13389ea0ea821fde =):
    private static volatile boolean receiversRegistered = false;

    private static void registerReceivers(Context contextIn) {
        if (receiversRegistered) return;

        Context context = contextIn.getApplicationContext();
        EnvironmentChangedReceiver receiver = new EnvironmentChangedReceiver();

        IntentFilter providerChanged = new IntentFilter();
        providerChanged.addAction("Android.intent.action.PROVIDER_CHANGED");
        providerChanged.addDataScheme("content");
        providerChanged.addDataAuthority("com.Android.calendar", null);
        context.registerReceiver(receiver, providerChanged);

        IntentFilter userPresent = new IntentFilter();
        userPresent.addAction("Android.intent.action.USER_PRESENT");
        context.registerReceiver(receiver, userPresent);

        Log.i(EventAppWidgetProvider.class.getName(), "Registered receivers from " + contextIn.getClass().getName());
        receiversRegistered = true;
    }
  1. RegisterReceiversメソッドの呼び出しをallアプリケーションの可能なエントリポイントに挿入して、アプリケーションが起動された場合でも受信者が登録される可能性を最大化するAndroidシステムは一度だけ、例えば:
    @Override
    public void onUpdate(Context baseContext, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        registerReceivers(baseContext);

        ...
    }
  1. AndroidManifest.xmlファイルの同じインテントに登録されているレシーバーを残すことができます(これはAndroid v.7より前に...)で動作します)が、この場合logcatでは「バックグラウンド実行は許可されていません」とレシーバへの参照が表示されます。これは、AndroidManifest.xmlを介した登録が機能しないことを意味します(Android 8+)とにかくレシーバーを呼び出す必要があります!

  2. 前述したように、ライトウィジェットではフォアグラウンドサービスを開始する必要は通常ありません。さらに、ユーザーは、「ウィジェット」がフォアグラウンドで実行されているという通知を常に見るのを好みません(したがって、常にリソースを消費します)。本当に必要な場合は、Androidがアプリケーションを頻繁に強制終了し、再起動後に行われた自己登録を削除する場合のみです。「ウィジェットアプリケーション」を軽量にすること可能な限り(できるだけ少ないメモリとCPUリソースが必要です...)、ウィジェットアプリがデバイスの重大な場合にのみ強制終了されることを保証する正しい方法です...おそらく、大きなアプリを2つに分割する必要があります。時々動作する必要があるヘビー級アプリ用のランチャーのようなウィジェットを作成しています...

1
yvolk