How to optimise battery with FUSED LOCATION API - Android

2.1k views Asked by At

Hi I am facing below issue/problem with location API in android

  • Battery consumption is high as 30% - 40%, which is causing lot of battery drain.

  • Location icon in status bar is always ON even when app is closed and when app is uninstalled it goes off automatically.

Requirement:

  • Need user location when app is opened.
  • I need to have users location even when app is not opened or not in use based on distance - need user location in background.

Approach:

  1. with GPS

  2. API used FUSED LOCATION API with pending intent.

  3. LocationManager - to check state of GPS On/Off.

Code walkthru:

  1. in OnCreate i m getting location manager instance - getting instance of location manager.

  2. checking is GPS enabled or is network state available else show dialog to enable location: CODE: -

    // get GPS state.
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    
    
        if (isGPSLocationEnabled(locationManager)) {
            buildGooleLocationApiClient();
        } else if (isNetworkLocationEnabled(locationManager)) {
            buildGooleLocationApiClient();
        } else {
            showAlert();
        }
    

Code for goolgeLocationAPiClient: In this method I am checking android version, requesting permission and enabling services

private void buildGooleLocationApiClient() {

        if (Build.VERSION.SDK_INT >= 23) {

            int isFineLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
            int isCoarseLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);

            if (isFineLocationPermission == PackageManager.PERMISSION_DENIED || isCoarseLocationPermission == PackageManager.PERMISSION_DENIED) {
                requestPermission();
            } else {
                checkGoogleLocationApiClient();
            }

        } else {
            checkGoogleLocationApiClient();
        }
    }

Building GoogleAPI Client:

private void checkGoogleLocationApiClient() {
        try {
            if (mGoogleApiClient != null) {
                if (mGoogleApiClient.isConnected()) {
                    getMyLocationCampaigns();
                } else {
                    mGoogleApiClient.connect();
                }
            } else {
                buildGoogleApiClient();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void getMyLocationCampaigns() {
        if (mCurrentLocation != null) {
            getData(mCurrentLocation.getLatitude()+"",mCurrentLocation.getLongitude()+"");
        } else {
            try {
                mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                getData(mCurrentLocation.getLatitude()+"",mCurrentLocation.getLongitude()+"");
            } catch (SecurityException ex) {
                ex.printStackTrace();
                getData("","");
            }
        }
    }

private synchronized void buildGoogleApiClient() {
        try {
            Log.i(TAG, "activity Building GoogleApiClient===");
            mGoogleApiClient = new GoogleApiClient.Builder(this)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .addApi(LocationServices.API)
                    .build();

            createLocationRequest();
        } catch (Exception e) {
            e.printStackTrace();
            getData("","");
        }
    }


    private void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(60 * 60 * 1000);
        mLocationRequest.setFastestInterval(60 * 1000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
        mLocationRequest.setSmallestDisplacement(100);

        connectGoogleApiClient();
    }


private void connectGoogleApiClient() {
        if (mGoogleApiClient != null) {
            if (!mGoogleApiClient.isConnected())
                mGoogleApiClient.connect();
        }
    }


@Override
    public void onLocationChanged(Location location) {
        mCurrentLocation = location;
    }

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

        if (mCurrentLocation == null) {
            try {
                mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                if (mCurrentLocation != null) {
// MyAPICALL                    getData(mCurrentLocation.getLatitude()+"",mCurrentLocation.getLongitude()+"");
                } else {
                    LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,mLocationRequest, this);
                    mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                    if (mCurrentLocation == null) {
                        if (locationManager != null) {
                            String provider = Utils.getUserLastLocation(locationManager);
                            if (provider != null) {
                                try {
                                    Location location = locationManager.getLastKnownLocation(provider);
                                    if (location != null) {
                                        getData(location.getLatitude() + "", location.getLongitude() + "");
                                    } else {
                                        getData("", "");
                                    }
                                } catch (SecurityException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    } else {
                        getData(mCurrentLocation.getLatitude()+"",mCurrentLocation.getLongitude()+"");
                    }
                }
            } catch (SecurityException ex) {
                ex.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
                getData("","");
            }
        }
    }

Method to getlocation in background with pending intent

private void startLocationUpdates() {
        try {


            Intent receiverIntentService = new Intent(this, LocationIntentService.class);
            PendingIntent pendingIntent = PendingIntent.getService(this, 1, receiverIntentService, 0);

            if (mGoogleApiClient != null) {
                if (mGoogleApiClient.isConnected()) {
                    LocationServices.FusedLocationApi.requestLocationUpdates(
                            mGoogleApiClient, mLocationRequest, pendingIntent);
                }

            }
        } catch (SecurityException se) {
            se.printStackTrace();
        }
    }

BroadCastReceiver: In case if device is restarted:

public class LocationBroadcastReceiver extends BroadcastReceiver implements
        GoogleApiClient.ConnectionCallbacks,GoogleApiClient.OnConnectionFailedListener, LocationListener {


    Context context;
    protected GoogleApiClient mGoogleApiClient;
    protected LocationRequest mLocationRequest;
    protected Location mCurrentLocation;
    public static Boolean mRequestingLocationUpdates = false;

    SharedPreferences checkUserStatus;

    public LocationBroadcastReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        try {
            this.context = context;

            checkUserStatus = context.getSharedPreferences(Params.LOGIN_DETAILS_PREFERENCE, 0);
            String isUserLogedIn = checkUserStatus.getString(Params.TOKEN,"");

// if user is still logged in then only trigger background service
            if (!isUserLogedIn.equals("")) {
                buildGoogleApiClient();
                if (mGoogleApiClient != null) {
                    if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates) {
                        startLocationUpdates();
                    } else {
                        buildGoogleApiClient();
                    }
                } else {
                    buildGoogleApiClient();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void onConnected(Bundle bundle) {
        startLocationUpdates();
    }

    @Override
    public void onConnectionSuspended(int i) {
        mGoogleApiClient.connect();
    }


    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.i("Broadcast receiver", "Connection failed: ConnectionResult.getErrorCode() = " + connectionResult.getErrorCode());
    }


    @Override
    public void onLocationChanged(Location location) {
        mCurrentLocation = location;
    }


    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(context)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();

        createLocationRequest();
    }



    protected void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(60 * 60 * 1000);
        mLocationRequest.setFastestInterval(60 * 1000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
        mLocationRequest.setSmallestDisplacement(100);

    }

    protected void startLocationUpdates() {
        try {


            Intent receiverIntentService = new Intent(context,LocationIntentService.class);
            PendingIntent pendingIntent = PendingIntent.getService(context,1,receiverIntentService,0);

            LocationServices.FusedLocationApi.requestLocationUpdates(
                    mGoogleApiClient, mLocationRequest, pendingIntent);


        }catch (SecurityException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

My intent service class: to get user updated location and make an API call

public class LocationIntentService extends IntentService {

    Context context;
    Bitmap myBitmap;

    URL url;

    SharedPreferences.Editor mMyLastLocationHolder;
    SharedPreferences mMyLastLocation;

    SharedPreferences checkUserStatus;


    public LocationIntentService() {
        super("LocationIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        if (intent != null) {
            Bundle bundle = intent.getExtras();
            if (bundle != null) {
                Location location = bundle.getParcelable("com.google.android.location.LOCATION");
                if (location != null) {
                    context = getApplicationContext();
// API call to server
                    updateAPI(location.getLatitude()+"",location.getLongitude()+"");

                    Log.v("TAG LOCATION ", " ==== " + location.getLatitude() + " - " + location.getLongitude() + " ==== ");
                    Log.v("TAG LOCATION ", " ==== calling my-campaigns near me ========");
                }
            }
        }

    }




    /**
     * Handle action Foo in the provided background thread with the provided
     * parameters.
     */
    private void handleActionFoo(String param1, String param2) {
        // TODO: Handle action Foo
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * Handle action Baz in the provided background thread with the provided
     * parameters.
     */
    private void handleActionBaz(String param1, String param2) {
        // TODO: Handle action Baz
        throw new UnsupportedOperationException("Not yet implemented");
    }



}
3

There are 3 answers

2
Matt On

I hope this could help you finding the best solution/approach.

Personally prefer to use GoogleApiClient and LocationRequest with a certain priority and interval. Write a service that implements the following interfaces:

  1. GoogleApiClient.ConnectionCallbacks
  2. GoogleApiClient.OnConnectionFailedListener
  3. LocationListener

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

Use GoogleApiClient and LocationRequest classes.

Into the onCreate() instantiate a GoogleApiClient object, a LocationRequest object and make mGoogleApiClient connect.

public void onCreate() {
    mGoogleApiClient = new GoogleApiClient.Builder(this)
                        .addConnectionCallbacks(this)
                        .addOnConnectionFailedListener(this)
                        .addApi(LocationServices.API)
                        .build();
    mLocationRequest = LocationRequest.create()
                    .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
                 .setInterval(mInterval).setFastestInterval(mFastInterval);
    mGoogleApiClient.connect();
}

Into the onDestroy() method make the mGoogleApiClient disconnect

@Override
public void onDestroy() {
    mGoogleApiClient.disconnect();
}

Now implement the interfaces

@Override
public void onLocationChanged(Location location) {

    Log.d("NewLocation", location.toString());

}

@Override
public void onConnected(@Nullable Bundle bundle) throws SecurityException {
    LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}

@Override
public void onConnectionSuspended(int i) {

}

@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

}

Now GoogleApiClient, based on the settings of the LocationRequest, will inform you of the position calling the onLocationChanged() callback

Your business logic should be placed into the onLocationChanged() method. Just pick a good interval timings and priority for the LocationRequest. (see documentation)

Please refer to the official documentation about location strategies, my solution is based on that.

I'm used to get the service started in Foreground to prevent unexpected behaviour by the operating system (e.g. service being killed)

0
LOG_TAG On

This will only explain you the better logic

Instead of long running service or IntentService Just use Firebase JobDispatcher or Any 3rd Party lib Jobscheduler API such that you move all your location update code to Jobscheduler (https://github.com/googlesamples/android-JobScheduler/blob/master/Application/src/main/java/com/example/android/jobscheduler/service/MyJobService.java)

Start the Job as per your location update interval, configure or alter the Job as per your requirement !! it's really a better solution compare to long running service !!!(You can use eventBus or RxBus for location update in Activity or fragment !!)

Tips: every time Job starts with firing location update before Job closes setup up some system delay of 3seconds or more because some times Googleapiclient takes some more time to update the new updated GPS time after the delay you can close Googleapiclient all unwanted call backs with the running JobService. control the Job configuration smartly with Google Awareness Api or Google Fit API by detecting the User Activity!

All in one Job Jobscheduler Lib: https://github.com/evernote/android-job

P.S: code will be updated very soon

0
no_name On

The documentation states

Activities should strongly consider removing all location request when entering the background (for example at onPause()), or at least swap the request to a larger interval and lower quality.

therefore what I did when I faced a similar issue was:

  1. I created two location requests, the first had a priority of PRIORITY_HIGH_ACCURACY and an interval of 1 min while the second one had a priority of PRIORITY_LOW_POWER with an internal of 1 hour and a smallest displacement of 1km
  2. When the app is launched I use the first location request (high priority) to get more frequent and accurate location updates
  3. When the app enters the background I swap to the second location request (low priority) to eliminate the battery usage while getting less frequent location updates
  4. (Optional) You can also get the battery percentage when the app is launched and choose according to a limit (eg. 15%) which location request you might want to use when the app is in the foreground

These steps helped me reduce the battery usage of my app from >30% to <3%.