First off: I know that ConnectivityManager.CONNECTIVITY_ACTION has been deprecated and I know how to use connectivityManager.registerNetworkCallback. Furthermore if read about JobScheduler, but I am not entirely sure whether I got it right.
My problem is that I want to execute some code when the phone is connected / disconnected to/from a network. This should happen when the app is in the background as well. Starting with Android O I would have to show a notification if I want to run a service in the background what I want to avoid. I tried getting info on when the phone connects / disconnects using the JobScheduler/JobService APIs, but it only gets executed the time I schedule it. For me it seems like I can't run code when an event like this happens. Is there any way to achieve this? Do I maybe just have to adjust my code a bit?
My JobService:
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class ConnectivityBackgroundServiceAPI21 extends JobService {
#Override
public boolean onStartJob(JobParameters jobParameters) {
LogFactory.writeMessage(this, LOG_TAG, "Job was started");
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
if (activeNetwork == null) {
LogFactory.writeMessage(this, LOG_TAG, "No active network.");
}else{
// Here is some logic consuming whether the device is connected to a network (and to which type)
}
LogFactory.writeMessage(this, LOG_TAG, "Job is done. ");
return false;
}
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
LogFactory.writeMessage(this, LOG_TAG, "Job was stopped");
return true;
}
I start the service like so:
JobScheduler jobScheduler = (JobScheduler)context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName service = new ComponentName(context, ConnectivityBackgroundServiceAPI21.class);
JobInfo.Builder builder = new JobInfo.Builder(1, service).setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).setRequiresCharging(false);
jobScheduler.schedule(builder.build());
jobScheduler.schedule(builder.build()); //It runs when I call this - but doesn't re-run if the network changes
Manifest (Abstract):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.VIBRATE" />
<application>
<service
android:name=".services.ConnectivityBackgroundServiceAPI21"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
</application>
</manifest>
I guess there has to be an easy solution to this but I am not able to find it.
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.
i've have to add some modifications to Ch4t4r jobservice and work for me
public class JobServicio extends JobService {
String LOG_TAG ="EPA";
ConnectivityManager.NetworkCallback networkCallback;
BroadcastReceiver connectivityChange;
ConnectivityManager connectivityManager;
#Override
public boolean onStartJob(JobParameters job) {
Log.i(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() { //this is not necesary if you declare the receiver in manifest and you using android <=6.0.1
#Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "recepcion", Toast.LENGTH_SHORT).show();
handleConnectivityChange(!intent.hasExtra("noConnectivity"), intent.getIntExtra("networkType", -1));
}
}, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
Log.i(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)
Toast.makeText(this, "erga", Toast.LENGTH_SHORT).show();
}
private void handleConnectivityChange(boolean connected, ConnectionType connectionType){
// Logic based on the new connection
}
private enum ConnectionType{
MOBILE,WIFI,VPN,OTHER;
}
ConnectivityManager.NetworkCallback x = new ConnectivityManager.NetworkCallback() { //this networkcallback work wonderfull
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
#Override
public void onAvailable(Network network) {
Log.d(TAG, "requestNetwork onAvailable()");
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
//do something
}
else {
//This method was deprecated in API level 23
ConnectivityManager.setProcessDefaultNetwork(network);
}
}
#Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
Log.d(TAG, ""+network+"|"+networkCapabilities);
}
#Override
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
Log.d(TAG, "requestNetwork onLinkPropertiesChanged()");
}
#Override
public void onLosing(Network network, int maxMsToLive) {
Log.d(TAG, "requestNetwork onLosing()");
}
#Override
public void onLost(Network network) {
}
}
}
and you can call the jobservice from another normal service or boot_complete receiver
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
Job job = dispatcher.newJobBuilder()
.setService(Updater.class)
.setTag("connectivity-job")
.setLifetime(Lifetime.FOREVER)
.setRetryStrategy(RetryStrategy.DEFAULT_LINEAR)
.setRecurring(true)
.setReplaceCurrent(true)
.setTrigger(Trigger.executionWindow(0, 0))
.build();
dispatcher.mustSchedule(job);
}
in manifest...
<service
android:name=".Updater"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
Related
In my project if there was no internet connection when the app was killed I want the user to receive a notification when there is an internet connection available. I was doing this by starting a service on the onCreate() of the Activity , and then once the app was killed and the service onTaskRemoved was triggered , the following code would happen. But as you can see I basically have a while structure that runs while there is no connection and there is a connection ( and the while structure ends) the notification is launched. But I don't think that this is the right approach to the code and actually it only works if the gap between killing the app and making a wifi connection is small. Also I believe that the code runs in the main thread because I only get "Application Terminated" on the Terminal once the notification is launched. How can I fix this? Once again, I want the user to get a notification once there is an internet connection available after the app was killed.
public void onTaskRemoved(Intent rootIntent) {
if(!isConnected(this)) {
while (!isConnected(this)) {
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, Home.CHANNEL_ID).setSmallIcon(R.mipmap.ic_launcher).setContentTitle("Network").setContentText("Network Available");
NotificationManager nNotifyMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nNotifyMgr.notify(1, builder.build());
}
}
If your app is killed and you still want to get notification via your app weather device is connected with wifi then you need to use Broad cast Receiver. Broad cast receiver works weather your app is on back ground, foreground or killed. You can use below code
1) Create a class
public class ConnectivityReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
if (checkWifiConnect()) {
Log.d(TAG, "wifi has connected");
// TODO
}
}
private boolean checkWifiConnect() {
ConnectivityManager manager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo != null
&& networkInfo.getType() == ConnectivityManager.TYPE_WIFI
&& networkInfo.isConnected()) {
return true;
}
return false;
}
}
2) You need to register your receiver in manifest as well. Use below code
<receiver android:name=".your.namepackage.here.ConnectivityReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
Hope this will help you.
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.
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
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.
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.
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.
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>
Problem:
So the problem is that I have an app which sends a request to our backend whenever WiFi is connected (with the connected SSID and other info) or when it is disconnected (over the mobile network). However with the changes in Android 7/N and above, CONNECTIVITY_CHANGE and CONNECTIVITY_ACTION no longer work in the background. Now in most cases people misuse this broadcast and as such I can completely understand why the change was made. However, I have no idea how to solve this problem in the current state.
Now I'm not at all much of an Android developer (this is for a Cordova plugin) so I'm counting on you guys!
Expected behavior:
App is woken up and request is sent whenever WiFi switches connectivity, even when app is killed/in background.
Current behavior:
App only sends request when the app is in the foreground.
Tried so far:
So far I've moved the implicit intent to listen to CONNECTIVITY_ACTION from the manifest to manually registering it in the main part of the app (plugin). This makes it work as long as the app is in memory but not on cold boot or actual background
Already looked at:
Most answers talk about using scheduled jobs to substitute for the missing broadcast. I see how this works for, for example, retrying a download or similar, but not for my case (but please correct me if I'm wrong). Below are the SO posts I've already looked at:
Detect connectivity changes on Android 7.0 Nougat when app is in foreground
ConnectivityManager.CONNECTIVITY_ACTION deprecated
Detect Connectivity change using JobScheduler
Android O - Detect connectivity change 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
The best way to grab Connectivity change Android Os 7 and above is register your ConnectivityReceiver broadcast in Application class like below, This helps you to get changes in background as well until your app alive.
public class MyApplication extends Application {
private ConnectivityReceiver connectivityReceiver;
private ConnectivityReceiver getConnectivityReceiver() {
if (connectivityReceiver == null)
connectivityReceiver = new ConnectivityReceiver();
return connectivityReceiver;
}
#Override
public void onCreate() {
super.onCreate();
registerConnectivityReceiver();
}
// register here your filtters
private void registerConnectivityReceiver(){
try {
// if (android.os.Build.VERSION.SDK_INT >= 26) {
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
//filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
//filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
//filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(getConnectivityReceiver(), filter);
} catch (Exception e) {
MLog.e(TAG, e.getMessage());
}
}
}
And then in manifest
<application
android:name=".app.MyApplication"/>
Here is your ConnectivityReceiver.java
public class ConnectivityReceiver extends BroadcastReceiver {
#Override
public void onReceive(final Context context, final Intent intent) {
MLog.v(TAG, "onReceive().." + intent.getAction());
}
}
That's how i did it. I have created a IntentService and in onCreate method and I have registered networkBroadacst which check for internet connection.
public class SyncingIntentService extends IntentService {
#Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
networkBroadcast=new NetworkBroadcast();
registerReceiver(networkBroadcast,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
#Override
public int onStartCommand(#Nullable Intent intent, int flags, int startId) {
onHandleIntent(intent);
return START_STICKY;
}
}
This is my broadcast class
public class NetworkBroadcast extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (Constants.isInternetConnected(context)) {
// Toast.makeText(context, "Internet Connect", Toast.LENGTH_SHORT).show();
context.startService(new Intent(context, SyncingIntentService.class));
}
else{}
}
}
In this way you can check internet connection in whether your app is in foreground or background in nougat.
Below is excerpt from documentation
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.
So you will get this Broadcast till your context is valid in Android N & above by explicitly registering for same.
Boot Completed:
You can listen android.intent.action.BOOT_COMPLETED broadcast
you will need this permission for same.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
App Killed Scenario:
You are not going to receive it.
That is very much expected and due to various reasons
Android Oreo has limitations on running services in background, so you may face this on O devices
Doze mode on Android Marshmallow onwards can cause this, it will stop all network operations itself & take away CPU wake locks
Though Doze mode have one mechanism for requesting whitelisting of apps, this might be useful for you.
Another approach which is simpler and easier when you use registerNetworkCallback (NetworkRequest, PendingIntent):
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
builder.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
Intent intent = new Intent(this, SendAnyRequestService.class);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
if (connectivityManager != null) {
NetworkRequest networkRequest = builder.build();
connectivityManager.registerNetworkCallback(networkRequest, pendingIntent);
}
Which is SendAnyRequestService.class is your service class, and you can call your API inside it.
This code work for Android 6.0 (API 23) and above
Ref document is here
My app is targeting Android 7, with minimum SDK Android 4.
Hence, listening to CONNECTIVITY_CHANGE (Even when the app is killed) no longer work anymore. What I wish to do is
Even when my main app is killed, when the internet connectivity change from "not available" to "available", I would like to start an alarm broadcast receiver.
I try to achieve with the following code
MainActivity.java
#Override
public void onPause() {
super.onPause();
installJobService();
}
private void installJobService() {
// Create a new dispatcher using the Google Play driver.
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(this));
Job myJob = dispatcher.newJobBuilder()
// the JobService that will be called
.setService(MyJobService.class)
// uniquely identifies the job
.setTag("my-unique-tag")
// one-off job
.setRecurring(true)
// persist forever
.setLifetime(Lifetime.FOREVER)
// start between 0 and 60 seconds from now
.setTrigger(Trigger.executionWindow(0, 60))
// overwrite an existing job with the same tag
.setReplaceCurrent(true)
// retry with exponential backoff
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
// constraints that need to be satisfied for the job to run
.setConstraints(
// only run on any network
Constraint.ON_ANY_NETWORK
)
.build();
dispatcher.mustSchedule(myJob);
}
However,
MyJobService.java
import android.content.Context;
import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;
import org.yccheok.jstock.gui.JStockApplication;
/**
* Created by yccheok on 21/5/2017.
*/
public class MyJobService extends JobService {
#Override
public boolean onStartJob(JobParameters jobParameters) {
Context context = this.getApplicationContext();
android.util.Log.i("CHEOK", "Internet -> " + Utils.isInternetAvailable(context));
// Answers the question: "Is there still work going on?"
return false;
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
// Answers the question: "Should this job be retried?"
return true;
}
}
However, the above code isn't reliable. How I test is
Quit my app.
Kill my app explicitly via Settings using "Force stop".
Turn off internet.
Turn on internet.
Wait for few minutes. MyJobService is never executed.
Is there any reliable way, to use FirebaseJobDispatcher to replace CONNECTIVITY_CHANGE reliably?
I had gone through Firebase JobDispatcher - how does it work compared to previous APIs (JobScheduler and GcmTaskService)? , but I still can't find a way to make it work reliably.
Not sure how you are detecting CONNECTIVITY_CHANGE through FirebaseJobDispatcher but for same situation I had used broadcast
public class ConnectivityStateReceiver extends BroadcastReceiver {
String TAG = "MyApp";
#Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent(context, NetworkService.class);
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
return;
} else if (cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected()) {
Log.e(TAG, "Connected!");
context.startService(serviceIntent);
} else {
Log.e(TAG, "Not Connected!");
context.stopService(serviceIntent);
}
}
}
I have written the following code for detecting the network status from within the BroadcastReceiver. I start a service when the network is available and stop the service when the network is not available.
I have the following class level variable.
private boolean IsNetworkAlreadyConnected = false;
Within onCreate method of the main class I start the service if the internet is available.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (InternetConnectivity.isConnected(MainActivity.this)) {
IsNetworkAlreadyConnected = true;
Intent timerIntent = new Intent(getBaseContext(), InActivityTimer.class);
startService(timerIntent);
}
}
and below is the code for my BroadcastReceiver in the same class,
public class mConnectivityCheckReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals("android.net.conn.CONNECTIVITY_CHANGE")) {
try {
boolean networkAvailable = InternetConnectivity.isConnected(context);
if (networkAvailable) {
if (!IsNetworkAlreadyConnected) {
Intent timerIntent = new Intent(getBaseContext(), InActivityTimer.class);
startService(timerIntent);
IsNetworkAlreadyConnected = true;
}
else {
Log.d("KC_HomeActivity", "Network was already connected. No need to start service again.");
}
}
else {
Log.d("KC_HomeActivity", "Network Disconnected. Service Stopped.");
IsNetworkAlreadyConnected = false;
Intent timerIntent = new Intent(getBaseContext(), InActivityTimer.class);
stopService(timerIntent);
}
}
catch (Exception e) {
}
}
}
};
When both Mobile data and Wifi are turned on then the service is started from onCreate method and it is not started again in the BroadcastReceiver but when I turn off the Wifi the Android changes the network mode to Mobile Data but for few seconds there is no internet connectivity and the service is stopped and then started again. I don't want to do this. If there is no connectivity only then the service should be stopped. If the network is shifting from Wifi to Mobile Data then the service should not be stopped.
Note: To check the internet connectivity I am using,
NetworkInfo info = InternetConnectivity.getNetworkInfo(context);
return (info != null && info.isConnectedOrConnecting());
Network connections aren't that precise. You should make it relax a bit, or you'll pull your hair out.
I would implement a smoothing function from the broadcasts. When you get a connectivity change notification, set a timeout for like 15 seconds. At that time, check your status and either start, stop, or do nothing. If another broadcast comes in, clear the first and reset for another 15 seconds. That will give the device time to reconnect.