web-dev-qa-db-ja.com

Androidでデバイスの再起動後にブロードキャストレシーバーが機能しない

関連する質問をすべて確認しましたが、この問題の解決策は見つかりませんでした。ですから、これは私にとって全く新しい問題です。

私が持っているもの

マニフェストにいくつかのブロードキャストレシーバーを登録するAndroidアプリがあります。これは私のマニフェストのようです。

_<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:tools="http://schemas.Android.com/tools"
    package="com.app.myapp">

    <uses-permission Android:name="Android.permission.PROCESS_OUTGOING_CALLS" />
    <uses-permission Android:name="Android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission Android:name="Android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission Android:name="Android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission Android:name="Android.permission.USE_FINGERPRINT" />
    <uses-permission Android:name="Android.permission.INTERNET" />
    <uses-permission Android:name="Android.permission.READ_CALL_LOG" />
    <uses-permission Android:name="Android.permission.WRITE_CALL_LOG" />
    <uses-permission Android:name="com.Android.vending.BILLING" />
    <uses-permission Android:name="Android.permission.INTERNET" />
    <uses-permission Android:name="Android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission Android:name="Android.permission.GET_ACCOUNTS" />

    <uses-feature
        Android:name="Android.hardware.telephony"
        Android:required="false" />

    <uses-feature
        Android:name="Android.hardware.screen.portrait"
        Android:required="false" />

    <application
        Android:name=".base.MyApp"
        Android:allowBackup="false"
        Android:icon="@drawable/ic_launcher"
        Android:label="@string/label_app_name"
        Android:largeHeap="true"
        Android:supportsRtl="true"
        Android:theme="@style/AppTheme"
        tools:replace="label, allowBackup">

        <receiver Android:name=".mics.BootReceiver">
            <intent-filter>
                <action Android:name="Android.intent.action.BOOT_COMPLETED" />
                <action Android:name="Android.intent.action.QUICKBOOT_POWERON" />
            </intent-filter>
        </receiver>

        <receiver Android:name=".PhoneCallReceiver">
            <intent-filter>
                <action Android:name="Android.intent.action.NEW_OUTGOING_CALL" />
            </intent-filter>
        </receiver>

        <receiver
            Android:name=".mics.DeviceAdminReceiver"
            Android:permission="Android.permission.BIND_DEVICE_ADMIN">
            <intent-filter>
                <action Android:name="Android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>

            <meta-data
                Android:name="Android.app.device_admin"
                Android:resource="@xml/device_admin" />
        </receiver>

        <receiver
            Android:name="com.clevertap.Android.sdk.InstallReferrerBroadcastReceiver"
            Android:exported="true">
            <intent-filter>
                <action Android:name="com.Android.vending.INSTALL_REFERRER" />
            </intent-filter>
        </receiver>

        <meta-data
            Android:name="com.app.myapp.utils.ImageLoaderModule"
            Android:value="GlideModule" />

        <meta-data
            Android:name="com.app.myapp.utils.AudioCoverLoaderModule"
            Android:value="GlideModule" />

        <provider
            Android:name="Android.support.v4.content.FileProvider"
            Android:authorities="${applicationId}.provider"
            Android:exported="false"
            Android:grantUriPermissions="true">

            <meta-data
                Android:name="Android.support.FILE_PROVIDER_PATHS"
                Android:resource="@xml/provider_paths" />
        </provider>

        <activity
            Android:name=".core.activities.SplashActivity"
            Android:excludeFromRecents="true"
            Android:label="@string/label_app_name"
            Android:screenOrientation="portrait">
            <intent-filter>
                <action Android:name="Android.intent.action.MAIN" />
            </intent-filter>
        </activity>

        <activity-alias
            Android:name=".core.activities.SplashActivity-Alias"
            Android:icon="@drawable/ic_launcher"
            Android:label="@string/label_app_name"
            Android:noHistory="true"
            Android:targetActivity="com.app.myapp.core.activities.SplashActivity">

            <intent-filter>
                <action Android:name="Android.intent.action.MAIN" />

                <category Android:name="Android.intent.category.LAUNCHER" />
                <category Android:name="Android.intent.category.DEFAULT" />
                <category Android:name="Android.intent.category.MONKEY" />
            </intent-filter>

        </activity-alias>

        <activity
            Android:name=".core.flow.authFlow.activities.AuthFlowActivity"
            Android:excludeFromRecents="true"
            Android:label="@string/label_app_name"
            Android:screenOrientation="portrait" />

        <service Android:name=".features.fileCloudSync.KillNotificationService" />

    </application>

</manifest>
_

他にも10〜15個のアクティビティがありますが、簡単にするために削除されています。そして、これは基本的なブートレシーバークラスです。ここからサービスを開始します。

_public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            AlertUtils.showToast(context, "BOOT COMPLETED", Toast.LENGTH_LONG);
        }
    }
}
_

通話受信者クラスは次のようになります(同様に単純化されています)。

_public class PhoneCallReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
            AlertUtils.showToast(context, "PHONE CALL RECEIVED", Toast.LENGTH_LONG);
            // Simplified for brevity
        }
    }
}
_

問題

これらのレシーバーはすべて、アプリをインストールして一度起動すると正常に機能します。しかし、デバイスを再起動した後、これらの受信機はまったく機能しません。 BootCompleteReceiverPhoneCallReceiveronReceive()メソッドが呼び出されません。

私の想定では、これらの受信機は再起動後に自動的に登録されますが、機能しません。アプリで重要なサービスを開始できるようにするには、BootCompleteReceiverが必要です。

私の観測

これを徹底的にテストしました。デバイスを再起動した後、レシーバーはNexus 5X(Nougat)、Nexus 6P(Nougat)、YU Yuphoria(Lollipop)で正常に動作しますが、私のOnePlus 3(Nougat)およびMi 4i(ロリポップ)

同じコードがいくつかのデバイスで完全に機能し、他のデバイスではまったく機能しないのはどうしてですか?何も変更していません。

ここで何が間違っていますか?私のアプリはこれらのブロードキャストに大きく依存しており、これらに基づいてサービスを開始します。どんな助けも大歓迎です。

編集1

問題をよりよく理解するために、単一のアクティビティとまったく同じBootCompleteReceiverPhoneCallReceiverを持つ非常に小さなテストプロジェクトを作成しました。

しかし、奇妙なことに、このプロジェクトはOnePlus 3で完全に機能し、実際のアプリの受信機は再起動後に機能しません。私は当初、問題はOSまたはデバイスにあると仮定していましたが、そうではありません。

では、実際の問題はどこにあるのでしょうか?私のアプリ(しかし、他のデバイスで完全に動作します)か、OSとデバイス(小さなテストプロジェクトは同じOSと同じデバイスで正常に動作します)ですか?

それは本当に私を混乱させます。これに関する専門家の助けが必要です。

編集2

@shadygoneinsaneの提案を試しました。これが私の観察です。

1)ADB経由でBOOT_COMPLETEDブロードキャストを送信しようとしました。

_./adb Shell am broadcast -a Android.intent.action.BOOT_COMPLETED -p com.app.myapp
_

そして、このスタックトレースを取得しました。

_Broadcasting: Intent { act=Android.intent.action.BOOT_COMPLETED pkg=com.app.myapp }
Java.lang.SecurityException: Permission Denial: not allowed to send broadcast Android.intent.action.BOOT_COMPLETED from pid=25378, uid=2000
    at Android.os.Parcel.readException(Parcel.Java:1683)
    at Android.os.Parcel.readException(Parcel.Java:1636)
    at Android.app.ActivityManagerProxy.broadcastIntent(ActivityManagerNative.Java:3696)
    at com.Android.commands.am.Am.sendBroadcast(Am.Java:778)
    at com.Android.commands.am.Am.onRun(Am.Java:404)
    at com.Android.internal.os.BaseCommand.run(BaseCommand.Java:51)
    at com.Android.commands.am.Am.main(Am.Java:121)
    at com.Android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
    at com.Android.internal.os.RuntimeInit.main(RuntimeInit.Java:276)
_

デバイスがルート化されていないためかもしれません。このブロードキャストを送信することはできません。

2)その後、PROCESS_OUTGOING_CALLSブロードキャストを試しました。

_./adb Shell am broadcast -a Android.intent.action.PROCESS_OUTGOING_CALLS -p com.app.myapp
_

私はこれを得た、

_Broadcasting: Intent { act=Android.intent.action.PROCESS_OUTGOING_CALLS pkg=com.app.myapp }
Broadcast completed: result=0
_

ブロードキャストは成功したようですが、Toastやログは表示されません。その後、ダイヤラーを開いて番号をダイヤルすると、Toastとログの両方が表示されます。

つまり、ADBを介したブロードキャストの送信は機能しなかったようです、実際にはダイヤラーを開いて番号をダイヤルしました。

編集

@ChaitanyaAtkuriからの提案に従って、インテントフィルターにも優先順位を追加しようとしましたが、うまくいきませんでした。

500、999などの優先順位と最高の整数値を使用しましたが、何も機能しません。この問題は、私の友達のアプリでも発生しています。一部のデバイスでは機能しますが、他のデバイスでは機能しません。

編集4

OnePlus 3で発生した問題の根本原因が最終的に判明。私のOnePlus 3は最近Nougatに更新され、特定のアプリが再起動後に自動起動しないようにするMiデバイスに似た機能を導入しました。

この機能を無効にすると、アプリは完全に再起動後にブロードキャストを受信し始めました。しかし、これはまだ2つのことを説明していません。

1)私の小さなテストプロジェクトはAutoLaunchアプリのリストに自動的にホワイトリストに登録されているため、期待どおりに機能します。しかし、これはどのように可能ですか? OSがこの小さなアプリを自動起動する価値があると考えるのはなぜですか?

2)LockDown Pro、500 Firepaperなどのアプリがあります。これはAutoLaunchアプリ画面にブラックリストに登録されていますが、OnePlus 3およびMi 4iで再起動後にブロードキャストを受信します。それは今どのように可能ですか?これらのデバイス(OnePlusおよびMi)でアプリをプログラムで自動起動できるようにすることは、どういうわけか可能ですか?

編集5

@Rahul Chowdhuryによって提案された解決策を試しましたが、本当にうまくいくようです。アクセシビリティサービスを追加した後、問題は解決されます。

しかし、ユーザーがアクセス許可を付与した後に取り消した場合、アクセシビリティ許可が私のアプリで利用可能かどうかをプログラムで確認する方法はありますか?

47
Aritra Roy

これは、あなたが言及したOnePlusとMiの両方のデバイスでテスト済みで動作するソリューションです。

OnePlusおよびMiデバイスの自動起動防止機能により、アプリが起動完了時に自動的にサービスを開始できないようにして、デバイス全体の起動速度とバッテリーを改善しますパフォーマンス。ただし、この機能がオンになっている場合でもアプリを動作させるための回避策があります。

アプリにAccessibilityServiceがあり、ユーザーがオンにすると、アプリはこれらのメーカーが適用するフィルターを通過し、アプリはそのブート完了イベントを受け取り、他のBroadcastReceiverは期待どおりに動作することに気付きました。

このトリックの考えられる説明は、AccessibilityServiceはシステムレベルのサービスであるため、独自のサービスを登録することでこれらのメーカーが適用した特定のフィルターを渡すであり、カスタムAccessibilityServiceがトリガーされるとすぐにOSの場合、アプリは、登録した適格なBroadcastReceiverを受信する際にアクティブになります。

だから、ここでそれを行う方法です、

この許可をAndroidManifest.xmlに追加することから始めます。

<uses-permission Android:name="Android.permission.BIND_ACCESSIBILITY_SERVICE"/>

これにより、アプリのAccessibilityServiceをシステムに登録できます。

次に、プロジェクトのresフォルダーの下にあるXMLフォルダー内にmy_accessibility_service.xmlなどのファイルを作成して、AccessibilityServiceの非常に基本的な構成を追加します。

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:accessibilityFeedbackType="feedbackSpoken"
    Android:description="@string/service_desc"
    Android:notificationTimeout="100"/>

あとあと1ステップ、プロジェクトでカスタムAccessibilityServiceを定義し、

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) { }

    @Override
    public void onInterrupt() {

    }
}

この回避策ではなく、AccessibilityServiceを何らかの目的で必要としないため、オーバーライドされたメソッドを空のままにしておくことができます。

最後に、AndroidManifest.xmlAccessibilityServiceを宣言するだけです。

<service
    Android:name=".MyAccessibilityService"
    Android:label="@string/app_name"
    Android:permission="Android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action Android:name="Android.accessibilityservice.AccessibilityService"/>
    </intent-filter>

    <meta-data
        Android:name="Android.accessibilityservice"
        Android:resource="@xml/my_accessibility_service"/>
</service>

それで全部です。アプリ内で、ユーザーに設定からアプリのアクセシビリティサービスを有効にして、そのままにしておくように頼むだけです!アプリは、起動時にアプリを自動起動するフィルターをOSが設定している場合でも、すべてのデバイスで正常に動作します。

編集1

アプリのアクセシビリティサービスがオンになっているかどうかを確認する方法は次のとおりです。

private static final int ACCESSIBILITY_ENABLED = 1;

public static boolean isAccessibilitySettingsOn(Context context) {
    int accessibilityEnabled = 0;
    final String service = context.getPackageName() + "/" + MyAccessibilityService.class.getCanonicalName();
    try {
        accessibilityEnabled = Settings.Secure.getInt(
                context.getApplicationContext().getContentResolver(),
                Android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
    } catch (Settings.SettingNotFoundException e) {
        Log.e("AU", "Error finding setting, default accessibility to not found: "
                + e.getMessage());
    }
    TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

    if (accessibilityEnabled == ACCESSIBILITY_ENABLED) {
        String settingValue = Settings.Secure.getString(
                context.getApplicationContext().getContentResolver(),
                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
        if (settingValue != null) {
            mStringColonSplitter.setString(settingValue);
            while (mStringColonSplitter.hasNext()) {
                String accessibilityService = mStringColonSplitter.next();

                if (accessibilityService.equalsIgnoreCase(service)) {
                    return true;
                }
            }
        }
    }

    return false;
}

お役に立てれば。

33
Rahul Chowdhury

こんにちは、私はパーティーに遅れていますが、最初からこの質問を追っていました。 One-plusおよびその他のOEMがBOOT_COMPLETEDブロードキャストを受信できるアプリのリストを保持していることを知っています。アプリがホワイトリストにない場合、起動時にアプリは起動しません。今、私はメモリとリソースの点で非常に効率的であり、再起動またはハードブート後にタスクまたはサービスを開始することも保証されているソリューションを持っていますAccessibilityServiceこの 回答で提案されているように 。ここに行きます。

  1. manifestファイルに<uses-permission Android:name="Android.permission.RECEIVE_BOOT_COMPLETED"/>権限を追加します。

2. com.google.Android.gms:play-services-gcmに依存関係がない場合は、build.gradleの依存関係セクションに次を追加します。

compile 'com.firebase:firebase-jobdispatcher:0.5.2'

それ以外の場合は、次を追加します。

compile 'com.firebase:firebase-jobdispatcher-with-gcm-dep:0.5.2'

これはfirebaseチームからの library であり、google-play-serviceライブラリに依存してジョブをスケジュールし、私の観点からgoogle-play-serviceに権限がありますgoogle-play-serviceの代わりに起動時に起動するようにすると、デバイスが再起動するとすぐにジョブが実行されます。

  1. 今、このステップは簡単ですJobServiceクラスを定義するだけです

    public class MyJobService extends JobService {
    
    @Override
    public boolean onStartJob(JobParameters job) {
        Log.v("Running", "====>>>>MyJobService");
    
        return false; // Answers the question: "Is there still work going on?"
    }
    
    @Override
    public boolean onStopJob(JobParameters job) {
        Log.v("Stopping", "====>>>>MyJobService");
        return true; // Answers the question: "Should this job be retried?"
    }
    

}

  1. マニフェストファイルにジョブサービスを追加します。

    <service
      Android:exported="false"
      Android:name=".MyJobService">
    <intent-filter>
        <action Android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
    </intent-filter>
    </service>
    
  2. アプリの起動時など、どこでも好きな場所でこのジョブをスケジュールします。

           FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(getApplicationContext()));
    
            Bundle myExtrasBundle = new Bundle();
    
            myExtrasBundle.putString("some_key", "some_value");
    
            Job myJob = dispatcher.newJobBuilder()
                    // the JobService that will be called
                    .setService(MyJobService.class)
                    // uniquely identifies the job
                    .setTag("my-unique-tag-test")
                    // repeat the job
                    .setRecurring(true)
                    // persist past a device reboot
                    .setLifetime(Lifetime.FOREVER)
                    // start between 0 and 60 seconds from now
                    .setTrigger(Trigger.executionWindow(0, 60))
                    // don't overwrite an existing job with the same tag
                    .setReplaceCurrent(false)
                    // retry with exponential backoff
                    .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
                    // constraints that need to be satisfied for the job to run
                    .setExtras(myExtrasBundle)
    
                    .build();
    
            dispatcher.mustSchedule(myJob);
    

6.それだけです!!これで、ホワイトリストに登録されているかどうかに関係なく、デバイスブートでタスクまたはサービスを実行できます。

Google Play Serviceをデバイスにインストールする必要があることに注意してください。そうしないと機能しません。

5
Sunny

@アリトラ、これを試して

<receiver
            Android:name=".mics.BootReceiver"
            Android:enabled="true"
            Android:exported="true" >
            <intent-filter Android:priority="500" >
                <action Android:name="Android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

QuickBootインテントフィルターを削除し、実行することを試みてください。ドキュメントによると、BootCompletedで必要なのはそれだけです。これが中断しているのかもしれません。

注意すべきもう1つの重要なポイント:

バッテリー使用量を最適化するためだけにプッシュ通知サービスやバックグラウンドサービスを停止するなど、Androidの基本機能を停止する独自のOSがあるため、完全に依存したりMiデバイスでテストしたりしないでください。Miデバイスでこれをテストするには、セキュリティアプリでアプリを「AutoStart」としてマークしてから試してください。

3
ChaitanyaAtkuri

IntentFiltersが機能する方法は、各<intent-filter></intent-filter>にコンポーネントを起動する1つの方法が含まれることです。複数の起動方法がある場合(1つのBroadcastReceiverでリッスンする2つのアクションなど)、それぞれに独立した<intent-filter></intent-filter>定義が必要です。

したがって、変更を試すことができます:

<receiver Android:name=".mics.BootReceiver">
    <intent-filter>
        <action Android:name="Android.intent.action.BOOT_COMPLETED" />
        <action Android:name="Android.intent.action.QUICKBOOT_POWERON" />
    </intent-filter>
</receiver>

に:

<receiver Android:name=".mics.BootReceiver">
    <intent-filter>
        <action Android:name="Android.intent.action.BOOT_COMPLETED" />
    </intent-filter>

    <intent-filter>
        <action Android:name="Android.intent.action.QUICKBOOT_POWERON" />
    </intent-filter>
</receiver>

詳細はこちら: Intents and Intent Filters | Android Developers

[〜#〜] edit [〜#〜]

それでも動作しない場合は、マニフェスト宣言が正しく行われているかどうかをテストしてみてください。ターミナルで次のコマンドを実行して、テストデバイスをコンピューターに接続したままにします。

adb Shell am broadcast -a Android.intent.action.BOOT_COMPLETED -n com.app.myapp/.mics.BootReceiver

これが機能しない場合は、マニフェストファイルでレシーバーの相対パッケージ宣言を再確認する必要があります。

EDIT 2

奇妙に聞こえるかもしれませんが、次の手順を試してください。

  • 電話からアプリをアンインストールします(すべてのユーザーに対してアプリがアンインストールされていることを確認してください)
  • 電話を再起動してください
  • プロジェクトをきれいにする
  • デバイスでプロジェクトを再度ビルドして実行します
2
Kamran Ahmed

私はほぼ一年からこの問題に苦労してきました。すべてのアプリで、アプリのバッテリー最適化を無効にするようユーザーに通知します。

One Plusデバイスで多くのテストを行った後、アプリのバッテリー最適化がオフになっていると、ブート完了ブロードキャストを受信できます。私の意見では、上記のアクセシビリティサービスよりもはるかに優れています。

アプリのバッテリー最適化を無効にするようユーザーに求める最も簡単な方法は、何らかの通知を表示し、ユーザーがクリックするとバッテリー最適化ページを開くことです。これを行うには、以下のコードを使用できます。

public void openPowerSettings(View v) {

    /* Make Sure to add below code to manifest
    <uses-permission Android:name="Android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
    */

    try {
        Intent i = new Intent(Android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
        startActivityForResult(i, 1);
    }  catch (Exception e) {
        Log.e (TAG, "Exception: " + e.toString());
    }

}

また、以下の関数がtrueを返す場合、通知を非表示にすることもできます。

public static boolean is_ignoring_battery_optimizations(Context context) {
    String PACKAGE_NAME = context.getPackageName();
    PowerManager pm = (PowerManager) context.getSystemService(context.POWER_SERVICE);
    boolean status = true;
    if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.M) {
        status = pm.isIgnoringBatteryOptimizations(PACKAGE_NAME);
    }
    return status;
}
1
mstoic