Zunächst einmal: Ich weiß, dass ConnectivityManager.CONNECTIVITY_ACTION
veraltet ist und ich weiß, wie connectivityManager.registerNetworkCallback
verwendet wird. Außerdem, wenn Sie über JobScheduler
gelesen haben, aber ich bin mir nicht ganz sicher, ob ich es richtig verstanden habe.
Mein Problem ist, dass ich einen Code ausführen möchte, wenn das Telefon mit einem Netzwerk verbunden ist oder nicht. Dies sollte passieren, wenn sich die App auch im Hintergrund befindet. Beginnend mit Android O muss ich eine Benachrichtigung anzeigen, wenn ich im Hintergrund einen Dienst ausführen möchte, was ich vermeiden möchte.
Ich habe versucht, Informationen zu erhalten, wenn das Telefon die JobScheduler/JobService
-APIs verwendet/trennt, aber es wird nur ausgeführt, wenn ich es plane. Für mich scheint es, als könnte ich keinen Code ausführen, wenn ein solches Ereignis passiert. Gibt es eine Möglichkeit, dies zu erreichen? Muss ich meinen Code vielleicht nur ein bisschen anpassen?
Mein 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;
}
Ich starte den Dienst 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 (Zusammenfassung):
<?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>
Ich denke, es muss eine einfache Lösung dafür geben, aber ich kann sie nicht finden.
Zweite Änderung: Ich benutze jetzt den JobDispatcher von firebase und funktioniert perfekt auf allen Plattformen (danke @cutiko). Dies ist die Grundstruktur:
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;
}
}
Ich nenne es so (in meinem 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: Ich habe einen gehackten Weg gefunden. Wirklich hackig zu sagen. Es funktioniert, aber ich würde es nicht verwenden:
stopForeground
. Der Benutzer sieht die Benachrichtigung nicht, aber Android registriert sie als im VordergrundstartService
onTaskRemoved
nicht aufheben, wird sie weiter ausgeführt.Dies startet effektiv einen Vordergrunddienst, der einen Hintergrunddienst startet und dann beendet wird. Der zweite Dienst überlebt den ersten. Ich bin nicht sicher, ob dies beabsichtigt ist oder nicht (vielleicht sollte ein Fehlerbericht eingereicht werden?), Aber es ist eine Problemumgehung (wieder eine schlechte!).
Anscheinend ist es nicht möglich, benachrichtigt zu werden, wenn sich die Verbindung ändert:
CONNECTIVITY_ACTION
-Empfängern, die im Manifest deklariert sind, werden keine Broadcasts empfangen. Darüber hinaus empfangen programmgesteuert deklarierte Empfänger die Broadcasts nur, wenn der Empfänger im Hauptthread registriert wurde (die Verwendung eines Dienstes funktioniert daher nicht). Wenn Sie weiterhin Updates im Hintergrund erhalten möchten, können Sie connectivityManager.registerNetworkCallback
verwenden. All dies ermöglicht diese Lösungen:
Das ist nicht möglich:
connectivityManager.registerNetworkCallback(NetworkInfo, PendingIntent)
kann nicht verwendet werden, da das PendingIntent seine Aktion sofort ausführt, wenn die Bedingung erfüllt ist. es wird nur einmal aufgerufen Da ich bereits einen VPNService habe, der im Vordergrundmodus läuft (und somit eine Benachrichtigung anzeigt), habe ich implementiert, dass der Dienst zur Überprüfung der Konnektivität im Vordergrund ausgeführt wird, falls dies nicht der Fall ist, und umgekehrt. Dies zeigt immer eine Benachrichtigung, aber nur eine.
Alles in allem finde ich das 8.0-Update äußerst unbefriedigend. Es scheint, als müsste ich entweder unzuverlässige Lösungen verwenden (JobService wiederholen) oder meine Benutzer mit einer permanenten Benachrichtigung unterbrechen. Benutzer sollten in der Lage sein, Apps auf die Whitelist zu setzen (Allein die Tatsache, dass es Apps gibt, die die Benachrichtigung "XX wird im Hintergrund ausgeführt" verdeckt, sollte genug sagen. So viel zum Off-Topic
Fühlen Sie sich frei, meine Lösung zu erweitern oder mir Fehler zu zeigen, aber dies sind meine Ergebnisse.
ich muss einige Änderungen an Ch4t4r Jobservice hinzufügen und für mich arbeiten
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) {
}
}
}
und Sie können den Jobservice von einem anderen normalen Dienst oder boot_complete-Empfänger aus aufrufen
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);
}
im Manifest ...
<service
Android:name=".Updater"
Android:permission="Android.permission.BIND_JOB_SERVICE"
Android:exported="true"/>