2

I've created a AsyncTask to retrieve a GPS position with a 20-meter accuracy. I would like to execute a while do cycle until accuracy is accetable.

The problem is that I got an exception when I request an update of position.

java.lang.NullPointerException: Calling thread must be a prepared Looper thread.

This is the piece of code that goes in error

@Override
protected String doInBackground(Void... params) {
if (ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                return null;
            }
            mLocationRequest.setInterval(100);
            mLocationRequest.setPriority(100);
            mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            while (mLastLocation == null || (mLastLocation.getAccuracy()>Float.parseFloat("20.0") && mLastLocation.hasAccuracy())){
                try {
                    LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,mLocationRequest,activity);
                    mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                } catch (SecurityException secex) {

                }
            }

            return null;
        }
michael
  • 146
  • 3
  • 19

4 Answers4

9

I had troubles with this a full work day. I have the FusedLocationApi running in background within a Service.

What I a did -I read it somewhere else- is add Looper.getMainLooper() as the last parameter of requestLocationUpdates().

So, instead of this

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

You'll have

LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this, Looper.getMainLooper());

If your read the whole FusedLocationProviderApi page, you will see that the method will do different thing depending on the parameters.

Pedro Varela
  • 2,296
  • 1
  • 26
  • 32
  • BE CAREFUL with this! All of the code in your callback will be ran on the mainUI thread. If it's intensive code you can make your application janky. – Jason Tholstrup Jul 01 '17 at 00:16
  • I tried different methods to solve that issue. I even set its own looper and handlers but didn't work. I stop using that method to get location updates. I am using the FusedLocationClient to do it, it does not require this parameter. – Pedro Varela Jul 03 '17 at 14:03
  • See my new answer. It should avoid the MainThread issue. https://stackoverflow.com/a/45068298/42994 – Jason Tholstrup Jul 12 '17 at 22:10
  • You are using Guava.. Particularly I stick to the framework, even if Guava was developed by google. – Pedro Varela Jul 17 '17 at 15:46
  • Yeah your solution didn't quite fit my use case. I needed a location once in a background fetch method and I didn't want to keep the gps on. Though what you have is probably the more common use case for location. – Jason Tholstrup Jul 17 '17 at 20:39
2

Use Looper.prepare() or runOnUiThread() function to make changes in UI when running in doInBackground or make changes in methods other than doInBackground

Example :

 runOnUiThread(new Runnable() {
            @Override
            public void run() {
                progressBar = new ProgressDialog(MainActivity.this);
                progressBar.setCancelable(false);
                progressBar.setTitle("Downloading Files...");
                progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                progressBar.setProgress(0);
                progressBar.setMax(100);
                progressBar.show();
            }
        });
Ashwin Mothilal
  • 2,462
  • 1
  • 16
  • 21
  • Looper.prepare() seems works! I don't understand why needed it because I do not make changes in UI. (googleapiclient is part of UI?) – michael Feb 27 '17 at 10:38
  • runOnUiThread() works on Runnable, the AsyncTask is not accepted as parameter. Any ideas? – michael Feb 28 '17 at 08:19
  • If Looper.prepare() works then you are executing multiple runnables at the same time. Please check it – Ashwin Mothilal Feb 28 '17 at 10:21
0

I used SettableFuture from guava so that I wouldn't run unnecessary code on the main looper when the call returned. (this could be a problem if your callback kicks off some heavy work load and can lead to UI jank.)

  protected String doInBackground(Void... params) {
      SettableFuture<Location> future = SettableFuture.create();
       Log.w(TAG, "before fused call isMainLooper= %b", Looper.getMainLooper().isCurrentThread());
      // future.set runs on the main thread.  
      //future.get stays on the background thread.
      mFusedLocationApi.requestLocationUpdates(
          client,
          LOCATION_REQUEST,
          location1 -> {
             future.set(location1);
      Log.w(TAG, "callback: isMainLooper= %b", Looper.getMainLooper().isCurrentThread());

          }
          Looper.getMainLooper());

      location = future.get(10, SECONDS);
      Log.w(TAG, "after fused call isMainLooper= %b", Looper.getMainLooper().isCurrentThread());

Log output

W TAG: before fused call isMainLooper = false
W TAG: callback: isMainLooper = true
W TAG: after fused call isMainLooper = false
Jason Tholstrup
  • 2,036
  • 3
  • 21
  • 25
0

I was having the problem while getting locations in background. When I found the out the exception I added Looper.getMainLooper.. then I decided to read again the documentation and I completely change the way how I get location in background; the difference is using the FusedLocationProviderClient and callbacks like this.

First define your provider client.

private FusedLocationProviderClient mFusedLocationClient;

Define location callbacks, make sure you use com.google.android.gms.location.LocationCallback

/**
 * Fuse location api callback
 */
private LocationCallback mLocationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(LocationResult locationResult) {

        super.onLocationResult(locationResult);

        for (Location location : locationResult.getLocations()) {

            mLastLocation = location;                

            //Send to your server or update your UI

        }
    }

    @Override
    public void onLocationAvailability(LocationAvailability locationAvailability) {

        super.onLocationAvailability(locationAvailability);
        //Send to your server or update your UI

    }
};

Define a method to request location updates

public void startLocationUpdates() {

    boolean hasLocationPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
            ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;

    if (!hasLocationPermission) {
        gettingLocationUpdates = false;
        return;
    }

    mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, /*Looper*/ null);

    com.google.android.gms.tasks.Task<Location> task = mFusedLocationClient.getLastLocation();

    task.addOnCompleteListener(task1 -> {

        mLastLocation = task1.getResult();

        gettingLocationUpdates = true;

    });
}

Initialize it in onCreate of your service and start requesting updates

@Override
public void onCreate() {
    mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
    startLocationUpdates();
}

Don't forget to remove the locations updates, or it will never stop

@Override
public void onDestroy() {

if (mFusedLocationClient != null) {
     mFusedLocationClient.flushLocations();                         
   mFusedLocationClient.removeLocationUpdates(mLocationCallback);
}

Give it a try and let me know.

Pedro Varela
  • 2,296
  • 1
  • 26
  • 32