Broadcast Receiver Not Working After Device Reboot in Android

早过忘川 提交于 2019-11-27 02:42:48
Rahul Chowdhury

Here's a tested and working solution on both the devices that you mentioned, OnePlus and Mi.

As you said the auto-start prevention feature on OnePlus and Mi devices prevent apps from starting up their services automatically on boot complete so as to improve the overall device boot speed and battery performance. However, there's a workaround to get your app working even when this feature is turned on.

I have noticed that if you have an AccessibilityService in your app and it is turned on by the user, then your app passes the filter that these manufacturers apply and the app receives it's boot complete event and any other BroadcastReceiver works as expected.

The possible explanation of this trick can be that since AccessibilityService is a system level service, so by registering your own service you are passing the certain filter applied by these manufacturers and as soon as your custom AccessibilityService gets triggered by the OS, your app becomes active in receiving the eligible BroadcastReceiver that you had registered.

So, here's how to do it,

Start by adding this permission to your AndroidManifest.xml,

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

This will allow you to register your app's AccessibilityService with the system.

Now, add a very basic configuration for your AccessibilityService by creating a file for example my_accessibility_service.xml inside XML folder under your res folder in your project.

<?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"/>

There's just one more step left to do, define your custom AccessibilityService in your project,

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) { }

    @Override
    public void onInterrupt() {

    }
}

Note, since you're not needing the AccessibilityService for any purpose rather than this workaround, you can leave the overridden methods empty.

Finally, just declare your AccessibilityService in your AndroidManifest.xml,

<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>

That's all. Now within your app, just ask your users to turn on the accessibility service for your app from the settings and leave it on and voila! Your app works fine on all devices even where the OS puts a filter on which apps should auto-start on boot.

EDIT 1

Here's how you can check if accessibility service is turned ON or not for your app,

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;
}

Hope this helps.

Sunny

Hi I am late to the party but I was following this question from it's start. I know there that One-plus and some other OEMs maintain a list of apps which can receive BOOT_COMPLETED broadcast. If your app is not white listed then your app won't be started on boot. Now I've a solution which is very efficient in terms of memory and resources and guaranteed to start your task or service after reboot or hard boot also does not need AccessibilityService as proposed in this answer. Here it goes..

  1. Add <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> permission in your manifest file.

2.If you don't have a dependency on com.google.android.gms:play-services-gcm, add the following to your build.gradle's dependencies section:

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

Otherwise add the following:

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

This is a library from firebase team which depends on google-play-service library to schedule your jobs and from my point of view google-play-service has the permission to start at boot so instead of system google-play-service will run your job as soon as device rebooted.

  1. Now this step is easy Just define a JobService class

    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. Add your Job Service in manifest file.

    <service
      android:exported="false"
      android:name=".MyJobService">
    <intent-filter>
        <action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
    </intent-filter>
    </service>
    
  2. Schedule this job anywhere you want for e.g when your app start.

           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.That's it!! Now you can execute your task or service on device boot no matter you are in white list or not.

There is one point to note that Google Play Service must be installed on device otherwise it won't work.

@Aritra, Try this

<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>

Remove quickBoot intent filter and try running it, as per the documentation we only required BootCompleted to acheive it. May be it is interrupting this.

Also one more important point to Note :

Don't rely completely or test on Mi devices as they have their own OS which halts the basic features of Android, like they stop the Push notifications services and background services just to optimize battery usage. To test this on Mi device, mark your app as "AutoStart" in Security app and then try.

The way IntentFilters work is that each <intent-filter></intent-filter> contains one way of firing up the component. If you have multiple ways of firing it up (like two actions that you want to listen to in one BroadcastReceiver), you'll need an independent <intent-filter></intent-filter> definition for each.

Hence, you can try changing:

<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>

to:

<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>

Read more here: Intents and Intent Filters | Android Developers

EDIT

If it still doesn't work, you can try to test if your manifest declaration is done correctly. Try executing the following command in your terminal, keeping the test device connected to the computer:

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

If this doesn't work, you should recheck the relative package declaration of the receiver in your manifest file.

EDIT 2

It may sound weird but try following these steps:

  • Uninstall the app from your phone (ensure it is uninstalled for all users)
  • Reboot your phone
  • Clean the project
  • Build and run the project in your device again

I have been struggling with this issue from almost a year. In all my apps, I show a notice to users to disable battery optimization for my app.

After a lot of testing on One Plus devices, I am able to receive boot completed broadcast when battery optimization is turned off for my app. In my opinion, it is much better than the accessibility service discussed above.

The simplest way to ask the user to disable the battery optimization for your app is to show some kind of notice, and open the battery optimization page when the user clicks on it. You can use the below code to do that.

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());
    }

}

And you can also hide the notice if below function returns 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;
}

How to start service on device boot(autorun app, etc.)

For first: since version Android 3.1+ you don't receive BOOT_COMPLETE if user never started your app at least once or user "force closed" application. This was done to prevent malware automatically register service. This security hole was closed in newer versions of Android.

Solution:

Create app with activity. When user run it once app can receive BOOT_COMPLETE broadcast message.

For second: BOOT_COMPLETE is sent before external storage is mounted. If app is installed to external storage it won't receive BOOT_COMPLETE broadcast message.

In this case there is two solution:

  1. Install your app to internal storage
  2. Install another small app in internal storage. This app receives BOOT_COMPLETE and run second app on external storage.

If your app already installed in internal storage then code below can help you understand how to start service on device boot.


In Manifest.xml

Permission:

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

Register your BOOT_COMPLETED receiver:

<receiver android:name="org.yourapp.OnBoot">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>

Register your service:

<service android:name="org.yourapp.YourCoolService" />

In receiver OnBoot.java:

public class OnBoot extends BroadcastReceiver
{

    @Override
    public void onReceive(Context context, Intent intent) 
    {
        // Create Intent
        Intent serviceIntent = new Intent(context, YourCoolService.class);
        // Start service
        context.startService(serviceIntent);

    }

 }

For HTC you maybe need also add in Manifest this code if device don't catch RECEIVE_BOOT_COMPLETED:

<action android:name="android.intent.action.QUICKBOOT_POWERON" />

Receiver now look like this:

<receiver android:name="org.yourapp.OnBoot">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.QUICKBOOT_POWERON" />
    </intent-filter>
</receiver>

How to test BOOT_COMPLETED without restart emulator or real device? It's easy. Try this:

adb -s device-or-emulator-id shell am broadcast -a android.intent.action.BOOT_COMPLETED

How to get device id? Get list of connected devices with id's:

adb devices

adb in ADT by default you can find in:

adt-installation-dir/sdk/platform-tools

Enjoy! )

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!