Android HandlerThread source code analysis

Posted by jolinar on Mon, 17 Jan 2022 07:50:47 +0100

  • preface

If we want to execute multiple time-consuming tasks, the first thing we think of is to create multiple threads to execute tasks. If we have learned thread pool, we can also use thread pool. Is there anything lighter than thread pool? Handler, find out~

what! Can the Handler also perform time-consuming tasks? Then the problem comes

  • How do child threads use Handler?

Let's try it first

new Thread(new Runnable() {
    @Override
    public void run() {
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("lkx", String.valueOf(msg));
            }
        };
        Message message = Message.obtain();
        message.obj = "Hello";
        handler.sendMessage(message);
    }
}).start();

results of enforcement:
E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.example.hello, PID: 12612
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:227)
        at android.os.Handler.<init>(Handler.java:129)
        at com.example.hello.HandlerThreadActivity$2$1.<init>(HandlerThreadActivity.java:42)
        at com.example.hello.HandlerThreadActivity$2.run(HandlerThreadActivity.java:42)
        at java.lang.Thread.run(Thread.java:923)

Sure enough... The error is reported. There is a particularly familiar method looper in the error information prepare()

Finish the last one Analysis of Handler source code , we should be familiar with the Handler process. We know that the reason why the main thread can directly use the Handler is that the APP is already in the activitythread when it is started The Looper object is initialized in main (). Can we initialize it ourselves in the child thread?

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare(); //Initializes the Looper object of the current thread
        Looper looper = Looper.myLooper(); //Gets the Looper object of the current thread
        Handler handler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("lkx", (String) msg.obj);
            }
        };
        Message message = Message.obtain();
        message.obj = "Hello";
        handler.sendMessage(message);
        Looper.loop(); //Note: looper Loop is an endless loop, which will block the subsequent code and make it impossible to execute
    }
}).start();

results of enforcement: Hello   Thread-2

In the above code, we can see that the Handler can be used in the sub thread, and the handlerMessage will callback in the sub thread, that is, we can perform time-consuming operations in the handlerMessage.

It's too troublesome to write like this every time. Is there a simpler way? Of course! Android has already provided us with a class. Yes, it is HandlerThread~

  • How to use HandlerThread?

//Create a HandlerThread and call the start() method
HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

//Create a Handler and pass in the Looper object of HandlerThread
Handler handler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.d("lkx", (String) msg.obj);
    }
};

//Create 5 download tasks
for (int i = 0; i < 5; i++) {
    Message message = Message.obtain();
    message.obj = "Download task" + i;
    handler.sendMessage(message);
}

results of enforcement:
	Download task 0
	Download task 1
	Download task 2
    Download task 3
    Download task 4
  • HandlerThread source code analysis

At first glance, HandlerThread inherits Thread, that is, HandlerThread should be a Thread.

public class HandlerThread extends Thread {}
  • Construction method

Name: thread name
Priority: thread priority

public HandlerThread(String name) {
    super(name);
    mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

public HandlerThread(String name, int priority) {
    super(name);
    mPriority = priority;
}
  • HandlerThread#run

Since it is a thread, the execution entry should be the run() method
The run() method seems to be very similar to the Handler used by our custom sub thread above. First call looper Prepare () initialize, then call Looper.. Loop() opens the loop

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}
  • HandlerThread#getLooper

getLooper() is cleverly designed here. In order to ensure that the Looper object can be obtained normally, if the Looper object is empty, it will call wait() to release the lock and enter the waiting state. At the same time, it will wait for the initialization of the run() method. When the run() method obtains the Looper object, it will call notifyAll() method to notify. Then the wait() here will be awakened and the code will continue to go down, Until the Looper object is returned.

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}
  • HandlerThread#quit

When not in use, we need to close the loop of Looper. At this time, we can call handlerthread Thread () or thread Quitsafely () stops the loop. The difference between the two is that the former stops directly and the latter stops after the task is executed.

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mHandlerThread!=null) {
        mHandlerThread.quit();
    }
}
  • summary

HandlerThread is essentially a Thread, but a Looper loop has been opened internally for us. We can send messages to the MessageQueue through the Handler many times. The Handler will pass messages to the handleMessage through the Looper loop. This loop will always be open, so we must close it in time when we are not in use.

Topics: Android