当屏幕关闭时,前台服务在 Android 7.0+ 中不接收位置更新

Foreground service not receiving location updates in Android 7.0+ when screen is off

我正在尝试创建一个 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 seems to have the same issue, here is the discussion

以下是我已经看过的资源:

Code Samples for Android O

Keep app running in background on Android all the time

Android, get the location when the screen is off

Background Location Limits

Inconsistent location updates in foreground service when in deep sleep (doze)

Foreground service not receiving location updates in Doze mode in Android O

编辑 2018.01.30:我也在自己的进程中尝试了 运行 ForegroundLocationService:

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

以及更新我的代码以使用 FusedLocationProviderClient,仍然没有运气。

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

我在模拟设备 运行 7.0 和 8.0 上遇到过这个问题,并且在屏幕关闭 (cmd+P) 时始终不会触发位置回调。我终于能够拿到真正的 7.0 设备 (Galaxy S7),但我无法重现这个问题。对不起,如果我浪费了任何人的时间,显然这是一个模拟器问题。

我遇到了同样的问题,并且找到了可行的解决方案。为了收听位置更新,您不应该调用此方法:

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

您应该使用挂起的意图而不是回调,即您应该调用方法:

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

查看 link 阅读更多内容:https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest, android.app.PendingIntent)

一个解决方案可能是这样的:

检查是否在 2 分钟内未收到位置stop/start重新开始整个位置更新。虽然前台服务肯定已经解决了问题

过去一周我一直在尝试在前台获取定位服务。终于通过将其包含在清单中使其正常工作。

android:foregroundServiceType="location"

我希望这对以后的人有所帮助!