Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
396 views
in Technique[技术] by (71.8m points)

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

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...