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

雨燕双飞 提交于 2020-11-29 23:53:38

问题


I am trying to write an app that tracks location in background and sends data to a server -- to monitor where my family members are, for example.
Currently I am playing with https://github.com/android/location-samples, particularly with LocationUpdatesBackgroundKotlin that seems to be the best way to receive location updates, but

after receiving about 8-10 location updates in background, the gps icon on the status bar dies without anyway notifying the application (here is the android/phone info, but I want the app to be compatible to Android 5.1).
I want to somehow know is receiving location updates alive or not and re-start it if it's dead (restarting receiving updates with fusedLocationClient.requestLocationUpdates on line 105 of MyLocationManager helps receiving further updates, but I have to monitor the status by eye).

Is there any way out, or a more reliable approach? Thanks.
P.S. Have been writing for android for a week.


回答1:


In order to get location constantly from the application, you have to use the foreground service in which you can initialize the location manager and get the location update constantly as per the parameters that have been set. Also, make sure you have background location permission given as it is the requirement after API level 29. Following is the very basic flow of how it can be achieved. Make sure to start this service after taking the location permission:

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 {

        }
    }
}

Here is the Location receiver class which you need to register in the Androidmanifest file also

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
            }
        }
    }
}

Permission that is required:

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

Note: Some of the methods like FusedLocationApi and isGooglePlayServicesAvailable in above code is deprecated




回答2:


It comes out for Android 5 Lollipop it's better (much more stable) to use third-party library for bacground location receiving, namely io.nlopez.smartlocation.SmartLocation from 'io.nlopez.smartlocation:library:3.3.3', as decribed here, along with a foreground service, as Rudrik Patel mentioned. Works like a charm.



来源:https://stackoverflow.com/questions/64275072/what-is-the-best-way-to-receive-location-updates-in-background

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!