Android O - 检测后台连接的变化

时间:2017-09-11 19:30:57

标签: android android-service android-8.0-oreo

首先:我知道ConnectivityManager.CONNECTIVITY_ACTION已被弃用,我知道如何使用connectivityManager.registerNetworkCallback。此外,如果阅读JobScheduler,但我不完全确定我是否做对了。

我的问题是,当手机与网络连接/断开连接时,我想执行一些代码。这应该在应用程序也在后台时发生。从Android OI开始,如果我想在后台运行我想要避免的服务,则必须显示通知。
我尝试获取有关何时使用JobScheduler/JobService API连接/断开手机的信息,但它只会在我安排它的时候执行。对我来说,似乎在这样的事件发生时我无法运行代码。有没有办法实现这个目标?我可能只需稍微调整一下我的代码吗?

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

Manifest(摘要):

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

我想必须有一个简单的解决方案,但我无法找到它。

2 个答案:

答案 0 :(得分:9)

第二次编辑:我现在正在使用firebase的JobDispatcher,它在所有平台上都很完美(感谢@cutiko)。这是基本结构:

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

我这样称呼(在我的启动接收器中):

   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();

编辑:我发现了一种黑客的方式。真的很难说。它有效,但我不会用它:

  • 启动前台服务,使用startForeground(id,通知)进入前台模式,然后使用stopForeground,用户不会看到通知,但Android会将其注册为处于前台< / LI>
  • 使用startService
  • 开始第二项服务
  • 停止第一项服务
  • 结果:恭喜,您有一个在后台运行的服务(您在第二个开始运行的服务)。
  • onTaskRemoved在打开应用程序时调用第二个服务并从RAM中清除,但在第一个服务终止时则不会。如果您有像处理程序这样的重复操作,并且未在onTaskRemoved中取消注册,则会继续运行。

实际上这会启动一个前台服务,它启动后台服务然后终止。第二项服务比第一项服务更长。我不确定这是否是预期的行为(也许应该提交错误报告?)但它是一种解决方法(再次,一个坏的!)。

看起来连接发生变化时无法收到通知:

  • 在清单中声明的​​Android 7.0 CONNECTIVITY_ACTION接收器不会接收广播。另外,如果接收器在主线程上注册,则以编程方式声明的接收器仅接收广播(因此使用服务不能工作)。如果您仍想在后台接收更新,可以使用connectivityManager.registerNetworkCallback
  • Android 8.0也有相同的限制,但除非它是前台服务,否则您无法从后台启动服务。

所有这些都允许这些解决方案:

  • 启动前台服务并显示通知
    • 这很可能困扰用户
  • 使用JobService并安排它定期运行
    • 根据您的设置,调用服务需要一些时间,因此连接更改后可能已经过了几秒钟。总而言之,这会延迟应对连接更改的行动

这是不可能的:

  • connectivityManager.registerNetworkCallback(NetworkInfo, PendingIntent)无法使用,因为如果符合条件,PendingIntent会立即执行该操作;它只被叫一次
    • 尝试以这种方式启动前台服务,前往前台1 ms并重新注册调用结果,与无限递归相似

由于我已经有一个以前台模式运行的VPNService(因此显示了通知),我实现了如果VPNService没有,反之亦然,检查连接的服务是否在前台运行。这总是显示一个通知,但只有一个。
总而言之,我发现8.0更新非常不满意,似乎我要么使用不可靠的解决方案(重复JobService),要么通过永久通知中断我的用户。用户应该能够将应用程序列入白名单(仅此一项事实就是有些应用程序可以隐藏&#34; XX正在后台运行&#34;通知应该说得足够多)。关于主题的内容如此多 随意扩展我的解决方案或向我显示任何错误,但这些是我的发现。

答案 1 :(得分:1)

我必须对Ch4t4r jobservice添加一些修改并为我工作

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) {
}
    }
    }

你可以从另一个普通服务或boot_complete接收器调用jobservice

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

在清单......

    <service
        android:name=".Updater"
        android:permission="android.permission.BIND_JOB_SERVICE"
        android:exported="true"/>