当应用程序处于 killed/in 后台时,在 Android 7 及更高版本中检测连接变化
Detect CONNECTIVITY CHANGE in Android 7 and above when app is killed/in background
问题:
所以问题是我有一个应用程序,它会在 WiFi 连接(使用连接的 SSID 和其他信息)或断开连接(通过移动网络)时向我们的后端发送请求。然而,随着 Android 7/N 及更高版本的更改,CONNECTIVITY_CHANGE 和 CONNECTIVITY_ACTION 不再在后台工作。现在在大多数情况下,人们会滥用此广播,因此我完全可以理解为什么要进行更改。但是,我不知道在当前状态下如何解决这个问题。
现在我根本不是 Android 开发人员(这是针对 Cordova 插件的)所以我指望你们了!
预期行为:
只要 WiFi 切换连接,应用程序就会被唤醒并发送请求,即使应用程序处于 killed/in 后台。
当前行为:
应用仅在应用处于前台时发送请求。
目前已尝试:
到目前为止,我已经将清单中隐含的监听 CONNECTIVITY_ACTION 的意图移动到在应用程序的主要部分(插件)中手动注册它。这使得只要应用程序在内存中但不在冷启动或实际后台时它就可以工作
已经看过:
大多数答案都谈到使用预定作业来替代丢失的广播。我知道这是如何工作的,例如,重试下载或类似的,但不适用于我的情况(但如果我错了请纠正我)。以下是我已经看过的 SO 帖子:
Detect Connectivity change using JobScheduler
我就是这样做的。我已经创建了一个 IntentService
和 onCreate
方法,我已经注册了 networkBroadacst
来检查互联网连接。
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;
}
}
这是我的直播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{}
}
}
通过这种方式,您可以检查您的应用程序在牛轧糖中是在前台还是后台的互联网连接。
牛轧糖及以上:
我们必须使用 JobScheduler 和 JobService 进行连接更改。
我可以将其分为三个步骤。
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>
请参阅以下链接了解更多信息。
https://github.com/jiteshmohite/Android-Network-Connectivity
https://github.com/evant/JobSchedulerCompat
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.
因此,通过显式注册,您将收到此广播,直到您的上下文在 Android N 及更高版本有效。
启动完成:
您可以收听android.intent.action.BOOT_COMPLETED
广播
您将需要此权限。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
应用程序被杀死的情况:
您不会收到它。
由于各种原因
,这是非常令人期待的
Android Oreo has limitations 运行 后台服务,因此您可能会在 O 设备上遇到此问题
Doze mode 在 Android Marshmallow 之后可能会导致此问题,它将自行停止所有网络操作并移除 CPU 唤醒锁
虽然打瞌睡模式有一种机制 requesting whitelisting of apps,这可能对你有用。
获取连接更改的最佳方法 Android Os 7 及更高版本是在应用程序 class 中注册您的 ConnectivityReceiver 广播,如下所示,这也有助于您在后台获得更改直到你的应用活着。
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());
}
}
}
然后在清单中
<application
android:name=".app.MyApplication"/>
这是你的ConnectivityReceiver.java
public class ConnectivityReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
MLog.v(TAG, "onReceive().." + intent.getAction());
}
}
使用 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);
}
其中SendAnyRequestService.class
是你的服务class,你可以在里面调用你的API
此代码适用于 Android 6.0 (API 23) 及更高版本
参考文档是 here
问题:
所以问题是我有一个应用程序,它会在 WiFi 连接(使用连接的 SSID 和其他信息)或断开连接(通过移动网络)时向我们的后端发送请求。然而,随着 Android 7/N 及更高版本的更改,CONNECTIVITY_CHANGE 和 CONNECTIVITY_ACTION 不再在后台工作。现在在大多数情况下,人们会滥用此广播,因此我完全可以理解为什么要进行更改。但是,我不知道在当前状态下如何解决这个问题。
现在我根本不是 Android 开发人员(这是针对 Cordova 插件的)所以我指望你们了!
预期行为: 只要 WiFi 切换连接,应用程序就会被唤醒并发送请求,即使应用程序处于 killed/in 后台。
当前行为: 应用仅在应用处于前台时发送请求。
目前已尝试: 到目前为止,我已经将清单中隐含的监听 CONNECTIVITY_ACTION 的意图移动到在应用程序的主要部分(插件)中手动注册它。这使得只要应用程序在内存中但不在冷启动或实际后台时它就可以工作
已经看过: 大多数答案都谈到使用预定作业来替代丢失的广播。我知道这是如何工作的,例如,重试下载或类似的,但不适用于我的情况(但如果我错了请纠正我)。以下是我已经看过的 SO 帖子:
Detect Connectivity change using JobScheduler
我就是这样做的。我已经创建了一个 IntentService
和 onCreate
方法,我已经注册了 networkBroadacst
来检查互联网连接。
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;
}
}
这是我的直播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{}
}
}
通过这种方式,您可以检查您的应用程序在牛轧糖中是在前台还是后台的互联网连接。
牛轧糖及以上: 我们必须使用 JobScheduler 和 JobService 进行连接更改。
我可以将其分为三个步骤。
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>
请参阅以下链接了解更多信息。
https://github.com/jiteshmohite/Android-Network-Connectivity
https://github.com/evant/JobSchedulerCompat
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.
因此,通过显式注册,您将收到此广播,直到您的上下文在 Android N 及更高版本有效。
启动完成:
您可以收听android.intent.action.BOOT_COMPLETED
广播
您将需要此权限。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
应用程序被杀死的情况:
您不会收到它。
由于各种原因
,这是非常令人期待的Android Oreo has limitations 运行 后台服务,因此您可能会在 O 设备上遇到此问题
Doze mode 在 Android Marshmallow 之后可能会导致此问题,它将自行停止所有网络操作并移除 CPU 唤醒锁
虽然打瞌睡模式有一种机制 requesting whitelisting of apps,这可能对你有用。
获取连接更改的最佳方法 Android Os 7 及更高版本是在应用程序 class 中注册您的 ConnectivityReceiver 广播,如下所示,这也有助于您在后台获得更改直到你的应用活着。
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());
}
}
}
然后在清单中
<application
android:name=".app.MyApplication"/>
这是你的ConnectivityReceiver.java
public class ConnectivityReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
MLog.v(TAG, "onReceive().." + intent.getAction());
}
}
使用 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);
}
其中SendAnyRequestService.class
是你的服务class,你可以在里面调用你的API
此代码适用于 Android 6.0 (API 23) 及更高版本
参考文档是 here