Android O-Detect changement de connectivité en arrière-plan

tout d'abord: je sais que ConnectivityManager.CONNECTIVITY_ACTION a été déprécié et je sais comment utiliser connectivityManager.registerNetworkCallback . En outre, si lu sur JobScheduler , mais je ne suis pas entièrement sûr si j'ai eu raison.

Mon problème est que je veux exécuter du code lorsque le téléphone est connecté / déconnecté à partir d'un réseau. Ce qui doit se passer lorsque l'application est en arrière-plan. En commençant par Android O je devrais montrer une notification si je veux exécuter un service dans le arrière-plan de ce que je veux éviter.

j'ai essayé d'obtenir des informations sur quand le téléphone se connecte / se déconnecte à l'aide de L'API JobScheduler/JobService , mais il n'est exécuté que le temps que je le programme. Pour moi, il semble que je ne peux pas exécuter de code quand un événement comme celui-ci se produit. Est-il un moyen pour y parvenir? Est-ce que je dois peut-être juste ajuster un peu mon code?

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

j'ai démarrer le service comme suit:

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

Manifeste (Abrégé):

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

je suppose qu'il doit y avoir une solution facile à cela, mais je ne suis pas en mesure de le trouver.

6
demandé sur Ch4t4r 2017-09-11 22:30:57

2 réponses

deuxième édition: j'utilise maintenant le JobDispatcher de firebase et il fonctionne parfaitement sur toutes les plateformes (merci @cutiko). C'est la structure de base:

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

Je l'appelle ainsi (dans mon récepteur de démarrage):

   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: j'ai trouvé un hacky. Vraiment hacky à dire. Cela fonctionne mais je ne l'utiliserais pas:

  • lancer un service avant-plan, passer en mode avant-plan en utilisant startForeground (id, notification) et utiliser stopForeground après cela, l'utilisateur ne verra pas la notification, mais Android l'enregistre comme ayant été au premier plan
  • lancer un second service, en utilisant startService
  • stopper le premier de service
  • résultat: félicitations, vous avez un service en arrière-plan (celui que vous avez démarré en deuxième position).
  • onTaskRemoved get appelé sur le second service quand vous ouvrez l'application, et il est effacé de la mémoire vive, mais pas lorsque le premier service se termine. Si vous avez une action répétitive comme un handler et que vous ne la désinscrivez pas dans onTaskRemoved , elle continue de fonctionner.

effectivement ceci commence un service de premier plan, qui commence un service de fond et se termine. Le second service survit au premier. Je ne suis pas sûr que ce soit un comportement intentionnel ou non (peut-être un rapport de bogue devrait-il être rempli?) mais c'est une contournez (encore une mauvaise!).


on dirait Qu'il n'est pas possible d'être notifié quand la connexion change:

  • avec Android 7.0 CONNECTIVITY_ACTION les récepteurs déclarés dans le manifeste ne recevront pas d'émissions. De plus, les récepteurs déclarés par programmation ne reçoivent les émissions que si le récepteur a été enregistré sur le thread principal (donc l'utilisation d'un service ne fonctionnera pas). Si vous voulez toujours recevoir mises à jour en arrière-plan vous pouvez utiliser connectivityManager.registerNetworkCallback
  • avec Android 8.0 les mêmes restrictions sont en place, mais en outre, vous ne pouvez pas lancer des services à partir de l'arrière-plan à moins que ce soit un service avant-plan.

tout cela permet ces solutions:

  • lancer un service de premier plan et afficher une notification
    • cela dérange très probablement les utilisateurs
  • utilisez JobService et programmez-le pour fonctionner périodiquement
    • selon votre réglage, il faut un certain temps jusqu'à ce que le service s'appelle ainsi quelques secondes auraient pu passer depuis le changement de connexion. Tout dans tout cela retarde l'action qui devrait se produire sur le changement de connexion

ce n'est pas possible:

  • connectivityManager.registerNetworkCallback(NetworkInfo, PendingIntent) ne peut pas être utilisé parce que le PendingIntent exécute son action instantanément si la condition est remplie; il n'est appelé qu'une fois
    • essayer de lancer un service de premier plan de cette façon qui va au premier plan pendant 1 ms et ré-enregistre les résultats de l'appel dans quelque chose comparable à la récursion infinie

étant donné que j'ai déjà un service VPNService qui fonctionne en mode premier plan (et donc affiche une notification) j'ai mis en œuvre que le Service à vérifier la connectivité est au premier plan si le service VPNService ne le fait pas et vice-versa. Ceci montre toujours une notification, mais seulement une.

dans l'ensemble, je trouve la mise à jour 8.0 extrêmement insatisfaisante, il semble que je doive soit utiliser des solutions peu fiables (service de répétition des tâches) ou interrompre mes utilisateurs avec une notification permanente. Les utilisateurs devraient être en mesure de blanchir les applications (le fait qu'il existe des applications qui masquent la notification "XX est en cours d'exécution en arrière-plan" devrait suffire). Si beaucoup de hors-sujet



N'hésitez pas à étendre ma solution ou me montrer des erreurs, mais ce sont mes conclusions.

7
répondu Ch4t4r 2018-09-25 13:22:44

je dois ajouter quelques modifications à Ch4t4r jobservice et travailler pour moi

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

et vous pouvez appeler le jobservice d'un autre service normal ou 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);
        }

dans le manifeste...

    <service
        android:name=".Updater"
        android:permission="android.permission.BIND_JOB_SERVICE"
        android:exported="true"/>
0
répondu Jesus Adrian Gerardus Ruiz 2018-02-09 05:09:19