Example Communicating with Handlerthread

Example communicating with HandlerThread

This is a working example:

HandlerThread ht = new HandlerThread("MySuperAwesomeHandlerThread");
ht.start();
Handler h = new Handler(ht.getLooper()) {
public void handleMessage(Message msg) {
Log.d(TAG, "handleMessage " + msg.what + " in " + Thread.currentThread());
};
};
for (int i = 0; i < 5; i++) {
Log.d(TAG, "sending " + i + " in " + Thread.currentThread());
h.sendEmptyMessageDelayed(i, 3000 + i * 1000);
}

UPDATE:

Make two class fields:

Handler mHtHandler;
Handler mUiHandler;

and try this:

HandlerThread ht = new HandlerThread("MySuperAwsomeHandlerThread");
ht.start();
Callback callback = new Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 0) {
Log.d(TAG, "got a meaasage in " + Thread.currentThread() + ", now sleeping... ");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "woke up, notifying ui thread...");
mUiHandler.sendEmptyMessage(1);
} else
if (msg.what == 1) {
Log.d(TAG, "got a notification in " + Thread.currentThread());
}
return false;
}
};
mHtHandler = new Handler(ht.getLooper(), callback);
mUiHandler = new Handler(callback);
mHtHandler.sendEmptyMessageDelayed(0, 3000);

You can of course get rid of Callback interface and create two Handlers with overridden handleMessage method...

Best use of HandlerThread over other similar classes

Here is a real life example where HandlerThread becomes handy. When you register for Camera preview frames, you receive them in onPreviewFrame() callback. The documentation explains that This callback is invoked on the event thread open(int) was called from.

Usually, this means that the callback will be invoked on the main (UI) thread. Thus, the task of dealing with the huge pixel arrays may get stuck when menus are opened, animations are animated, or even if statistics in printed on the screen.

The easy solution is to create a new HandlerThread() and delegate Camera.open() to this thread (I did it through post(Runnable), you don't need to implement Handler.Callback).

Note that all other work with the Camera can be done as usual, you don't have to delegate Camera.startPreview() or Camera.setPreviewCallback() to the HandlerThread. To be on the safe side, I wait for the actual Camera.open(int) to complete before I continue on the main thread (or whatever thread was used to call Camera.open() before the change).


So, if you start with code

try {
mCamera = Camera.open(1);
}
catch (RuntimeException e) {
Log.e(LOG_TAG, "failed to open front camera");
}
// some code that uses mCamera immediately

first extract it as is into a private method:

private void oldOpenCamera() {
try {
mCamera = Camera.open(1);
}
catch (RuntimeException e) {
Log.e(LOG_TAG, "failed to open front camera");
}
}

and instead of calling oldOpenCamera() simply use newOpencamera():

private void newOpenCamera() {
if (mThread == null) {
mThread = new CameraHandlerThread();
}

synchronized (mThread) {
mThread.openCamera();
}
}
private CameraHandlerThread mThread = null;
private static class CameraHandlerThread extends HandlerThread {
Handler mHandler = null;

CameraHandlerThread() {
super("CameraHandlerThread");
start();
mHandler = new Handler(getLooper());
}

synchronized void notifyCameraOpened() {
notify();
}

void openCamera() {
mHandler.post(new Runnable() {
@Override
public void run() {
oldOpenCamera();
notifyCameraOpened();
}
});
try {
wait();
}
catch (InterruptedException e) {
Log.w(LOG_TAG, "wait was interrupted");
}
}
}

Note that the whole notify() -- wait() inter-thread communication is not necessary if you don't access mCamera in the original code immediately after opening it.

Update: Here the same approach is applied to accelerometer: Acclerometer Sensor in Separate Thread

Worker Thread to Worker Thread Communication


public class ModelFragment extends Fragment implements Handler.Callback {

Handler backHandler1, backHandler2, mainHandler;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);

HandlerThread backThread1 = new HandlerThread("BACK_THREAD_1");
backThread1.start();
backHandler1 = new Handler(backThread1.getLooper(), this);

HandlerThread backThread2 = new HandlerThread("BACK_THREAD_2");
backThread2.start();
backHandler2 = new Handler(backThread2.getLooper(), this);

mainHandler = new Handler(Looper.getMainLooper(), this);
// sending message from main thread to thread 1
backHandler1.obtainMessage(BACK1_WHAT, backObj).sendToTarget();
}

@Override
public boolean handleMessage(Message msg) {
switch(msg.what){
case BACK1_WHAT:
// this code runs on thread 1

// sending message to thread 2 from thread 1
backHandler2.obtainMessage(BACK2_WHAT, backObj1).sendToTarget();
return true;

case BACK2_WHAT:
// this code runs on thread 2

// sending message to thread 1 from thread 2
backHandler1.obtainMessage(BACK1_WHAT, backObj2).sendToTarget();

// sending message to main thread from thread 2
mainHandler.obtainMessage(MAIN_WHAT, backObj2).sendToTarget();
return true;

case MAIN_WHAT:
// runs on ui thread
return true;
}
return false;
}

@Override
public void onDestroy() {
if(null != backHandler1) {
backHandler1.removeCallbacksAndMessages(null);
if(null != backHandler1.getLooper())
backHandler1.getLooper().quit();
}
if(null != backHandler2) {
backHandler2.removeCallbacksAndMessages(null);
if(null != backHandler2.getLooper())
backHandler2.getLooper().quit();
}
super.onDestroy();
}
}

Android: how to communicate from worker thread to a service

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:

  1. 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.

  2. Add a Handler argument to your Thread constructor. Instantiate your LocalHandler class in a service and inject it to your thread via constructor.

  3. 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.

How to correctly use the HandlerThread with a socket?

Think of HandlerThread as a worker thread that has a Looper waiting for messages to execute (which can be spawning a new Thread). So to communicate with it, just prepare a Message object and dispatch it to the handler (e.g. you can start a new thread for socket reading ) e.g.

Handler handler = new Handler(handlerThread.getLooper()){
  @Override
  public void handleMessage(Message msg) {
     // Do action based on this message
  }
}

Typically HandlerThread is handy when you are listening to events that require a Looper e.g. requestLocationUpdates.

You do not need to use a HandlerThread to avoid NetworkOnMainThreadException (I do not know how you get this exception if you are using Thread as per your question ) and to use it with sockets, just use a normal thread and do your socket operation in the run method

Android: posting message to HandlerThread makes UI thread unresponsive or gives IllegalStateException

Got it. Obsiously the situation about recycling messages is this:

If you send it to a handler, the handler/looper will recycle it for you.

So one must not recycle the message within handleMessage.

HandlerThread.getLooper() blocks indefinitely

on the MediaPlayerThread you need to call the super method for run:

public void run (){
mMediaPlayer = new MediaPlayer();
super.run();
}

Important things are executed in its parent class run method. More specifically the looper is created and if you call getLooper() and looper is not created it will wait for its creation.



Related Topics



Leave a reply



Submit