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

时间:2018-01-29 20:03:26

标签: android gps location android-7.0-nougat wakelock

我正在尝试创建一个Android应用程序,在设备屏幕关闭时实时连续记录设备位置数据。我的代码可以在Android 6.0及更早版本中正常运行,但似乎Android 7.0+破坏了我的应用。

我已经实现了一个使用唤醒锁并订阅Google FusedLocation API的Android前台服务。关闭屏幕后,永远不会触发onLocationChanged回调。

有没有人见过这个问题的解决方案?我已尝试通过Android应用的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

EDIT 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,仍然没有运气。

5 个答案:

答案 0 :(得分:7)

过去一周,我一直在尝试在前景中获得定位服务。最终通过将其包含在清单中使其工作。

android:foregroundServiceType="location"

我希望这可以在将来对某人有所帮助!

答案 1 :(得分:4)

我有同样的问题,我找到了一个有效的解决方案。为了收听位置更新,您不应该调用此方法:

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)

答案 2 :(得分:1)

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

答案 3 :(得分:0)

不推荐使用LocationServices.FusedLocationApi。

我刚刚重构了我的应用程序,如下所示。希望它有所帮助:

这就是我在onCreate-Method of my Service中所要求的:

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

答案 4 :(得分:0)

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

检查是否在2分钟内未收到位置信息。再次停止/开始整个开始位置更新。尽管前台服务必须解决了问题