Source code analysis of HandlerThread of Android

1, introduction

HandlerThread, as the name implies, is the combination of Thread and Handler. Its essence is still a Thread. Only a Handler related to the Thread is added to the Thread, and a lightweight asynchronous class is implemented. It has the following characteristics:

  • Ordinary thread class: similar to the use of ordinary thread, you need to start the thread by creating a new thread object and calling start();
  • Inner nested Handler: contains the Handler bound to the thread, which facilitates the data interaction between threads;
  • Serial operation: it implements the task's serial blocking queue through Looper.

2. Easy to use

2.1 use as thread

Compared with ordinary threads, HandlerThread has the advantage of creating a work Handler object in this class. After the asynchronous task ends, it is sent to handleMessage of the Handler in the workspace. If ordinary threads need to maintain the prepare() and loop() methods of the loop manually.

//1. Create and execute MYHandlerThread class, add thread name
MYHandlerThread myHandlerThread = new MYHandlerThread("xiaohan");
myHandlerThread.start();

class MYHandlerThread extends HandlerThread {
        public MYHandlerThread(String name) {
            super(name);
        }
//2. Override the onLooperPrepared() method
        @Override
        protected void onLooperPrepared() {
            super.onLooperPrepared();
 //Perform time-consuming tasks
            SystemClock.sleep(3 * 1000);
//3. End of task, send result to Handler in workspace
            Message message = subHandler.obtainMessage();
            message.what = 101;
            message.obj = "finsh";
            subHandler.sendMessage(message);
        }
//4. Create the Handler in the workspace to receive the processing results of asynchronous tasks
        Handler subHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                String result = (String) msg.obj;
                Log.i(TAG, "handleMessage: In a child thread Handler  " + result);
            }
        };
    }
//5. End thread
mHandlerThread.quit();

2.2 use as a "thread pool"

//1. Create HandlerThread and add thread name
 mHandlerThread = new HandlerThread("xiaohan");
//2. Turn on HandlerThread thread
 mHandlerThread.start();
//3. Create WorkHandle workspace to handle different asynchronous tasks
mWorkHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case UPDATE_THREAD1:
                        Log.i(TAG, "handleMessage: Subthread processing task 1");
                        break;
                    case UPDATE_THREAD2:
                        Log.i(TAG, "handleMessage: Subthread processing task 2");
                        break;
                }
            }
 };
Message message = mWorkHandler.obtainMessage();
message.what = UPDATE_THREAD1;
message.obj = num + "";
 mWorkHandler.sendMessage(message);
//4. End thread
mHandlerThread.quit();

Brief description:

  • Create HandlerThread object: create HandlerThread object, fill in the thread name, and start the thread through start();
  • Create workspace Handler: get the Looper object of the current thread through getLooper(), bind it when creating the Handler, so that the Handler holds the Looper and MessageQueue, which works normally.
  • Send asynchronous task: send asynchronous Message information to handleMessage in the workspace as required for time-consuming operation.

3. Source code analysis

Source code analysis, refer to the steps used in the second part, we can see that the above two ways are roughly divided into five steps. The execution process in reference 2.2 below is analyzed as follows:

3.1 create HandlerThread and add thread name

The construction method of HandlerThread is as follows:

//thread priority 
int mPriority;
//Thread number
int mTid = -1;
//Looper object
 Looper mLooper;
 //Handler object
private @Nullable Handler mHandler;
//A construct containing only the thread name, with the default thread priority of 0
public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
//Structure containing thread name and priority  
public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

This section is mainly to create a new HandlerThread class and set the process name and priority.

3.2 enable HandlerThread

Start the run method of the thread after calling start(), as follows:

 @Override
    public void run() {
    //Get thread number
        mTid = Process.myTid();
     //Call prepare() method to prepare MessageQueue
        Looper.prepare();
      //Block and wait to see if the Looper is ready to complete. Its notifyAll() is mainly aimed at the wait() method in getLooper()
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
      //Set thread priority
        Process.setThreadPriority(mPriority);
       //In customization, override this method to implement asynchronous operation
        onLooperPrepared();
        //Open loop
        Looper.loop();
        mTid = -1;
    }
  • After starting the thread, it mainly completes the preparation, blocking and opening of Looper;
  • Provide a custom override of the onloop erprepared() method call;
  • Turn on loop to store and distribute messages

3.3 create WorkHandle workspace to handle different asynchronous tasks

mWorkHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case UPDATE_THREAD1:
                        Log.i(TAG, "handleMessage: Sub thread processing 1");
                        break;
                    case UPDATE_THREAD2:
                        Log.i(TAG, "handleMessage: Sub thread processing 2");
                        break;
                }

            }
        };
  • Create Handler: get the Looper object of HandlerThread and bind it to the working Handler;
  • Override handleMessage method: because WorkHandler is bound to the thread of HandlerThread, its handleMessage runs in its child thread;

be familiar with Handler As we all know, when calling Handler in child threads, we must bind to Looper, otherwise we can not get MessageQueue and execute loop() method. Then how can we ensure that Looper objects are ready when running workspace Handler?

The solution is as follows:

@Override
    public void run() {
		...
		//1. Start Loop preparation
		Looper.prepare();
		//2. Blocking preparation
		synchronized (this) {
		//3. Looper ready, unblock wait, send notification, release lock
            mLooper = Looper.myLooper();
            notifyAll();
        }
        ...
}

 public Looper getLooper() {
			...
			synchronized (this) {
			//4. Wait for Looper to finish
            		while (isAlive() && mLooper == null) {
               				 try {
                 				   wait();
               				 } catch (InterruptedException e) {
                }
			...
			//5. Send Looper object
			return mLooper;
}

The main process is as follows:

  • Prepare Looper: start to prepare the Looper object through Looper.prepare();
  • Blocking waiting: lock (synchronized) to prepare the Looper object, and notify to release the lock after preparation;
  • Complete the acquisition of Looper: when passing getLooper(), keep locking until the preparation of Looper is completed, until notifyAll() releases the lock, so make sure that the preparation of Looper is completed when getLooper().

After completing the workspace Handler, you can send a message to its handleMessage as needed to perform asynchronous task processing

3.4 end thread

Call the quit() method of HandlerThread to end the thread. Check the source code and you can find that the end of its thread includes two methods: quit() and quitsafety(), where: quit() thread is not safe, and quitsafety() is thread safe. If thread safety is needed, it will certainly lead to a reduction in execution efficiency.

//HandlerThread
 public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    
public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

Comparing the two methods, we can see that the execution thread end is mainly controlled by the quit() and quitsafety() methods of Looper, while the quit of MessageQueue is controlled by true or false when it is assigned in Looper.

//Looper 
public void quit() {
        mQueue.quit(false);
    }
public void quitSafely() {
        mQueue.quit(true);
    }
//MessageQueue
void quit(boolean safe) {
...
//Determine whether it is thread safe
		 if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
...
}
//Removing all message threads is not safe
//Traverse all messages and empty
 private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
//Remove all message thread safety    
private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
	  Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
		....
	}
}	

Brief description:
Differentiation: from HandlerThread – looper - MessageQueue, it can be seen that the final differentiation is whether the message is safe or not is the quit method in MessageQueue, while looking at its source code, it can be seen that the thread safe processing is to judge the time of the message when it is recycled. If the message is not processed, it will wait for processing, otherwise it will be processed directly (similar to thread unsafe)

89 original articles published, 46 praised, 60000 visitors+
Private letter follow

Posted on Mon, 03 Feb 2020 09:49:04 -0800 by alego