web-dev-qa-db-ja.com

Android 7.0以降)画面がオフのときにフォアグラウンドサービスが位置情報の更新を受信しない

Androidデバイスの画面がオフのときにリアルタイムでデバイスの位置データを継続的に記録するアプリケーションを作成しようとしています。私のコードはAndroid 6.0以前で正しく動作しますAndroid 7.0+でアプリが壊れるようです。

Androidフォアグラウンドサービスを実装し、ウェイクロックを使用し、Google FusedLocation APIにサブスクライブします。画面がオフになると、onLocationChangedコールバックはトリガーされません。

誰かがこの問題の解決策を見たことがありますか? Androidアプリのデバイス設定、Googleサービスフレームワーク、フューズドロケーションの両方で、バッテリーの最適化を無効にしてみました。

public class ForegroundLocationService extends Service implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        LocationListener {

    private static final String TAG = ForegroundLocationService.class.getSimpleName();

    // the notification id for the foreground notification
    public static final int GPS_NOTIFICATION = 1;

    // the interval in seconds that gps updates are requested
    private static final int UPDATE_INTERVAL_IN_SECONDS = 15;

    // is this service currently running in the foreground?
    private boolean isForeground = false;

    // the google api client
    private GoogleApiClient googleApiClient;

    // the wakelock used to keep the app alive while the screen is off
    private PowerManager.WakeLock wakeLock;

    @Override
    public void onCreate() {
        super.onCreate();

        // create google api client
        googleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();

        // get a wakelock from the power manager
        final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (!isForeground) {

            Log.v(TAG, "Starting the " + this.getClass().getSimpleName());

            startForeground(ForegroundLocationService.GPS_NOTIFICATION,
                    notifyUserThatLocationServiceStarted());
            isForeground = true;

            // connect to google api client
            googleApiClient.connect();

            // acquire wakelock
            wakeLock.acquire();
        }

        return START_REDELIVER_INTENT;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {

        Log.v(TAG, "Stopping the " + this.getClass().getSimpleName());

        stopForeground(true);
        isForeground = false;

        // disconnect from google api client
        googleApiClient.disconnect();

        // release wakelock if it is held
        if (null != wakeLock && wakeLock.isHeld()) {
            wakeLock.release();
        }

        super.onDestroy();
    }

    private LocationRequest getLocationRequest() {

        LocationRequest locationRequest = LocationRequest.create();

        // we always want the highest accuracy
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        // we want to make sure that we get an updated location at the specified interval
        locationRequest.setInterval(TimeUnit.SECONDS.toMillis(0));

        // this sets the fastest interval that the app can receive updates from other apps accessing
        // the location service. for example, if Google Maps is running in the background
        // we can update our location from what it sees every five seconds
        locationRequest.setFastestInterval(TimeUnit.SECONDS.toMillis(0));
        locationRequest.setMaxWaitTime(TimeUnit.SECONDS.toMillis(UPDATE_INTERVAL_IN_SECONDS));

        return locationRequest;
    }

    private Notification notifyUserThatLocationServiceStarted() {

        // pop up a notification that the location service is running
        Intent notificationIntent = new Intent(this, NotificationActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        final Notification.Builder builder = new Notification.Builder(this)
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentTitle(getString(R.string.foreground_location_service))
                .setContentText(getString(R.string.service_is_running))
                .setContentIntent(pendingIntent)
                .setWhen(System.currentTimeMillis());

        final Notification notification;
        if (Build.VERSION.SDK_INT < 16) {
            notification = builder.getNotification();
        } else {
            notification = builder.build();
        }

        return notification;
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {

        try {

            // request location updates from the fused location provider
            LocationServices.FusedLocationApi.requestLocationUpdates(
                    googleApiClient, getLocationRequest(), this);

        } catch (SecurityException securityException) {
            Log.e(TAG, "Exception while requesting location updates", securityException);
        }
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.i(TAG, "Google API Client suspended.");
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.e(TAG, "Failed to connect to Google API Client.");
    }

    @Override
    public void onLocationChanged(Location location) {
        Log.e(TAG, "onLocationChanged: " + location.toString());
    }
}

私がここでテストするために使用しているコードの完全な作業サンプルを含めました: https://github.com/joshuajwitter/ForegroundLocationService

FitBitにも同じ問題があるようです、ここに議論があります

ここに私がすでに見てきたリソースがあります:

Android O のコードサンプル

アプリをバックグラウンドで実行し続けるAndroid常時

Android、画面がオフのときに位置を取得します

背景の場所の制限

ディープスリープ(居眠り)時のフォアグラウンドサービスでの一貫性のない場所の更新

フォアグラウンドサービスがDozeモードで位置情報の更新を受信しないAndroid O

編集2018.01.30:独自のプロセスでForegroundLocationServiceを実行してみました:

<service
    Android:name="foregroundlocation.stackoverflowexample.com.foregroundlocation.ForegroundLocationService"
    Android:enabled="true"
    Android:stopWithTask="false"
    Android:process=":ForegroundLocationService"
    Android:exported="false" >
</service>

FusedLocationProviderClientを使用するようにコードを更新するだけでなく、まだうまくいきません。

15
Joshua W

7.0と8.0を実行しているエミュレートされたデバイスでこの問題が発生していましたが、画面がオフ(cmd + P)のときは常にロケーションコールバックがトリガーされませんでした。ようやく実際の7.0デバイス(Galaxy S7)を手に入れることができ、問題を再現することができませんでした。私が誰かの時間を無駄にしたら申し訳ありませんが、どうやらそれはエミュレータの問題です。

1
Joshua W

私は同じ問題を抱えていて、有効な解決策を見つけました。現在地の更新をリッスンするには、このメソッドを呼び出さないでください。

public Task<Void> requestLocationUpdates (LocationRequest request, 
LocationCallback callback, Looper looper)

コールバックの代わりに保留中のインテントを使用する必要があります。つまり、メソッドを呼び出す必要があります。

public Task<Void> requestLocationUpdates (LocationRequest request, 
PendingIntent callbackIntent)

詳しくはリンクをご覧ください: https://developers.google.com/Android/reference/com/google/Android/gms/location/FusedLocationProviderClient.html#requestLocationUpdates(com.google.Android.gms.location .LocationRequest、Android.app.PendingIntent)

4
zavanton

1つの解決策はこれかもしれません:

2分以内に位置情報が受信されないかどうかを確認し、全体の位置情報の更新をもう一度停止/開始します。フォアグラウンドサービスで問題を解決する必要がありますが

0
iDeveloper

LocationServices.FusedLocationApiは非推奨です。

アプリを次のようにリファクタリングしました。それが役に立てば幸い:

これは私のサービスのonCreate-Methodで私が呼んでいるものです:

public void start(Context ctx) {
        FusedLocationProviderClient client = LocationServices
                .getFusedLocationProviderClient(ctx);
        //Define quality of service:
        LocationRequest request = LocationRequest.create();
        request.setInterval(1000); //Every second
        request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        if (locCallback != null) {
            client.removeLocationUpdates(locCallback);
        }
        locCallback = createNewLocationCallback();
        client.requestLocationUpdates(request, locCallback, null);
}

そして、これはコールバックを作成する方法です:

private LocationCallback createNewLocationCallback() {
    LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult result) {
            Location loc = result.getLastLocation();
            // Do something with the location (may be null!)
        }
    };
    return locationCallback;
}

推測、私はどこかで例としてそれを見つけましたが、参照を保持しませんでした。

オレオは一人ではありません。しかし、一部のテスターはそれが機能することを確認しました。

サービスをフォアグラウンドで維持するには、サービスのonCreateメソッドで後で「startForeground」を呼び出すだけです。

0
user2808624