I've created a service class and a worker class that is executed in a separate thread. I would like to setup a communication between them, so the worker could send some status back to the service.
I've tried to convert my worker Thread
to HandlerThread
and setup a Handler
on the service side, but then I don't know how to actually send a message from the worker. It looks like I can't grasp the concept.
Here's my classes (without communication logic):
Service class
package com.example.app;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class ConnectionService extends Service {
protected ConnectionWorker thread;
@Override
public void onCreate() {
super.onCreate();
// Creating a connection worker thread instance.
// Not starting it yet.
this.thread = new ConnectionWorker();
Log.d(this.getClass().getName(), "Service created");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(this.getClass().getName(), "Service started");
// Checking if worker thread is already running.
if (!this.thread.isAlive()) {
Log.d(this.getClass().getName(), "Starting working thread");
// Starting the worker thread.
this.thread.start();
}
return Service.START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Log.d(this.getClass().getName(), "Stopping thread");
// Stopping the thread.
this.thread.interrupt();
Log.d(this.getClass().getName(), "Stopping service");
super.onDestroy();
Log.d(this.getClass().getName(), "Service destroyed");
}
}
Worker class
package com.example.app;
import android.os.SystemClock;
import android.util.Log;
public class ConnectionWorker extends Thread {
public ConnectionWorker() {
super(ConnectionWorker.class.getName());
}
@Override
public void run() {
super.run();
Log.d(this.getClass().getName(), "Thread started");
// Doing the work indefinitely.
while (true) {
if (this.isInterrupted()) {
// Terminating this method when thread is interrupted.
return;
}
// @todo: send a message to the service
Log.d(this.getClass().getName(), "Doing some work for 3 seconds...");
SystemClock.sleep(3 * 1000);
}
}
}
How do I implement a HandlerThread
, send messages from the worker thread and receive them in my service?
I will highly appreciate a code example.
Update
Actually, it looks like I can't use Looper
inside of my thread cause it will block the thread execution and I don't want that. Is it possible to send messages from the thread without using Looper
?
It looks like I've finally nailed it!
In order to pass data from thread back to a service you will need to do this:
Subclass a Handler
class inside of your service (call it e.g. a LocalHandler
). You will have to make it static. Override a handleMessage
method, it will receive messages from the thread.
Add a Handler
argument to your Thread
constructor. Instantiate your LocalHandler
class in a service and inject it to your thread via constructor.
Save reference to the Handler
inside of your thread and use it to send messages whenever appropriate.
Here's the complete example:
Service Class
package com.example.app;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
public class ConnectionService extends Service {
protected ConnectionWorker thread;
static class LocalHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Log.d(this.getClass().getName(), "Message received: " + (String) msg.obj);
}
}
protected Handler handler;
@Override
public void onCreate() {
super.onCreate();
// Instantiating overloaded handler.
this.handler = new LocalHandler();
// Creating a connection worker thread instance.
// Not starting it yet.
// Injecting our handler.
this.thread = new ConnectionWorker(this.handler);
Log.d(this.getClass().getName(), "Service created");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(this.getClass().getName(), "Trying to start the service");
// Checking if worker thread is already running.
if (!this.thread.isAlive()) {
Log.d(this.getClass().getName(), "Starting working thread");
// Starting the worker thread.
this.thread.start();
Log.d(this.getClass().getName(), "Service started");
}
return Service.START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Log.d(this.getClass().getName(), "Stopping thread");
// Stopping the thread.
this.thread.interrupt();
Log.d(this.getClass().getName(), "Stopping service");
super.onDestroy();
Log.d(this.getClass().getName(), "Service destroyed");
}
}
Worker Class (Thread)
package com.example.app;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
public class ConnectionWorker extends Thread {
// Reference to service's handler.
protected Handler handler;
public ConnectionWorker(Handler handler) {
super(ConnectionWorker.class.getName());
// Saving injected reference.
this.handler = handler;
}
@Override
public void run() {
super.run();
Log.d(this.getClass().getName(), "Thread started");
// Doing the work indefinitely.
while (true) {
if (this.isInterrupted()) {
// Terminating this method when thread is interrupted.
return;
}
Log.d(this.getClass().getName(), "Doing some work for 3 seconds...");
// Sending a message back to the service via handler.
Message message = this.handler.obtainMessage();
message.obj = "Waiting for 3 seconds";
this.handler.sendMessage(message);
SystemClock.sleep(3 * 1000);
}
}
}
I hope it's a valid implementation. If you now a better approach - please let me know.
Slava, actually, your answer doesn't work. Let me explain why.
Why does Android have HandlerThread when Thread already exists?
This is because as the documentation says, HandleThread is a
Handy class for starting a new thread that has a looper. The looper
can then be used to create handler classes. Note that start() must
still be called.
How is a Looper used to create a Handler?
A Looper should be passed in the Handler's constructor to tell the Handler which Looper it is working with.
So each HandlerThread has one Looper and there can be many Handlers handling messages the Looper loops through.
When you created a new Handler in your Service class using the default constructor, the Handler gets associated to the current Looper, as explained in it's documentation.
Handler ()
Default constructor associates this handler with the Looper for the
current thread. If this thread does not have a looper, this handler
won't be able to receive messages so an exception is thrown.
You can confirm the problem still exists using by printing this
Thread.currentThread().getId()
at appropriate places - within the Service's onCreate()
, within the Handler's handleMessage()
and within the run()
of ConnectionWorker. How to solve your problem then?
You can create a HandlerThread, even within the service, and when it's Looper is ready, create the Handler using that Looper.
new HandlerThread("ConnectionWorker") {
@Override
protected void onLooperPrepared() {
new Handler(getLooper()).post(new Runnable() {
@Override
public void run() {
// do your stuff
getLooper().quitSafely();
}
});
}
}.start();
Hope it isn't too late an answer :P