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.