web-dev-qa-db-ja.com

Android O-バックグラウンドで接続の変更を検出

最初に:ConnectivityManager.CONNECTIVITY_ACTIONが非推奨になったことと、connectivityManager.registerNetworkCallbackの使用方法を知っています。さらに、JobSchedulerについて読んだとしても、それが正しいかどうかはわかりません。

私の問題は、電話がネットワークに接続/切断されたときにコードを実行したいということです。これは、アプリがバックグラウンドにあるときにも発生するはずです。 Android Oで始まるO回避したいことをバックグラウンドでサービスを実行したい場合、通知を表示する必要があります。
JobScheduler/JobService AP​​Iを使用して電話が接続/切断するときに情報を取得しようとしましたが、スケジュールした時間にのみ実行されます。私にとっては、このようなイベントが発生するとコードを実行できないようです。これを達成する方法はありますか?コードを少し調整する必要があるのでしょうか?

私の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>

これには簡単な解決策が必要だと思いますが、見つけられません。

13
Ch4t4r

2番目の編集:現在、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、notification)を使用してフォアグラウンドモードに入り、その後stopForegroundを使用すると、ユーザーには通知が表示されませんが、Androidとして登録します前景にいた
  • startServiceを使用して、2番目のサービスを開始します
  • 最初のサービスを停止します
  • 結果:おめでとうございます。バックグラウンドで実行されているサービスがあります(2番目に開始したサービス)。
  • onTaskRemoved getは、アプリを開いたときに2番目のサービスで呼び出され、RAMからクリアされますが、最初のサービスが終了したときではありません。ハンドラーのような繰り返しアクションがあり、onTaskRemovedで登録を解除しない場合、実行は継続されます。

事実上、これはフォアグラウンドサービスを開始し、バックグラウンドサービスを開始してから終了します。2番目のサービスは最初のサービスよりも長持ちします。これが意図された動作であるかどうかはわかりません(バグレポートを提出する必要がありますか?)しかし、それは回避策です(これも悪いことです!


接続が変更されたときに通知を受け取ることができないようです:

  • Android 7.0 _CONNECTIVITY_ACTION_受信機はマニフェストで宣言されません。さらに、プログラムで宣言された受信機は、受信機がメインスレッドに登録されている場合にのみ放送を受信します。まだ動作しません。バックグラウンドで更新を受信したい場合は、_connectivityManager.registerNetworkCallback_を使用できます。
  • Android 8.0では同じ制限が適用されますが、さらに、フォアグラウンドサービスでない限り、バックグラウンドからサービスを起動できません。

これにより、次のソリューションが可能になります。

  • フォアグラウンドサービスを開始し、通知を表示します
    • これはおそらくユーザーを悩ます
  • JobServiceを使用し、定期的に実行するようにスケジュールします
    • 設定によっては、サービスが呼び出されるまでに時間がかかるため、接続が変更されてから数秒経過する可能性があります。全体として、これは接続の変更時に発生するアクションを遅らせます

これは不可能です:

  • connectivityManager.registerNetworkCallback(NetworkInfo, PendingIntent)は、条件が満たされた場合にPendingIntentがアクションを即座に実行するため使用できません。一度だけ呼び出されます
    • この方法でフォアグラウンドサービスを開始して1ミリ秒間フォアグラウンドに移動し、呼び出しを再登録すると、無限再帰に匹敵する結果が得られます。

フォアグラウンドモードで実行される(したがって通知を表示する)VPNServiceが既にあるので、VPNServiceが存在しない場合に接続を確認するサービスがフォアグラウンドで実行されるように実装しました。これは常に通知を表示しますが、通知は1つだけです。
全体として、8.0アップデートは非常に不満だと感じています。信頼性の低いソリューションを使用する(JobServiceを繰り返す)か、永続的な通知でユーザーを中断する必要があるようです。ユーザーはアプリをホワイトリストに登録できる必要があります(「XXはバックグラウンドで実行されています」という通知を非表示にするアプリがあるという事実だけでも十分です)。オフトピックのためにそんなに

ソリューションを拡張したり、エラーを表示したりしてください。ただし、これらは私の調査結果です。

11
Ch4t4r

ch4t4rジョブサービスにいくつかの変更を加えて、私のために働く必要があります

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"/>