Android HandlerThread usage guide

In Android development, it is necessary to put time-consuming operations in sub-threads to avoid blocking the main thread, resulting in program ANR. The ways to implement such asynchronous tasks are:

  • Thread + Handler
  • AsyncTask
  • HandlerThread
  • IntentService

This article will explain and analyze HandlerThread. Before we start, let's understand how Handler is used.

Handler mechanism

Create Handler in child thread
public class MainActivity extends AppCompatActivity {

    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {
            @Override
            public void run() {
                handler = new Handler();
            }
        }).start();
    }

}

write picture description here
The program crashes, which means that the Handler object cannot be created without calling the Looper.prepare() method in the thread.

Cause Analysis:

I click on the source code of the Handler constructor, and I will find that when the Handler object is created, the system will check whether there is a Looper object in the current thread, and if not, an exception will be thrown.

Handler source code:

/**
Handler.java
*/
public Handler() {
     this(null, false);
}

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
            (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                  klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();   // 获取当前线程的 Looper 对象
    if (mLooper == null) {
        throw new RuntimeException(  // 没有Looper 对象,则需要调用 Looper.prepare()创建 Looper
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

So why create a Handler object in the main thread, without calling the Looper.prepare() method, and the program does not crash? This is because the looper object is created for us by the system when the main thread is created. Look at the source code of the program entry ActivityThread.main():

/**
* ActivityThread.java
*/
public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();  // 获取主线程中的Looper对象,该方法最终会调用Looper构造函数

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Conclusion: To create a Handler object in a thread, the Looper object of the thread needs to exist. The child thread requires us to manually create the Looper object, that is, call Looper.prepare() in the thread to create it, and use the Looper.loop() method to start polling; in the main thread, the system helps us create the Looper object.

General usage:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();   // 创建 Looper
                workerHandler = new Handler();
                Looper.loop();      // 开启轮询 
            }
        }).start();

Handler communicates between different threads

(1) The child thread sends a message to the child thread

To send a message between a child thread and a child thread, is to create a Handler in a child thread, so that the callback handleMessage() will naturally be in the child thread, and then use the handler in another child thread to send messages.

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "xinxing.tao";

    private Handler workerHandler;

    private WorkerThread workerThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        workerThread = new WorkerThread("WorkerThread");
        workerThread.start();

        // 在另一个子线程中发送消息给 WorkerThread 子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                // sleep(1000) 确保 WorkerThread 已经初始化好了 workerHandler
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 6; i++) {
                    Log.d(TAG, "sendMessage :name = " + Thread.currentThread().getName() + ", i = " + i);
                    Message message = new Message();
                    message.what = i;
                    message.obj = "WorkerThread Message";
                    workerHandler.sendMessage(message);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 停止 WorkThread,因为 WorkThread run()通过 Looper.loop() 死循环轮询,所以需要拿到他的 Looper 进行停止。
                workerHandler.getLooper().quit();
            }
        }).start();
    }

    class WorkerThread extends Thread {

        public WorkerThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            // 子线程创建时,没有创建 Looper 对象,必须手动调用 prepare() 方法创建 Looper
            Looper.prepare();
            workerHandler = new Handler() {
                // 子线程中创建 Handler, 回调自然在子线程中
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what= " + msg.what + " ,msg.obj = " + msg.obj);
                }
            };
            Log.d(TAG, "run: end ??????");
            Looper.loop();  // 开启Looper轮询,直到 Looper 停止退出轮询,才能执行后面的代码
            Log.d(TAG, "run: end");
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

write picture description here

(2) The main thread sends a message to the child thread (Thread method)

Create a Handler in the child thread (you need to create a Looper object), and then use the Handler to send messages in the main thread.

public class MainThread2WorkerThreadActivity extends AppCompatActivity {

    private static final String TAG = "debug";

    private Handler workerHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_thread2_worker_thread);
        // 子线程
        WorkerThread workerThread = new WorkerThread("WorkerThread");
        workerThread.start();
    }

    public void click(View view) {
        // send message
        Message message = new Message();
        message.what = 100;
        message.obj = "message from main to worker thread";
        workerHandler.sendMessage(message);
        Log.d(TAG, "sendMessage : " + Thread.currentThread().getName());
    }


    class WorkerThread extends Thread {

        public WorkerThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            super.run();
            // 子线程中使用 Handler,需要手动创建 Looper 对象
            Looper.prepare();
            workerHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what = " + msg.what + ",msg.obj = " + msg.obj);
                }
            };
            // 开启轮询
            Looper.loop();
        }
    }
}

Output result:

D/debug: sendMessage : main
D/debug: handleMessage: name = WorkerThread, msg.what = 100,msg.obj = message from main to worker thread

It can be seen that it is quite troublesome to use Handler in sub-threads. Fortunately, Android has provided us with a more convenient way to use it, namely HandlerThread.

HandlerThread parsing

Introduction to HandlerThread

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.

A class used to facilitate the opening of child threads. It contains Looper, which can be used to create a Handler class. Note that the start() method must be called.

HandlerThread general usage

  1. Create a HandlerThread object

    HandlerThread handlerThread  = new HandlerThread("handler-thread"); // 名字随意取
  2. Start HandlerThread

    handlerThread.start();
  3. Create a child thread Handler for processing asynchronous tasks and associate with handlerThread.

    Handler workerHandler = new Handler(handlerThread.getLooper()){
       public void handleMessage() {
           // 执行耗时操作,运行于子线程中
       }
    }

Demo1 : The following code uses HandlerThread to complete the main thread sending messages to child threads:

public class MainThread2WorkerThreadActivity extends AppCompatActivity {

    private static final String TAG = "debug";

    private HandlerThread handlerThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_worker_thread2_main_thread);

        // 创建,开启子线程 handlerThread
        handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        // 创建 workerHandler对象,传入的参数是 handlerThread 中的 Looper,即在 handlerThread这个子线程中创建 handler
        Handler workerHandler = new Handler(handlerThread.getLooper()) {

            // 这个方法运行在 handlerThread 子线程中,可以执行耗时操作
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);

                Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what = " + msg.what); // handleMessage: name = HandlerThread, msg.what = 11
            }
        };

        // 主线程中使用 workerHandler 发送消息
        Log.d(TAG, "sendMessage: name = " + Thread.currentThread().getName()); // sendMessage: name = main
        Message message = new Message();
        message.what = 11;
        message.obj = "message";
        workerHandler.sendMessage(message);


    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 退出 handlerThread looper 循环
        handlerThread.quit();
    }
}

Output result:

D/debug: sendMessage: name = main
D/debug: handleMessage: name = HandlerThread, msg.what = 11

Demo2: Custom HandlerThread, inherited from the HandlerThread class, the looper passed in to construct the workerHandler is the looper object in the HandlerThread, so the handleMessage() callback in the workerHandler runs in the child thread.
write picture description here

// DownloadHandlerThread.java
public class DownloadHandlerThread extends HandlerThread implements Handler.Callback {

    public static final int WHAT_START_DOWNLOAD = 1;

    public static final int WHAT_FINISH_DOWNLOAD = 2;

    private List<String> urlList = new ArrayList<>();

    private Handler uiHandler;

    private Handler workerHandler;

    public DownloadHandlerThread() {
        super("DownloadHandlerThread");
    }


    /**
     * 构造 workerHandler 时,传入的 Looper 是 HandlerThread 中的 looper,
     * 所以 workerHandler 中的 handleMessage() 是运行于子线程中。
     */
    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        //
        workerHandler = new Handler(getLooper(), this);   // 使用HandlerThread子线程中的 looper
        // 将接收到的任务消息挨个添加到消息队列中
        for (String url : urlList) {
            Message msg = workerHandler.obtainMessage();
            Bundle bundle = new Bundle();
            bundle.putString("url", url);
            msg.setData(bundle);
            workerHandler.sendMessage(msg);
        }
    }

    /**
     * 子线程中处理任务,完成后发送消息给主线程
     *
     * @param msg
     * @return
     */
    @Override
    public boolean handleMessage(Message msg) {
        if (msg == null || msg.getData() == null) {
            return false;
        }
        String url = msg.getData().getString("url");
        // 下载之前通知主线程显示下载提示
        Message startMsg = uiHandler.obtainMessage();
        startMsg.what = WHAT_START_DOWNLOAD;
        startMsg.obj = url + " = start download ";
        // 发送消息给主线程
        uiHandler.sendMessage(startMsg);

        // 开始下载
        SystemClock.sleep(2000);   // 模拟下载过程

        // 每个 URL 下载完成通知主线程更新ui
        Message finishMsg = uiHandler.obtainMessage();
        finishMsg.what = WHAT_FINISH_DOWNLOAD;
        finishMsg.obj = url + " = finish download";
        uiHandler.sendMessage(finishMsg);

        return true;
    }


    public void setUrlList(List<String> list) {
        this.urlList = list;
    }


    /**
     * 注入主线程 handler
     *
     * @param handler
     */
    public void setUiHandler(Handler handler) {
        this.uiHandler = handler;
    }

    @Override
    public boolean quitSafely() {
        uiHandler = null;
        return super.quitSafely();
    }
}
// MainActivity.java
public class MainActivity extends AppCompatActivity implements Handler.Callback {

    private String[] urls = {"http://www.baidu.com", "http://www.sina.com", "http://google.com"};

    private DownloadHandlerThread handlerThread;

    private TextView startTextView;

    private TextView finishTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startTextView = findViewById(R.id.tv_tip);
        finishTextView = findViewById(R.id.tv_finish);


        Handler uiHandler = new Handler(this);
        handlerThread = new DownloadHandlerThread();
        handlerThread.setUrlList(Arrays.asList(urls));
        handlerThread.setUiHandler(uiHandler);

    }

    public void click(View view) {
        handlerThread.start();
    }

    /**
     * 主线程回调
     *
     * @param msg
     * @return
     */
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case DownloadHandlerThread.WHAT_START_DOWNLOAD:
                startTextView.setText(startTextView.getText().toString() + "\n" + msg.obj);
                break;
            case DownloadHandlerThread.WHAT_FINISH_DOWNLOAD:
                finishTextView.setText(finishTextView.getText().toString() + "\n" + msg.obj);
                break;
        }

        return true;
    }
}

HandlerThread source code analysis

  /**
    * HandlerThread.java
    */
    public class HandlerThread extends Thread {
        int mPriority;
        int mTid = -1;
        Looper mLooper;
        private @Nullable Handler mHandler;

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

        /**
         * Constructs a HandlerThread.
         * @param name
         * @param priority The priority to run the thread at. The value supplied must be from 
         * {@link android.os.Process} and not from java.lang.Thread.
         */
        public HandlerThread(String name, int priority) {
            super(name);
            mPriority = priority;
        }

        /**
         * Call back method that can be explicitly overridden if needed to execute some
         * setup before Looper loops.
         */
        protected void onLooperPrepared() {
        }

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

        /**
         * 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() {
            if (!isAlive()) {
                return null;
            }

            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }

        /**
         * @return a shared {@link Handler} associated with this thread
         * @hide
         */
        @NonNull
        public Handler getThreadHandler() {
            if (mHandler == null) {
                mHandler = new Handler(getLooper());
            }
            return mHandler;
        }

        /**
         * Quits the handler thread's looper.
         * <p>
         * Causes the handler thread's looper to terminate without processing any
         * more messages in the message queue.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p><p class="note">
         * Using this method may be unsafe because some messages may not be delivered
         * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
         * that all pending work is completed in an orderly manner.
         * </p>
         *
         * @return True if the looper looper has been asked to quit or false if the
         * thread had not yet started running.
         *
         * @see #quitSafely
         */
        public boolean quit() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quit();
                return true;
            }
            return false;
        }

        /**
         * Quits the handler thread's looper safely.
         * <p>
         * Causes the handler thread's looper to terminate as soon as all remaining messages
         * in the message queue that are already due to be delivered have been handled.
         * Pending delayed messages with due times in the future will not be delivered.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p><p>
         * If the thread has not been started or has finished (that is if
         * {@link #getLooper} returns null), then false is returned.
         * Otherwise the looper is asked to quit and true is returned.
         * </p>
         *
         * @return True if the looper looper has been asked to quit or false if the
         * thread had not yet started running.
         */
        public boolean quitSafely() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quitSafely();
                return true;
            }
            return false;
        }

        /**
         * Returns the identifier of this thread. See Process.myTid().
         */
        public int getThreadId() {
            return mTid;
        }
    }

As you can see, HandlerThread inherits from Thread, so HandlerThread is still Thread in essence, and its run() method is executed in the child thread. When we use HandlerThread, when the asynchronous task is started by calling its start(), the run() method of HandlerThread will be called. In the run() method, Looper.prepare() is called to create the Looper object of the thread, and then the call Looper.loop() turns on the polling of messages. The loop() method is an infinite loop that continuously gets messages from the message queue MessageQueue for processing. Since the message queue is composed of a linked list structure, asynchronous tasks are executed serially.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325389896&siteId=291194637