Network Changed Broadcast Receiver does not execut

2020-04-11 13:37发布

问题:

I have a BroadcastReciever name NetworkReciver.java that executes when Internet is Connected or Disconnected. And it is working well.

But when app is closed from recent apps, then NetworkReciver.java does not executes in One Plus 6 Phone while it works proper in Samsung Phones.

I am not getting why the behavior is different in One Plus Device

My Code:

Manifest

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


  <receiver android:name=".NetworkReciever" >
       <intent-filter>
          <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
       </intent-filter>
  </receiver>

NetworkReciever.java:

  public class NetworkReciever extends BroadcastReceiver 
  {

       @Override
     public void onReceive(Context context, Intent intent)
     {
            Log.i("TAG", "Network REceiver Executed");
     }
}

Problem:

NetworkReciever does not execute when app is closed from recent apps in One Plus Device.

回答1:

Starting in Android N, the system does not send CONNECTIVITY_ACTION broadcasts to manifest receivers of applications targeting N+.

Explicit BroadcastReceivers registered via Context.registerReceiver() continue to receive these broadcasts.

Solution: See ConnectivityManager.CONNECTIVITY_ACTION deprecated



回答2:

Apps targeting Android 7.0+ do not receive CONNECTIVITY_ACTION broadcasts if they register to receive them in their manifest, and processes that depend on this broadcast will not start.

So, if you want to do some work when internet connection is available. You can use Job scheduler or work manager.

For example, here is sample code for job scheduler.

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
      (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo job = new JobInfo.Builder(
    MY_BACKGROUND_JOB,
    new ComponentName(context, MyJobService.class))
      .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
      .setRequiresCharging(true)
      .build();
  js.schedule(job);
}

When the conditions for your job are met, your app receives a callback to run the onStartJob() method in the specified JobService.class

Android JobScheduler Sample

Also, registering broadcasts in the activity's onCreate and unregistering it in onDestroy will not work for your case as you will not receive the broadcast after the app is killed.



回答3:

In Android Nougat, Android does not broadcast for network changes to manifest registered BroadcastReceiver.

From the Android Nogout Changes & Also mentioned in ConnectivityManager

Monitor for changes in connectivity

Apps targeting Android 7.0 (API level 24) and higher do not receive CONNECTIVITY_ACTION broadcasts if they declare the broadcast receiver in their manifest. Apps will still receive CONNECTIVITY_ACTION broadcasts if they register their BroadcastReceiver with Context.registerReceiver() and that context is still valid.

Solution

NetworkReciever does not execute when app is closed from recent apps

I don't know why you want to get network changes after the app is closed. But in this case you have to make some periodic task with WorkManager or JobScheduler. I suggest you use WorkManager because it will work for all devices. whether JobScheduler is available only for devices >= 21 version. Here is a good example for WorkManager (It is quite easy).

Background solution (execute when only you need)

public class MyWorker extends Worker {
    @Override
    public Worker.Result doWork() {

        // get online status

         boolean isOnline =  isOnline(getApplicationContext());

        // Indicate success or failure with your return value:
        return Result.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }

    public boolean isOnline(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        //should check null because in airplane mode it will be null
        return (netInfo != null && netInfo.isConnected());
    }
}

and schedule this Work at app start.

public static void scheduleWork() {

    int TIME_INTERVAL_IN_SECONDS = 15;
    PeriodicWorkRequest.Builder photoCheckBuilder = new PeriodicWorkRequest.Builder(MyWorker .class, TIME_INTERVAL_IN_SECONDS, TimeUnit.SECONDS);
    PeriodicWorkRequest photoCheckWork = photoCheckBuilder.build();
    WorkManager instance = WorkManager.getInstance();
    if (instance != null) {
        instance.enqueueUniquePeriodicWork("TAG", ExistingPeriodicWorkPolicy.KEEP, photoCheckWork);
    }
}

Foreground solution (recommended)

Or if you just want to receive network changes when you app is live. You can below solution.

  • Register this receiver in your BaseActivity. or create one if you don't have yet any BaseActivity.
  • Register on onStart() and unregister on onStop(). Because you may not want to invoke your UI after onStop().

Here is BaseActivity.java

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

/**
 * Created by KHEMRAJ on 9/5/2018.
 */
public class BaseActivity extends AppCompatActivity {
    NetworkReceiver receiver;
    public boolean isOnline;

    @Override
    protected void onStart() {
        super.onStart();
        isOnline = isOnline(this);
        // register network change receiver
        receiver = new NetworkReceiver();
        registerReceiver(receiver, new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"));
    }

    @Override
    protected void onStop() {
        super.onStop();
        // unregister network change receiver
        unregisterReceiver(receiver);
        receiver = null;
    }

    public class NetworkReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            isOnline = isOnline(context);
            Log.i("TAG", "Network REceiver Executed");
        }
    }

    public boolean isOnline(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        //should check null because in airplane mode it will be null
        return (netInfo != null && netInfo.isConnected());
    }
}

I suggested you WorkManger only, because I created a sample earlier days with JobScheduler, EvernoteJobs, AlarmManager, [JobService][7], and WorkManager. In which I started periodic task of 15 minutes with each of these. and wrote logs of each in separate file when invoked.

Conclusion of this test was that. WorkManager and EvernoteJobs were most efficient to do jobs. Now because EvernoteJobs will use WorkManager from next version. So I came up with WorkManager.



回答4:

Root cause:

From Android N OnePlus introduced a feature similar to Mi devices which prevent certain apps from auto-starting after reboot. I suspect that same feature is preventing your app to receive BroadcastReceiver as well.

Solution

Use AccessibilityService service in your app and ask user to turn on AccessibilityService for your app from Settings and boing doing this BroadcastReceiver in your app will work as expected.

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.

Here is how you can register your own AccessibilityService.

Create your custom AccessibilityService

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {//do nothing }

    @Override
    public void onInterrupt() { //do nothing}
}

Create configuration file my_accessibility_service.xml and add below code:

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

Add permission to AndroidManifest.xml file:

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

Add your AccessibilityService in AndroidManifest.xml file:

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

You done!

Below is method to check status of AccessibilityService:

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

Note: I have not tried but it may help.



回答5:

Broadcast Receiver is not supported in Oreo as manifest tag, you must have to register it as a Service/ Activity with context.registerReceiver(). Or you use the WorkManager to schedule something for specific network conditions.

use this code in OnCreate

    NetworkReciever  receiver = NetworkReciever ()
    IntentFilter filter = new IntentFilter();
    filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
    registerReceiver(receiver, filter);

don't forget to unregister it in onDestroy

@Override
 protected void onDestroy() {
  if (receiver != null) {
   unregisterReceiver(receiver);
   receiver = null;
  }
  super.onDestroy();
 }

and delete this from Manifest

<receiver android:name=".NetworkReciever" >
       <intent-filter>
          <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
       </intent-filter>
  </receiver>