Detect Connectivity Change in Android 7 and Above When App Is Killed/In Background

Detect CONNECTIVITY CHANGE in Android 7 and above when app is killed/in background

Nougat and Above:
We have to use JobScheduler and JobService for Connection Changes.

All I can divide this into three steps.

Register JobScheduler inside activity. Also, Start JobService(
Service to handle callbacks from the JobScheduler. Requests scheduled
with the JobScheduler ultimately land on this service's "onStartJob"
method.)

public class NetworkConnectionActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network_connection);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

scheduleJob();

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void scheduleJob() {
JobInfo myJob = new JobInfo.Builder(0, new ComponentName(this, NetworkSchedulerService.class))
.setRequiresCharging(true)
.setMinimumLatency(1000)
.setOverrideDeadline(2000)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.build();

JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(myJob);
}

@Override
protected void onStop() {
// A service can be "started" and/or "bound". In this case, it's "started" by this Activity
// and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call
// to stopService() won't prevent scheduled jobs to be processed. However, failing
// to call stopService() would keep it alive indefinitely.
stopService(new Intent(this, NetworkSchedulerService.class));
super.onStop();
}

@Override
protected void onStart() {
super.onStart();
// Start service and provide it a way to communicate with this class.
Intent startServiceIntent = new Intent(this, NetworkSchedulerService.class);
startService(startServiceIntent);
}
}

The service to start and finish the job.

public class NetworkSchedulerService extends JobService implements
ConnectivityReceiver.ConnectivityReceiverListener {

private static final String TAG = NetworkSchedulerService.class.getSimpleName();

private ConnectivityReceiver mConnectivityReceiver;

@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service created");
mConnectivityReceiver = new ConnectivityReceiver(this);
}

/**
* When the app's NetworkConnectionActivity is created, it starts this service. This is so that the
* activity and this service can communicate back and forth. See "setUiCallback()"
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand");
return START_NOT_STICKY;
}

@Override
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "onStartJob" + mConnectivityReceiver);
registerReceiver(mConnectivityReceiver, new IntentFilter(Constants.CONNECTIVITY_ACTION));
return true;
}

@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "onStopJob");
unregisterReceiver(mConnectivityReceiver);
return true;
}

@Override
public void onNetworkConnectionChanged(boolean isConnected) {
String message = isConnected ? "Good! Connected to Internet" : "Sorry! Not connected to internet";
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();

}
}

Finally, The receiver class which checks the network connection
changes.

public class ConnectivityReceiver extends BroadcastReceiver {

private ConnectivityReceiverListener mConnectivityReceiverListener;

ConnectivityReceiver(ConnectivityReceiverListener listener) {
mConnectivityReceiverListener = listener;
}

@Override
public void onReceive(Context context, Intent intent) {
mConnectivityReceiverListener.onNetworkConnectionChanged(isConnected(context));

}

public static boolean isConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}

public interface ConnectivityReceiverListener {
void onNetworkConnectionChanged(boolean isConnected);
}
}

Don't forget to add permission and service inside manifest file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourpackagename">

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

<!-- Always required on api < 21, needed to keep a wake lock while your job is running -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- Required on api < 21 if you are using setRequiredNetworkType(int) -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- Required on all api levels if you are using setPersisted(true) -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".connectivity.NetworkConnectionActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

<!-- Define your service, make sure to add the permision! -->
<service
android:name=".connectivity.NetworkSchedulerService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"/>
</application>

</manifest>

Please refer below links for more info.

https://github.com/jiteshmohite/Android-Network-Connectivity

https://github.com/evant/JobSchedulerCompat

https://github.com/googlesamples/android-JobScheduler

https://medium.com/@iiro.krankka/its-time-to-kiss-goodbye-to-your-implicit-broadcastreceivers-eefafd9f4f8a

Detect Connectivity Change When App is Killed/In Background

Is it possible to achieve this with the Job Scheduler?

Answer - I don't know BUT

You can use WorkManager. That is part of Android Jetpack.

There are 2 options.

1) OneTimeWorkRequest (that what you need).

2) PeriodicWorkRequest (executes periodically, min time is 15 minutes, same as jobScheduler).

WorkManager executes only 10 seconds, as jobscheduler.

Why is WorkManager good. Image in below will give answer.
Sample Image

Sample Image

Documentation - https://developer.android.com/topic/libraries/architecture/workmanager

Detect connectivity changes on Android 7.0 Nougat when app is in foreground

Apps that are running can still listen for CONNECTIVITY_CHANGE on their main thread if they request notification with a BroadcastReceiver.

https://developer.android.com/about/versions/nougat/android-7.0-changes.html

Android O - Detect connectivity change in background

Second edit: I'm now using firebase's JobDispatcher and it works perfect across all platforms (thanks @cutiko). This is the basic structure:

public class ConnectivityJob extends JobService{

@Override
public boolean onStartJob(JobParameters job) {
LogFactory.writeMessage(this, LOG_TAG, "Job created");
connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
connectivityManager.registerNetworkCallback(new NetworkRequest.Builder().build(), networkCallback = new ConnectivityManager.NetworkCallback(){
// -Snip-
});
}else{
registerReceiver(connectivityChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handleConnectivityChange(!intent.hasExtra("noConnectivity"), intent.getIntExtra("networkType", -1));
}
}, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}

NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
if (activeNetwork == null) {
LogFactory.writeMessage(this, LOG_TAG, "No active network.");
}else{
// Some logic..
}
LogFactory.writeMessage(this, LOG_TAG, "Done with onStartJob");
return true;
}

@Override
public boolean onStopJob(JobParameters job) {
if(networkCallback != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)connectivityManager.unregisterNetworkCallback(networkCallback);
else if(connectivityChange != null)unregisterReceiver(connectivityChange);
return true;
}

private void handleConnectivityChange(NetworkInfo networkInfo){
// Calls handleConnectivityChange(boolean connected, int type)
}

private void handleConnectivityChange(boolean connected, int type){
// Calls handleConnectivityChange(boolean connected, ConnectionType connectionType)
}

private void handleConnectivityChange(boolean connected, ConnectionType connectionType){
// Logic based on the new connection
}

private enum ConnectionType{
MOBILE,WIFI,VPN,OTHER;
}
}

I call it like so (in my boot receiver):

Job job = dispatcher.newJobBuilder()
.setService(ConnectivityJob.class)
.setTag("connectivity-job")
.setLifetime(Lifetime.FOREVER)
.setRetryStrategy(RetryStrategy.DEFAULT_LINEAR)
.setRecurring(true)
.setReplaceCurrent(true)
.setTrigger(Trigger.executionWindow(0, 0))
.build();

Edit: I found a hacky way. Really hacky to say. It works but I wouldn't use it:

  • Start a foreground service, go into foreground mode using startForeground(id, notification) and use stopForeground after that, the user won't see the notification but Android registers it as having been in the foreground
  • Start a second service, using startService
  • Stop the first service
  • Result: Congratulations, you have a service running in the background (the one you started second).
  • onTaskRemoved get's called on the second service when you open the app and it is cleared from RAM, but not when the first service terminates. If you have an repeating action like a handler and don't unregister it in onTaskRemoved it continues to run.

Effectively this starts a foreground service, which starts a background service and then terminates. The second service outlives the first one. I'm not sure whether this is intended behavior or not (maybe a bug report should be filed?) but it's a workaround (again, a bad one!).


Looks like it's not possible to get notified when the connection changes:

  • With Android 7.0 CONNECTIVITY_ACTION receivers declared in the manifest won't receive broadcasts. Additionally receivers declared programmatically only receive the broadcasts if the receiver was registered on the main thread (so using a service won't work). If you still want to receive updates in the background you can use connectivityManager.registerNetworkCallback
  • With Android 8.0 the same restrictions are in place but in addition you can't launch services from the background unless it's a foreground service.

All of this allows these solutions:

  • Start a foreground service and show a notification

    • This most likely bothers users
  • Use JobService and schedule it to run periodically

    • Depending on your setting it takes some time until the service get's called thus a few seconds could have passed since the connection changes. All in all this delays the action which should happen on connection change

This is not possible:

  • connectivityManager.registerNetworkCallback(NetworkInfo, PendingIntent) cannot be used because the PendingIntent executes it's action instantly if the condition is met; it's only called once

    • Trying to start a foreground service this way which goes to the foreground for 1 ms and re-registers the call results in something comparable to infinite recursion

Since I already have a VPNService which runs in foreground mode (and thus shows a notification) I implemented that the Service to check connectivity runs in foreground if the VPNService doesn't and vice-versa. This always shows a notification, but only one.
All in all I find the 8.0 update extremely unsatisfying, it seems like I either have to use unreliable solutions (repeating JobService) or interrupt my users with a permanent notification. Users should be able to whitelist apps (The fact alone that there are apps which hide the "XX is running in background" notification should say enough). So much for off-topic

Feel free to expand my solution or show me any errors, but these are my findings.

Detect network state and Upload video data from background when app is killed in android pie (API 28)

JobScheduler or WorkManager has a timeout of 10 minutes. If the upload takes longer than that, then you must use Foreground Service!

Considering the device's limited resources, starting from Android 8.0, Google has taken some strict measures in managing background task for the app. For any long running task, Apps must use Foreground Service so that users are always aware of it.

So after scheduling your task using either WorkManager or JobScheduler, you can start a Foreground Service in which you can do the process of uploading the video. (reference)

and in your Service you can use ConnectivityManager.registerNetworkCallback to listen for network connection. (reference)



Related Topics



Leave a reply



Submit