Android O-在后台检测连接性更改
首先,我知道ConnectivityManager.CONNECTIVITY_ACTION
已被弃用,并且知道如何使用connectivityManager.registerNetworkCallback
.此外,如果阅读有关JobScheduler
的信息,但我不确定是否正确.
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.
我的问题是,当手机与网络连接/断开连接时,我想执行一些代码.当应用程序也在后台运行时,也应该发生这种情况.从Android OI开始,如果我想在后台运行服务,我必须避免显示.
我尝试获取有关使用JobScheduler/JobService
API进行手机连接/断开连接时的信息,但仅在我安排它的时间被执行.对我来说,当发生这样的事件时,我似乎无法运行代码.有什么办法可以做到这一点?我也许只需要稍微调整一下代码即可吗?
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?
我的JobService:
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;
}
我这样启动服务:
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
清单(摘要):
<?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.
第二次我现在正在使用Firebase的JobDispatcher,它在所有平台上都可以完美运行(感谢@cutiko).这是基本结构:
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();
我发现了一种骇人听闻的方法.真的很客气.它有效,但我不会使用:
I found a hacky way. Really hacky to say. It works but I wouldn't use it:
- 启动前台服务,使用startForeground(id,notification)进入前台模式,然后使用
stopForeground
,用户将看不到通知,但Android将其注册为位于前台. - 使用
startService
启动第二项服务
- 停止第一项服务
- 结果:恭喜,您有一个在后台运行的服务(您第二次启动的服务). 打开应用程序时,在第二个服务上调用
- onTaskRemoved get并从RAM中清除它,但在第一个服务终止时不会调用.如果您有类似处理程序的重复操作,并且没有在
onTaskRemoved
中注销它,它将继续运行.
- 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.
有效地,这将启动前台服务,然后启动前台服务,然后终止.第二项服务比第一种服务有效.我不确定这是否是预期的行为(也许应该提交错误报告?),但这是一种变通方法(同样,是错误的!).
似乎在连接更改时无法收到通知:
Looks like it's not possible to get notified when the connection changes:
- 对于Android 7.0,清单中声明的
CONNECTIVITY_ACTION
接收器将不会接收广播.另外,以编程方式声明的接收器仅在接收器已在主线程上注册的情况下才接收广播(因此,使用服务将不起作用).如果您仍想在后台接收更新,则可以使用connectivityManager.registerNetworkCallback
- 对于Android 8.0,存在相同的限制,但除此之外,除非它是前台服务,否则您不能从后台启动服务.
- 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 useconnectivityManager.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
- 根据您的设置,调用服务要花费一些时间,因此连接更改后可能要过几秒钟.总而言之,这会延迟应在连接更改时执行的操作
这是不可能的:
-
connectivityManager.registerNetworkCallback(NetworkInfo, PendingIntent)
不能使用,因为如果满足条件,则PendingIntent会立即执行其动作;它只被叫过一次- 尝试以这种方式启动前台服务,该服务将在1毫秒内到达前台并重新注册该呼叫,结果类似于无限递归
-
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
由于我已经有一个以前台模式运行的VPNService(因此显示了一条通知),因此我实现了一个检查连接性的服务(如果VPNService没有运行,则在前台运行),反之亦然.总会显示一条通知,但只有一条.
总而言之,我发现8.0更新极其令人不满意,似乎我要么必须使用不可靠的解决方案(重复JobService),要么用永久性通知打扰我的用户.用户应该能够将应用列入白名单(仅存在隐藏"XX在后台运行"通知的应用这一事实就足够了).太离题了
随时扩展我的解决方案或向我显示任何错误,但这是我的发现.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.