bmv2143 bmv2143 - 1 month ago 12
Android Question

Handler.handleMessage is not called while running test, but is called in the app

I have a service that runs in a separate process. The service spawns a new thread in

onCreate()
method. This thread sends messages back to the service.

If I start the app manually everything works fine - messages are received by the
Handler
in my service. But in my test
handleMessage()
method is never get called.

How can I fix my test to make
handleMessage()
method work?

Thanks!

Service:

public class MyService extends Service {

private ServiceHandler mHandler;
private final int MSG_HELLO_WORLD = 1;

private final String TAG = getClass().getSimpleName();

@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
}


@Override
public void onCreate() {
Log.d(TAG, "MyService.onCreate");
super.onCreate();

mHandler = new ServiceHandler();

new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Thread: run()");

while (true) {

try {
Log.d(TAG, "sleeping...");
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.d(TAG, "Thread was interrupted.");
break;
}

Log.d(TAG, "sending message...");
Message msg = Message.obtain(mHandler, MSG_HELLO_WORLD);
msg.sendToTarget();
}
}
}).start();

}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "MyService.onStartCommand");
return START_REDELIVER_INTENT;
}

private class ServiceHandler extends Handler {

@Override
public void handleMessage(Message msg) {
Log.d(TAG, "ServiceHandler.handleMessage");

if (msg.what == MSG_HELLO_WORLD) {
Log.d(TAG, "Hello World message received!");
}
}
}

}


Test:

public class MyServiceTest extends ServiceTestCase<MyService> {

private static final String TAG = MyService.class.getSimpleName();

public MyServiceTest() {
super(MyService.class);
}


public void setUp() throws Exception {
super.setUp();
}


public void testStartService() throws Exception {

Intent startServiceIntent = new Intent(getContext(), MyService.class);

startService(startServiceIntent);

MyService myService = getService();

assertNotNull(myService);

// give the service some time to send messages
Thread.sleep(10000);
}
}


App's logcat output (
handleMessage()
is called):

MyService.onCreate
MyService.onStartCommand
Thread: run()
sleeping...
sending message...
sleeping...
ServiceHandler.handleMessage
Hello World message received!
sending message...
sleeping...


Test's logcat output (
handleMessage()
is not called):

MyService.onCreate
MyService.onStartCommand
Thread: run()
sleeping...
sending message...
sleeping...
sending message...
sleeping...
sending message...
sleeping...

Answer

Your question seemed interesting to me, so I started digging.

"If I start the app manually everything works fine - messages are received by the Handler in my service."

Whatever component starts MyService, the service runs on the thread of that component, which means that when you initialize mHandler as

mHandler = new ServiceHandler();

the handler is associated with that component thread's message queue which is looping. As the result your messages are handled within the queue and you can see "Hello World message received!" in the logcat.

"But in my test handleMessage() method is never get called."

When you test the service it is running on InstrumentationThread (see the threads in Debugger). The Looper of the thread is prepared but not running, so the messages can't be handled and aren't shown in the log.

"How can I fix my test to make handleMessage() method work?"

I suggest 3 options:

  1. Tie MyServiceTest to the thread of the component that starts MyService. Write one more unit test for the component and then combine the test with MyServiceTest in one common .java test file. This will show the desired logcat output.
  2. Loop the prepared Looper of InstrumentationThread in MyServiceTest. You can do it by adding the following line in the testStartService() method of MyServiceTest:

    public void testStartService() throws Exception {
    
        Intent startServiceIntent = new Intent(getContext(), MyService.class);
        startService(startServiceIntent);
        MyService myService = getService();
        assertNotNull(myService);
    
        // Run the Looper (this line)
        Looper.loop();
    
        Thread.sleep(10000);
    }
    

    Note: running the Looper will show the desired logcat output, but the test won't stop because Looper.loop() runs indefinitely until you call quit().

  3. Change the structure of MyService (recommended). A better option would be to pass the Looper of the component that starts MyService to the constructor of the service.

Comments