在后台接收位置更新的最佳方式是什么?

What is the best way to receive location updates in background?

我正在尝试编写一个在后台跟踪位置并将数据发送到服务器的应用程序 -- 例如,用于监控我的家人所在的位置。
目前我正在玩 https://github.com/android/location-samples, particularly with LocationUpdatesBackgroundKotlin 这似乎是接收位置更新的最佳方式,但是

在后台收到大约 8-10 次位置更新后,状态栏上的 gps 图标在没有通知应用程序的情况下消失了(here 是 android/phone 信息,但我希望应用程序兼容 Android 5.1).
我想以某种方式知道是否正在接收位置更新并在它死了时重新启动它(在 MyLocationManager 的第 105 行使用 fusedLocationClient.requestLocationUpdates 重新开始接收更新有助于接收进一步的更新,但我必须监视状态)。

有什么出路,或者更靠谱的方法吗?谢谢。
P.S。已经为 android 写了一个星期。

为了不断从应用程序获取位置信息,您必须使用前台服务,您可以在其中初始化位置管理器并根据参数不断更新位置信息已设置。此外,请确保您拥有后台位置权限,因为这是 API 29 级之后的要求。以下是实现它的基本流程。请务必在获取位置权限后启动此服务:

public class MyCustomService extends Service implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    private GoogleApiClient mGoogleApiClient;
    private PowerManager.WakeLock mWakeLock;
    private LocationRequest mLocationRequest;
    private boolean mInProgress;

    private Boolean servicesAvailable = false;

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

    private static final int UPDATE_INTERVAL_IN_SECONDS = 120;
    private static final int MILLISECONDS_PER_SECOND = 1000;
    public static final long UPDATE_INTERVAL = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS;
    private static final int FASTEST_INTERVAL_IN_SECONDS = 60;
    public static final long FASTEST_INTERVAL = MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS;


    @Override
    public void onCreate() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService();
        }

        mInProgress = false;
        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
        mLocationRequest.setInterval(UPDATE_INTERVAL);
        mLocationRequest.setFastestInterval(FASTEST_INTERVAL);
        mLocationRequest.setSmallestDisplacement(100);
        servicesAvailable = servicesConnected();

        /*
         * Create a new location client, using the enclosing class to
         * handle callbacks.
         */
        setUpLocationClientIfNeeded();
        super.onCreate();

    }


    private void setUpLocationClientIfNeeded() {
        if (mGoogleApiClient == null)
            buildGoogleApiClient();
    }

    /*
     * Create a new location client, using the enclosing class to
     * handle callbacks.
     */
    protected synchronized void buildGoogleApiClient() {
        this.mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    }

    private boolean servicesConnected() {

        // Check that Google Play services is available
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

        // If Google Play services is available
        if (ConnectionResult.SUCCESS == resultCode) {

            return true;
        } else {

            return false;
        }
    }

    /* Used to build and start foreground service. */
    private void startForegroundService() {
        Intent notificationIntent = new Intent(this, HomeActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        String CHANNEL_ID = "1";
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.noti_icon)
                .setPriority(Notification.PRIORITY_LOW)
                .setOngoing(true)
                .setAutoCancel(false)
                .setContentTitle("ServiceTitle")
                .setContentText("Service Reason text")
                .setTicker("TICKER")
                .setChannelId(CHANNEL_ID)
                .setVibrate(new long[]{0L})
                .setContentIntent(pendingIntent);
        Notification notification = builder.build();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "NOTIFICATION_CHANNEL_NAME", NotificationManager.IMPORTANCE_HIGH);
            channel.setDescription("NOTIFICATION_CHANNEL_DESC");
            channel.enableVibration(false);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
            notificationManager.createNotificationChannel(channel);
        }
        startForeground(123, notification);
    }

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

        this.mInProgress = false;

        if (this.servicesAvailable && this.mGoogleApiClient != null) {
            this.mGoogleApiClient.unregisterConnectionCallbacks(this);
            this.mGoogleApiClient.unregisterConnectionFailedListener(this);
            this.mGoogleApiClient.disconnect();
            // Destroy the current location client
            this.mGoogleApiClient = null;
        }

        if (this.mWakeLock != null) {
            this.mWakeLock.release();
            this.mWakeLock = null;
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService();
        }
        setUpLocationClientIfNeeded();
        if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress) {
            mInProgress = true;
            mGoogleApiClient.connect();
        }
        return START_STICKY;

    }


    @Override
    public void onConnected(@Nullable Bundle bundle) {
        Intent intent = new Intent(this, LocationReceiver.class);
        PendingIntent pendingIntent = PendingIntent
                .getBroadcast(this, 54321, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        if (this.mGoogleApiClient != null)
            LocationServices.FusedLocationApi.requestLocationUpdates(this.mGoogleApiClient,
                    mLocationRequest, pendingIntent);
    }

    @Override
    public void onConnectionSuspended(int i) {
        // Turn off the request flag
        mInProgress = false;
        // Destroy the current location client
        mGoogleApiClient = null;
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        mInProgress = false;

        /*
         * Google Play services can resolve some errors it detects.
         * If the error has a resolution, try sending an Intent to
         * start a Google Play services activity that can resolve
         * error.
         */
        if (connectionResult.hasResolution()) {

            // If no resolution is available, display an error dialog
        } else {

        }
    }
}

这是您还需要在 Androidmanifest 文件中注册的位置接收器 class

public class LocationReceiver extends BroadcastReceiver {

    private String TAG = "LOCATION RECEIVER";

    private LocationResult mLocationResult;
    private Context context;
    Location mLastLocation;
    

    @Override
    public void onReceive(Context context, Intent intent) {
        // Need to check and grab the Intent's extras like so
        this.context = context;
        if (LocationResult.hasResult(intent)) {
            this.mLocationResult = LocationResult.extractResult(intent);
            if (mLocationResult.getLocations().get(0).getAccuracy() < 100) {

                // DO WHATEVER YOU WANT WITH LOCATION
            }
        }
    }
}

所需权限:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_GPS" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>

注意:上面代码中的一些方法,如 FusedLocationApi 和 isGooglePlayServicesAvailable 已被弃用

它是为 Android 5 Lollipop 推出的,最好(更稳定)使用 third-party 库进行免费位置接收,即来自 'io.nlopez.smartlocation:library:3.3.3'io.nlopez.smartlocation.SmartLocation,因为正如 Rudrik Patel 提到的那样,描述了 here,以及前台服务。很有魅力。