0

I have a service that listens to location changes and sends message to UI thread together with Polylineoptions, it goes like this:

    //some code here
    Bundle bundle = new Bundle();
    bundle.putParcelable("polylineOptions", mPolylineOptions);
    Message msg = Message.obtain(null, LOCATION_UPDATE);
    msg.setData(bundle);
    mMainHandler.dispatchMessage(msg);
    //some more code 

mPolylineOptions contains new location together with all previous locations. in the main thread i have handleMessage method that should update my map. it gos like this:

    private class MainHandler extends Handler {

     private MainHandler (Looper looper){
         super(looper);
     }
     @Override
     public void handleMessage (Message msg){
        switch (msg.what){
        case TrackingService.LOCATION_UPDATE:
            if (D){Log.d(TAG, "Location update received");};
            myPolylineOptions = (PolylineOptions) msg.getData().getParcelable("polylineOptions");
            new Color();
            myPolyline = mMap.addPolyline(myPolylineOptions
                    .color(Color.argb(128, 255, 0, 0))
                    .geodesic(true));
            break;
        }
     }
 }

i can see that handler receives the message, but I get "illegalstateexception: Not On The Main Thread" when i call

    myPolyline = mMap.addPolyline(myPolylineOptions
                .color(Color.argb(128, 255, 0, 0))
                .geodesic(true));

Anybody has ideas how to solve this? Thanks!

EDIT:

I bound to service and pass to it my UI Handler like this:

    private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        if (D) {Log.d(TAG, "main - onServiceConnected started");};
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
        while (mService.getThreadHandler() == null){
            try {
                Thread.sleep(100);
                if(D) {Log.d(TAG, "Thread Handler is not ready");};
            } catch (Exception e){}
        }
        mThreadHandler = mService.getThreadHandler();
        mService.setHandler(new MainHandler(Looper.getMainLooper()));

    }

Code for a thread. this thread runs in a service:

I know, this thread class is very "dirty" inelegant and unprofessional....

    private class ThreadHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
            case MAIN_HANDLER:
                if(D) {Log.d(TAG, "main hadler received");};
                break;
            }
            super.handleMessage(msg);
        }
    }

    public LocationThread (Context context){
        mContext = context;
        keepOn = true;
    }

    public void cancel() {
        keepOn = false;
        if (D){Log.d(TAG, "thread was canceled");}; 
    }

    public void run(){
        try {
            Looper.prepare();
        } catch (Exception e) {}
        // create handler for communication
        mThreadHandler = new ThreadHandler();
        // setup location updates
        Location mLocation;
        Location lastLocation = null;
        PolylineOptions mPolylineOptions = new PolylineOptions();
        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // Use high accuracy 
        mLocationRequest.setInterval(UPDATE_INTERVAL); // Set the update interval to 5 seconds 
        mLocationRequest.setFastestInterval(FASTEST_INTERVAL); // Set the fastest update interval to 1 second
        mLocationClient = new LocationClient(mContext, this, this);
        mLocationClient.connect();

        while (keepOn){
            try {
                Thread.sleep(10000);
            } catch (Exception e){}
            if (mConnected){
                if (D) {Log.d(TAG, "thread is running");};
                mLocation = mLocationClient.getLastLocation();
                if (lastLocation == null) {
                    LatLng mLatLng = new LatLng(mLocation.getLatitude(), mLocation.getLongitude());
                    mPolylineOptions.add(mLatLng);
                    lastLocation = mLocation;
                }
                // Report to the UI that the location was updated
                float distance = mLocation.distanceTo(lastLocation);
                if (distance > 1){
                    LatLng mLatLng = new LatLng(mLocation.getLatitude(), mLocation.getLongitude());
                    mPolylineOptions.add(mLatLng);
                    new Color();
                    lastLocation = mLocation;
                }

                if (hasBindedActivity){
                    Bundle bundle = new Bundle();
                    bundle.putParcelable("polylineOptions", mPolylineOptions);
                    Message msg = Message.obtain(null, LOCATION_UPDATE);
                    msg.setData(bundle);
                    mMainHandler.dispatchMessage(msg);
                }
            }

        }

        Looper.loop();
    }
PauliusM
  • 171
  • 1
  • 11
  • The whole getThreadHandler thing seems unnecessary to me based on this code. I think you can first of all use the constructor without parameters for your MainHandler (no need to give it the looper). Then just create a new handler by calling `mMainHandler = new MainHandler();` from within the onServiceConnected (which runs on UI thread). – J.Nieminen Oct 28 '13 at 21:21
  • Hmm.. I tried to create mMainHandler without any looper provided before posting this question. still same result. – PauliusM Oct 28 '13 at 21:35
  • Can you add the whole code? Now I can, for example, only see `mThreadHandler` being initialised, but the constructor for the `mMainHandler` is not shown. – J.Nieminen Oct 28 '13 at 21:38

3 Answers3

0

For me it looks like your mMainHandler is constructed outside the GUI thread. Make sure that you call mMainHandler = new MainHandler(); from within the GUI thread because Handler callbacks are executed in the thread where the Handler was created.

Edit: As Robin said, as long as you are just planning to update the UI based on some background activity, you should use for example an AsyncTask instead of a service. Simply create a private class extending AsyncTask within your Activity, do the background stuff in doInBackground and then update the GUI in onPostExecute, which runs again in the GUI thread. See http://developer.android.com/reference/android/os/AsyncTask.html for more info.

J.Nieminen
  • 566
  • 5
  • 12
  • I create Handler and pass it to my service on onServiceConnected method. is that OK? – PauliusM Oct 28 '13 at 21:22
  • Thanks for a suggestion about asyncTask, but I REALLY need service this time. Any ideas why my implementation is not working? Am I creating Handler in a right way? – PauliusM Oct 28 '13 at 21:31
  • Okay, but why exactly do you need a service for this? In any case, here's another question that might help: http://stackoverflow.com/questions/14695537/android-update-activity-ui-from-service – J.Nieminen Oct 28 '13 at 21:34
  • Service should listen for location updates even after app is killed. I know, maybe its not good idea because of the power consumption and maybe there are other ways to do that without service, but i feel like i am close to finish, all i need is to update my UI after receiving location updates. – PauliusM Oct 28 '13 at 21:40
  • But there's no need to update a GUI if the app is no longer visible. So I would try to decouple the logic: use a service for receiving location updates and store that info somewhere (e.g. a database). Then in your GUI code have, for example, an `AsyncTask` that checks if there's new info available and updates the GUI accordingly. – J.Nieminen Oct 28 '13 at 21:43
  • This suggestion is really attractive, and i am sure this will be my next step. Thanks for that. until then, i still want to make working demo without DataBase. Ofcourse, GUI will only get updates when its alive :) – PauliusM Oct 28 '13 at 21:51
  • For a quick demo you can also use for example the `SharedPreferences` if you don't want to set up a database right away. I'm often storing a lot of different stuff (such as location info) into the prefs by using [Google Gson](https://code.google.com/p/google-gson/) and storing the created json as a string. – J.Nieminen Oct 28 '13 at 21:55
  • I know, there are a lot of thing to discus and change. My thread is not good, i should request for LocationUpdates and so on, but please stay on topic :) I need to send message to Main thread and update my map with new polyline. – PauliusM Oct 28 '13 at 21:59
  • Well, I'm otherwise out of ideas (and need to go now). But if you didn't yet, check the link from the third comment about using broadcasts for the communication. Might be a way to go. Otherwise I actually think that you might be faster off doing a simple SharedPreferences-based data sharing instead of hitting the wall with the handlers. Sometimes rewriting a lot is faster than finding a really annoying bug. :) Good luck! – J.Nieminen Oct 28 '13 at 22:08
0

You can not change the UI from without the main thread. If you really want to do it from without use an AsyncTask

Maybe this will help.

Community
  • 1
  • 1
Robin Dijkhof
  • 18,665
  • 11
  • 65
  • 116
  • well, I think that I already implemented something similar to that on your link. And somehow it's not working. I have done this before, but not sure why it's not working this time. – PauliusM Oct 28 '13 at 21:29
0

Apparently comand mMainHandler.dispatchMessage(msg); executes Handlers handleMessage method on the wrong thread (worker thread). Documantation says that "dispatchMessage(Message msg) Handle system messages here." I have to use sendMessage(Message msg) that "Pushes a message onto the end of the message queue after all pending messages before the current time."

PauliusM
  • 171
  • 1
  • 11