0

I am writing an app to get location every 5s. I received this error. java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare(). I have searched for this error code. Many says that there is a need to create runOnUiThread(). However, even when i comment away the editTextShowLocation, meaning on output to ui, the same error happens.

Would appreciate the much needed help. Thank you.

Below is my code:

package com.example.celine.telemetryloop;

import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {

    private EditText editTextShowLocation;
    private Button buttonGetLocation;

    private LocationManager locManager;
    private LocationListener locListener = new MyLocationListener();

    private boolean gps_enabled = false;
    private boolean network_enabled = false;
    String TAG = "App";

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editTextShowLocation = (EditText) findViewById(R.id.editTextShowLocation);

        locManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

        Thread rcvLocationThread = new Thread(new RcvLocation());
        rcvLocationThread.start();
    }


    class RcvLocation implements Runnable {
        public void run() {
            try {
                gps_enabled = locManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            } catch (Exception ex) {
            }
            try {
                network_enabled = locManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
            } catch (Exception ex) {
            }

            // don't start listeners if no provider is enabled
            if (!gps_enabled && !network_enabled) {
                Log.d(TAG, "Not Enabled!"); //to enable it for user.
            }

            if (gps_enabled) {
                locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locListener);
            }
            if (network_enabled) {
                locManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locListener);
            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class MyLocationListener extends Handler implements LocationListener {
        @Override
        public void onLocationChanged(Location location) {
            Log.d(TAG, "2");
            if (location != null) {
                // This needs to stop getting the location data and save the battery power.
                //locManager.removeUpdates(locListener);

                String londitude = "Londitude: " + location.getLongitude();
                String latitude = "Latitude: " + location.getLatitude();
                String altitiude = "Altitiude: " + location.getAltitude();
                String accuracy = "Accuracy: " + location.getAccuracy();
                String time = "Time: " + location.getTime();

                editTextShowLocation.setText(londitude + "\n" + latitude + "\n" + altitiude + "\n" + accuracy + "\n" + time);
            }
        }

        @Override
        public void onProviderDisabled(String provider) {
            // TODO Auto-generated method stub

        }

        @Override
        public void onProviderEnabled(String provider) {
            // TODO Auto-generated method stub

        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
            // TODO Auto-generated method stub

        }
    }
}
Learner
  • 3
  • 1

2 Answers2

0

The error actually say what to do: Looper.prepare() hasn't been called inside your thread. Refer to this SO answer: https://stackoverflow.com/a/16886486/798406

It basically says that you have to put Looper.prepare() inside the run() method of your Runnable. However, you should avoid using Thread.sleep. Instead, you could use another handler and post a delayed message after the 5 seconds:

private class Poller implements Runnable {

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
        // Do something

        long pollingDelay = 5000;
        mHandler.postDelayed(this, pollingDelay);
    }
}

// Start the handler
mPollingRunnable = new Poller();
mHandler.post(mPollingRunnable);
Community
  • 1
  • 1
Denis Loh
  • 2,194
  • 2
  • 24
  • 38
0

First, you're thinking about the Location APIs incorrectly - you don't need to poll for updates every 5 seconds. You tell the LocationManager that you want to listen for updates, and it will tell you about an update every time one happens. If you tell it to limit your updates to once every 5 seconds, it will ignore or delay updates in order to satisfy your requirement, but asking it every five seconds won't get it to give you updates more often if there are none to give.

Second, the code you've written wouldn't actually do anything every five seconds anyway. The Thread you start simply registers for updates, waits five seconds, and then exits - there is no looping happening. Fortunately, aside from the unnecessary extra thread and five-second delay, that actually is the correct way to use the Location APIs.

Finally, to your actual question: The reason you're getting your RuntimeException is that you're giving the LocationManager a LocationListener and telling it (inadvertently) to execute its callback methods on your rcvLocationThread. Looper is a class that is used as a message pump. Loopers run on top of Threads and execute work given to them by Handlers. Handlers run on top of Loopers and receive messages and/or Runnables, and the Looper pumps through the Handler's queue and executes each of the incoming items.

If you don't specify a Looper when calling requestLocationUpdates, then the LocationManager assumes that you're making the call from a Looper thread and tries to enqueue the LocationListener method calls back onto the same thread. Since your thread doesn't have a Looper running on it (and it shouldn't in this case), it can't actually enqueue the callbacks and so throws an exception.

So - what do you do? Get rid of all the Thread nonsense, and put everything you've got in your RcvLocation.run() method in place of your new Thread(...).start() at the end of onCreate. Also, your MyLocationListener class doesn't need to extend Handler - just have it implement LocationListener and you'll be fine. The callbacks will execute on the main thread (which already has its own Looper). That's good because you're modifying views (something that must be done from the main thread) and appears safe since you're not doing any long-running processing operations.

public class MainActivity extends Activity {

    ... // variables and such

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editTextShowLocation = (EditText) findViewById(R.id.editTextShowLocation);

        locManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

        setupLocationUpdates();
    }


    private void setupLocationUpdates() {
        try {
            gps_enabled = locManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        } catch (Exception ex) { /* tsk tsk, empty catch block */ }
        try {
            network_enabled = locManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        } catch (Exception ex) { /* tsk tsk, empty catch block */ }

        // don't start listeners if no provider is enabled
        if (!gps_enabled && !network_enabled) {
            Log.d(TAG, "Not Enabled!"); //to enable it for user.
        }

        if (gps_enabled) {
            locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locListener);
        }
        if (network_enabled) {
            locManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locListener);
        }
    }

    class MyLocationListener implements LocationListener {
        @Override
        public void onLocationChanged(Location location) {
            // do your stuff
        }

        // all the other required callback methods
    }
}
tophyr
  • 1,658
  • 14
  • 20
  • Hi, thank you so much for your reply. I learnt quite alot from it. I dont think the solution above will notify me if there is a location change since setupLocationUpdates is only called once during onCreate. So I will still need thread or other looping mechanism i guess. – Learner Jul 22 '15 at 09:04
  • Hi Celine, `requestLocationUpdates` only *needs* to be called once. You don't get an update for every time you call that function; what it does is tell the LocationManager that you want to listen for updates. LocationManager will then call `onLocationChanged` for you every time there is an update to tell you about, automatically. – tophyr Jul 22 '15 at 09:15
  • Thanks alot tophyr. I just tested it and it works! :) When i am on the move, the update is done only every 20s. Any chance that you know how to speed this up? I am sure my location has changed far before the 20s mark. – Learner Jul 22 '15 at 09:23
  • Make sure your application holds the `ACCESS_FINE_LOCATION` permission and is using the GPS provider; that will provide you with the most rapid updates possible. If all of those are the case, then unfortunately there is no way (in software) to speed up the updates - LocationManager will send updates out as fast as it receives them from the GPS driver. If you're outdoors in an open area, you can usually get as fast as an update every second or so on standard phone hardware - but that is optimal conditions. – tophyr Jul 22 '15 at 09:38
  • Hi @Celine, hate to be a nag but if this answer solved your problem then please accept it as "the" answer by clicking the checkmark to the left of it. That will bubble this answer up to the top and make the question more prominent in search results, as well as giving you a few reputation points for asking a good question. Thanks! https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – tophyr Jul 29 '15 at 16:57
  • No apology needed! Glad I could help :) – tophyr Aug 05 '15 at 08:52