0

I am using Looper inside a Service on Android app. Usually it is working without any issue. But in some cases (for example very often when I am trying to debug my app) it shows up that there is a race inside the app. Looper is starting, Thread and Handler are creating in the method startMessageThread() which is being invoked at the end of the Service.onCreate() method.

The race is because there are other methods and classes which use this class initialized before Looper.loop();. And in some cases these methods are running before Looper ends. It results in NullPointerException.

public class MyService extends Service {

    private Thread thread;
    private Handler handler;

    @Override
    public void onCreate() {
        super.onCreate();
        //some code
        startMessageThread();
    }

    private void startMessageThread() {
        thread = new Thread() {

            @Override
            public void run() { 
                Looper.prepare();
                handler = new Handler() {

                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            //message handling
                        }
                    }
                };
                MyObject myObject = new MyObject(arg1, arg2);
                myObject.init();
                myObject.attr = myAttr;
                Looper.loop();
            }
        }              
    }
}

If I am right, it is quite similar for this question How to create a Looper thread, then send it a message immediately? But I don't want to send any message immediately after creating 'HandlerThread'. Just to force main thread to wait in the end of the onCreate method for the Looper ends.

I have tried with this added at the end of the Service.onCreate method:

synchronized (thread) {
    try {
        if (looperRun) {
            wait();
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

looperRun is of course boolean. I have added looperRun = true; before Looper.loor(); method invocation. And this code at the end of the startMessageThread() method.

if (looperRun) {            
    notify();
    looperRun = false;
}

In some cases I have got this exception:

java.lang.RuntimeException: Unable to create service com.myapplication.service.MyService:
java.lang.IllegalMonitorStateException: object not locked by thread before wait()
    at android.app.ActivityThread.handleCreateService(ActivityThread.java:2582)
    at android.app.ActivityThread.access$1800(ActivityThread.java:135)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5017)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IllegalMonitorStateException: object not locked by thread before wait()
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:364)
    at com.myapplication.service.MyService.onCreate(MyService.java:162)
    at android.app.ActivityThread.handleCreateService(ActivityThread.java:2572)
    at android.app.ActivityThread.access$1800(ActivityThread.java:135)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5017)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
    at dalvik.system.NativeStart.main(Native Method)

EDIT

protected void startMessageThread() {
    MessageThread messageThread = new MessageThread("messageThread");
    messageThread.start();
    messageHandler = new Handler(messageThread.getLooper()) {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                //message handling
            }
        }
    };
}

private class MessageThread extends HandlerThread {

    public MessageThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        Looper.prepare();
        MyObject myObject = new MyObject(arg1, arg2);
        myObject.init();
        myObject.attr = myAttr;
        Looper.loop();
    }
}

EDIT 2

I have tried with solution similar to this: How to create a Looper thread, then send it a message immediately? My code is below. However my app seems to be frozen after start.

private class BackgroundThread extends HandlerThread {

    private Handler handler;

    public BackgroundThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        Looper.prepare();
        backgroundHandler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                log.finest("DEAService message:" + msg.what);
                switch (msg.what) {
                    case MSG_INIT:                            
                        MyObject myObject = new MyObject(arg1, arg2);
                        myObject.init();
                        myObject.attr = myAttr;
                        break;
                    //message handling
                }
            }
        };
        Looper.loop();
    };

    public synchronized void waitUntilReady() {
        handler = new Handler(getMainLooper());
    }
}

I start this HandlerThread in this method as previously:

protected void startBackgroundThread() {
    BackgroundThread backgroundThread = new BackgroundThread("backgroundThread");
    backgroundThread.start();
    backgroundThread.waitUntilReady();
    backgroundHandler.sendEmptyMessage(MSG_INIT);
}

As I mentioned above - app seems to be frozen after start. Nothing happens.

Community
  • 1
  • 1
woyaru
  • 5,544
  • 13
  • 54
  • 92

2 Answers2

2

You should never, ever block the main thread. Never!

I've used several times without any issue the nice handy HandlerThread class like this:

HandlerThread ht = new HandlerThread("myThread");
ht.start();
Handler handler = new Handler(ht.getLooper());
handler.post(... stuff

maybe you should give it a try

edit: sorry, there're two ways of doing handlers, my original post is the other one. The way you're using is like that.

HandlerThread ht = new HandlerThread("myThread");
ht.start();
Handler handler = new Handler(ht.getLooper()){
      @Override
      public void handleMessage(Message msg) {
        switch (msg.what) {
            //message handling
        }
   }
};

new edit:

see the code.

protected void startMessageThread() {
    HandlerThread messageThread = new HandlerThread("messageThread");
    messageThread.start();
    messageHandler = new Handler(messageThread.getLooper()) {

        @Override
        public void handleMessage(Message msg) {
          switch (msg.what) {
              case INIT:
                 MyObject myObject = new MyObject(arg1, arg2);
                 myObject.init();
                 myObject.attr = myAttr; 
              break;
            }
        }
    };
   messageHandler.sendEmptyMessage(INIT);
}
Budius
  • 39,391
  • 16
  • 102
  • 144
  • Hmm, I can switch my `Thread` to a `HandlerThread`. But what stuff I could post by `handler` created in this way? I can't place in this way any code which is running after `onCreate` completion. – woyaru Jun 13 '14 at 13:05
  • I have once more question: where should I place in this way my code I mentioned in question, which is placed in the `run()` method before `Looper.loop()`? If I am right I could extend `HandlerThread` and place it in the `run()` method. But should I place also in this `run()` method `Looper.prepare()` and `Looper.loop()`? – woyaru Jun 13 '14 at 14:00
  • I'm not sure I understand your question. From what I posted on the answer you have to keep a reference for the `handler` Object and call `handler.sendMessage(...)` that will be handled inside the the `switch` statement. No need to extend `HandlerThread` you're already extending the `Handler` – Budius Jun 13 '14 at 14:15
  • OK, I am not clear and perfect in English. :-) I have updated my question. I have added in my question first version three lines of code before `Looper.loop()` and I have also added the solution I am wondering about. This is what I have described in previous comment. These three code lines are very important so I should place them in this `Thread`. – woyaru Jun 13 '14 at 14:33
  • check my second edition. I don't know what your object is doing, but seems like you could do like that. I never mixed a Looper thread with some other type of code. Not sure how this will do – Budius Jun 13 '14 at 15:28
  • 1
    I've re-edited the answer, something like that seems like possible. – Budius Jun 13 '14 at 15:34
  • I am going to test it out today. It looks quite promising. :-) – woyaru Jun 16 '14 at 08:30
1

I have tried with this code and everything seems to be working fine.

private class BackgroundThread extends HandlerThread {

    private Handler handler;

    public BackgroundThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        Looper.prepare();
        backgroundHandler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                log.finest("DEAService message:" + msg.what);
                switch (msg.what) {
                    //message handling
                }
            }
        };
        MyObject myObject = new MyObject(arg1, arg2);
        myObject.init();
        myObject.attr = myAttr;
        Looper.loop();
    };

    public synchronized void waitUntilReady() {
        handler = new Handler(getMainLooper());
    }
}

And start this HandlerThread with this code:

protected void startBackgroundThread() {
    BackgroundThread backgroundThread = new BackgroundThread("backgroundThread");
    backgroundThread.start();
    backgroundThread.waitUntilReady();
}
woyaru
  • 5,544
  • 13
  • 54
  • 92