Broadcast-receiver which always receives broadcast (even in background) for API Level +26
Asked Answered
D

2

5

I'm posing this as Q&A style because I found this idea working. And it's a fix to the hard problem to crack for beginners with Android.

Google has deprecated registering Broadcast Receiver into manifest like this below from API Level 26+ ( Except Some )

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.net.wifi.STATE_CHANGE" />
    </intent-filter>
</receiver>

But, If one wants to receive particular device state changes like Internet Connectivity Changes (Which isn't allowed) while the app is in background and if it's important for any feature of his application, what should he do?

Discern answered 31/5, 2018 at 15:42 Comment(0)
D
5

When I was going through the documentation, My eyes got stuck here :

Context-registered receivers receive broadcasts as long as their registering context is valid. For an example, if you register within an Activity context, you receive broadcasts as long as the activity is not destroyed. If you register with the Application context, you receive broadcasts as long as the app is running.

That practically means if I can hold a Context, the broadcast-receiver registered with it will run in the background.

For doing that, a Service will be the best practice.

This is below code for a STICKY_SERVICE which is started again after killed and thus the context remains valid.

AlwaysOnService.class

package app.exploitr.auto;

import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.support.annotation.Nullable;

public class AlwaysOnService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        registerReceiver(new ClickReceiver(), new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"));
        return Service.START_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onLowMemory() {  // rem this if you want it always----
        stopSelf();
        super.onLowMemory();
    }
}

Now, the receiver which actually does things :

ClickReceiver.class

package app.exploitr.auto;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.util.Objects;

public class ClickReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(final Context context, Intent intent) {

        switch (Objects.requireNonNull(intent.getAction())) {

            case AutoJob.NOTIFICATION_CANCEL_TAG:
                System.out.println("Not related");
                break;

            case AutoJob.LOGIN_CANCEL_TAG:
                System.out.println("Not related");
                break;

            case "android.net.conn.CONNECTIVITY_CHANGE":
                System.out.println("Oops! It works...");
                break;
        }
    }
}

Launch Code From Any Activity Class

private void setUpBackOffWork() {
    if (DataMan.getInstance(getBaseContext()).getPeriodic()) {
        AutoJob.schedulePeriodic();
        //Not related
    }
    if (DataMan.getInstance(getBaseContext()).getPureAutoLogin()) {
        startService(new Intent(this, AlwaysOnService.class));
    }
}

So my target was to Login into my isp automatically when I turn up my android's WiFi, and the code works smooth. It doesn't fail ever (It's running for 7 hours and 37 minutes till now and working well | not across reboots).


To keep the receiver running across reboots, try manifest registerable BOOT_COMPLETED actions. It works just like the old one.

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


Update 1

Now, as Google took one step to limit background execution & as a result you've also to make the service a foreground service. So, the procedure goes below.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, new Intent(THIS_SERVICE_CLASS_NAME.this, ACTIVITY_TO_TARGET.class), 0);

    /*Handle Android O Notifs as they need channel when targeting 28th SDK*/
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        NotificationChannel notificationChannel = new NotificationChannel(
        "download_check_channel_id", 
        "Channel name",
        NotificationManager.IMPORTANCE_LOW);

        if (notificationManager != null) {
            notificationManager.createNotificationChannel(notificationChannel);
        }

        builder = new Notification.Builder(this.getBaseContext(), notificationChannel.getId())
                .setContentTitle("Hi! I'm service")
                .setContentIntent(pendingIntent)
                .setOngoing(true);

        notification = builder.build();
        startForeground("StackOverflow".length(), notification);
    }

    return START_STICKY;
}
Discern answered 31/5, 2018 at 15:42 Comment(18)
After 120 hours, I'm accepting my own, cause no other answer received in this period.Discern
That doesn't belong to this one specificallyDiscern
I need help in restarting my service on boot as Android oreo doesnt allow me to listen boot eventZadazadack
Can you please provide me sample code how can i keep running my services in android oreo and pie version. It seem a mess as there are no sample code avaibleZadazadack
No, Oreo allows you to listen boot event. I'm using 8.1.0 and it works perfectly.Discern
BOOT event has no restriction.Discern
how to register for it as it says you must register with context but as soon i register it in my activity i have unregister it in OnDestroy. I am looking for long running services and it will get started on boot. Can you help me with sample code. Boot event is not working one plus 5 handset i checked itZadazadack
No no, BOOT event has no context registration restriction. You can declare it in the manifest normally. I've already told it in the last section of my answer. Please read carefully.Discern
Toaster just checking in One plus 5 and 6 its not working.Zadazadack
raised my question here #53156180Zadazadack
As part of the Android 8.0 (API level 26) Background Execution Limits, apps that target the API level 26 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest.For more details please check Android guide developer.android.com/guide/components/broadcast-exceptionsZadazadack
developer.android.com/guide/components/broadcast-exceptions Read this. I think the problem is with your device make. - OnePlus. Fine works on my Nokia 5, Nokia 8 , Samsung A8, Motorola devices.Discern
What should be alternative for this. Most of my users use one plus and xiaomi phone.Zadazadack
It's working on MI devices? Then nothing to worry. Anyway, you can't tell people to use custom rom or buy a new device to use your app. So..Discern
your answer works fine! but in my case, the broadcast needs to be registered at a particular time. I am trying to achieve this but I have not been successful in my attempts. Can you help me out with it? @ToasterSnowdrift
@Mittal Varsani That's pretty easy. Check this out : github.com/evernote/android-jobDiscern
what if I don't want to use the library? @ToasterSnowdrift
You will use WorkManager class. Google will help you in this context. Using the library will help you to support older APIs.Discern
C
2

This also applies to Xamarin Android. The Play Store demanded to upgrade my apps's SDK to 8.0 Oreo, and a bunch of stuff stopped working on it.

Microsoft's documentation on Broadcast Receivers is quite confusing:

Apps that target Android 8.0 (API level 26) or higher may not statically register for an implicit broadcast. Apps may still statically register for an explicit broadcast. There is a small list of implicit broadcasts that are exempt from this restriction.

Even Google's official docs are quite inscrutable.

On Xamarin Android it used to be enough to follow this pattern:

[BroadcastReceiver]
[IntentFilter(new string[] {MyReceiver.MyAction})]
public class MyReceiver : BroadcastReceiver
{
    public const String MyAction = "com.mytest.services.MyReceiver.MyAction";

    public override void OnReceive (Context context, Intent intent)
    {
        // ...
    }
}

The IntentFilter annotation instructs the compiler to add the receiver and intent filters registrations to the Manifest file during the build process. But from target SDKs v8.0 (Oreo/API 26) and above Android ignores these configurations on Manifest (except some system implicit actions). So this means that the IntentFilter annotations only works for those exceptions, and to make your broadcast receivers receive broadcasts it is required to register them on execution time:

#if DEBUG
[Application(Debuggable=true)]
#else
[Application(Debuggable=false)]
#endif
public class MyApplication: Application
{
    public override void OnCreate ()
    {
        base.OnCreate ();

        Context.RegisterReceiver(new MyReceiver(), new IntentFilter(MyReceiver.MyAciton));
    }
}

It is also possible to register a receiver only for the lifecycle of an Activity, as explained by @Toaster. You can keep sending broadcasts normally:

// ...

ApplicationContext.SendBroadcast(new Intent(MyReceiver.MyAction));

// ...
Curvy answered 14/12, 2018 at 20:33 Comment(1)
Thanks for enriching the post adding xamarin context. You can keep that BroadcastReceiver in a service context beyond end of life of an activity. Just you've to hold the context by any mean, that's it.Discern

© 2022 - 2024 — McMap. All rights reserved.