This is the fifth day of my participation in Gwen Challenge

This article is based onAndroid 9.0.0The source code

framework/base/core/java/andorid/os/HandlerThread.java

Use profile

To create a Handler on a child thread, call looper.prepare () manually

Handler mHandler;
private void createManualThreadWithHandler(a) {
  new Thread() {
      @Override
        public void run(a) {
            super.run();
            Looper.prepare();
            mHandler = new Handler(Looper.myLooper());
            Looper.loop();
        }
    }.start();
}
Copy the code

Android provides a handy class to simplify the process of creating a Handler. Using this class, you can quickly create a thread with a Looper that generates a Handler.

How to use

// Step 1: Create and start the HandlerThread thread containing Looper
HandlerThread handlerThread = new HandlerThread("lingdage");
handlerThread.start();

// Step 2: Create Handler
Handler handler = new Handler(handlerThread.getLooper());

// Step 3: Send the message
handler.post(new Runnable() {
        @Override
        public void run(a) {
            System.out.println("thread id="+Thread.currentThread().getId()); }});Copy the code

Source code analysis

Create the HandlerThread object

public HandlerThread(String name) {
    super(name);
    // The default priority of HandlerThread is process.thread_priority_default, which is 0.
    mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
    
public HandlerThread(String name, int priority) {
    super(name);
    mPriority = priority;
}
Copy the code

The priority of a thread ranges from -20 to 19. Those with higher priorities get more CPU resources, and those with lower priorities get less. -20 indicates the highest priority and 19 indicates the lowest priority. 0 is in the middle, but the HandlerThread, which is the worker thread, doesn’t need to have such a high priority, so we need to lower it. Attention! Is the priority of Process, not Thread.

Controllable priority

  • THREAD_PRIORITY_DEFAULT, the default thread priority, value 0.

  • THREAD_PRIORITY_LOWEST, with a value of 19.

  • THREAD_PRIORITY_BACKGROUND Background threads recommend setting this priority, with a value of 10.

  • With a value of -1, THREAD_PRIORITY_MORE_FAVORABLE takes precedence over THREAD_PRIORITY_DEFAULT.

  • THREAD_PRIORITY_LESS_FAVORABLE lags behind THREAD_PRIORITY_DEFAULT, with a value of 1.

    The above priorities can be set in the program, in addition to uncontrollable priorities are automatically adjusted by the system.

    A common way to add a priority is as follows

    Runnable run = new Runnable() {
      @Override
      public void run(a) { android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); }};Copy the code

For more information about thread scheduling in Android, see Nice for process and Thread Scheduling in Android

For which

/**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
public Looper getLooper(a) {
    // To use handlerThread, start first
    if(! isAlive()) {return null;
    }
        
    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
        while (isAlive() && mLooper == null) { // Enter the synchronization block, wait indefinitely if the condition is not satisfied,
            try { Exit the while until the mLooper is set to valid.
                wait();  // notifyAll in the run method is used to wake this up
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}
Copy the code

This method is relatively simple, directly read the official notes to understand.

Execute HandlerThread’s run()

public void run(a) {
    mTid = Process.myTid();  // Get the tid of the thread
    Looper.prepare();   // Create a Looper object
    synchronized (this) {
        mLooper = Looper.myLooper(); // Get the looper object
        notifyAll(); // Wake up the waiting thread
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();  // Rewrite onLooperPrepared to do some initialization
    Looper.loop();   The loop method blocks and the code does not execute until quit() or quitSafely() is executed
    mTid = -1;
}
Copy the code

Which exit

 public boolean quit(a) {
    Looper looper = getLooper();
    if(looper ! =null) {
        looper.quit();
        return true;
    }
    return false;
}
Copy the code
public boolean quitSafely(a) {
    Looper looper = getLooper();
    if(looper ! =null) {
        looper.quitSafely();
        return true;
    }
    return false;
}
Copy the code

The difference between quit() and quitSafely() is simply whether to remove the message that is currently being processed. Removing a message that is currently being processed may cause unsafe behavior.

Usage scenarios

To conclude, let’s think about a scenario where we now need to request network data (let’s say we need to request an image, and the UI needs to be updated when the image request comes back). We all know that time-consuming network requests are not allowed in the UI thread. If you don’t use the three-party library for network requests, you usually start the request with a new Thread() and then start(). In this case, if there are multiple requests for the image, then we have to new many threads. So this is a problem!!

HandlerThread is a child thread that contains a Looper. Let’s look at that problem again: we need new Thread(), and then start(). This is because the UI Thread cannot make network requests, but the HandlerThread is a child Thread, so it can directly request network requests, so the new Thread() and start() above solve the problem. Of course, just because it’s a child doesn’t convince me that it’s a child and doesn’t need a new Thread(), but it might need to be created multiple times. It just changed from a new Thread to a new HanderThread(). It’s not like eggs are useless.

So what explains why it doesn’t need to be created repeatedly? In fact, it is not difficult, as long as the child thread does not terminate not on the line. (The run method adds a while(true)), but instead of a while(true), it calls a loop() method.

The loop method is blocked, so the following statement cannot be executed without it quitting (which can be done with the quit() and quitSafely() methods). In addition, it can implement a Handler externally, which then sends a message to Looper to fulfill a continuous stream of network requests. So, this really solves the problem that was raised above. Here is a connection that shows how to create an external Handler to stream network requests. Android multithreading HandlerThread complete details

conclusion

What the HandlerThread does is create a Looper in the newly opened child Thread, which is used in a combination of Thread and Looper usage scenarios, i.e. to perform time-consuming, potentially multi-task operations in the child Thread.

HandlerThread is suitable for single-thread and asynchronous queue scenarios, such as I/O read and write operations, which take little time and do not cause large blocking. HandlerThread is not suitable for network IO operations because it has only one thread and has to queue up one by one.

For local I/O reads, we can use the postAtFrontOfQueue method to quickly queue the reads and return them to the main thread to update the UI if necessary. Sample scenario, reading data from a database is represented in the ListView. Note that reading also takes a certain amount of time. It is recommended to have the necessary user-aware progress prompt before data presentation.

A typical example of using HandlerThread is IntentService (see another article). IntentService source code analysis

reference

Android Messaging 3-Handler(In action)

HandlerThread

Understanding HandlerThread

Android Advanced 15: HandlerThread usage scenarios and source code parsing