Skip to content

8为什么需要HandlerThread

如果我们需要创建一个子线程,主线程发送的消息能否交给子线程处理呢?如何实现呢?

  1. HandlerThread正是开辟一个带有Looper的工作线程
  2. 通过创建一个与这个工作线程的Looper绑定的workHandler
  3. 在主线程中用workHandler发送消息,最终消息又交给workHandler在工作线程中处理
  4. 由此实现在主线程发送的消息交给子线程处理

设计本质

本质上HandlerThread是通过继承Thread类和封装Handler来实现线程之间的通信。内部原理依旧是Thread类+Hnadler类机制。

  • 通过继承Thread类,快速地创建1个带有Looper对象的新工作线程,并且拥有自己的MessageQueue
  • 通过封装Handler类,快速创建Handler & 与其他线程进行通信
  1. 因此HandlerThread本身就是一个线程
  2. 不同于直接通过Thread类创建的线程,HandlerThread它是一个带有Looper,并且通过Looper会创建MessageQueue的线程。
  3. Handler中必须要有Looper,而在UI主线程中默认为我们创建了Looper,如果通过Thread创建的线程是没有Looper。在创建Handler的过程中可以指定任意线程的Looper对象。
  4. 这样的设计使得HandlerThread其实和UI主线程类似,可以通过HandlerThread来分担UI线程的工作量,降低主线程的压力。

具体使用

  1. 创建HandlerThread对象,并指定线程的名字
  2. 启动线程
  3. 创建工作线程Handler 并 指定该Handler的Looper ,复写handlerMessage()
  4. 在主线程中使用工作线程Handler向工作线程的消息队列发送消息
  5. 结束线程,即停止线程的消息循环
java
// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

// 步骤2:启动线程
   mHandlerThread.start();

// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  Handler workHandler = new Handler( mHandlerThread.getLooper() ) {
        @Override
        public boolean handleMessage(Message msg) {
            ...//消息处理
            ···//将处理结果通知给主线程
            return true;
        }
    });

// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
  // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

// 步骤5:结束线程,即停止线程的消息循环
  mHandlerThread.quit();

使用实例

java
public class MainActivity extends AppCompatActivity {

    Handler mainHandler,workHandler;
    HandlerThread mHandlerThread;
    TextView text;
    Button button1,button2,button3;

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

        // 显示文本
        text = (TextView) findViewById(R.id.text1);

        // 创建与主线程关联的Handler
        mainHandler = new Handler();

        /**
          * 步骤1:创建HandlerThread实例对象
          * 传入参数 = 线程名字,作用 = 标记该线程
          */
        mHandlerThread = new HandlerThread("handlerThread");

        /**
         * 步骤2:启动线程
         */
        mHandlerThread.start();

        /**
         * 步骤3:创建工作线程Handler & 复写handleMessage()
         * 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
         * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
         */

        workHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            // 消息处理的操作
            public void handleMessage(Message msg)
            {
                //设置了两种消息处理操作,通过msg来进行识别
                switch(msg.what){
                    // 消息1
                    case 1:
                        try {
                            //延时操作
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 通过主线程Handler.post方法进行在主线程的UI更新操作
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run () {
                                text.setText("我爱学习");
                            }
                        });
                        break;

                    // 消息2
                    case 2:
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run () {
                                text.setText("我不喜欢学习");
                            }
                        });
                        break;
                    default:
                        break;
                }
            }
        };

        /**
         * 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
         * 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
         */
        // 点击Button1
        button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // 通过sendMessage()发送
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1; //消息的标识
                msg.obj = "A"; // 消息的存放
                // b. 通过Handler发送消息到其绑定的消息队列
                workHandler.sendMessage(msg);
            }
        });

        // 点击Button2
        button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // 通过sendMessage()发送
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 2; //消息的标识
                msg.obj = "B"; // 消息的存放
                // b. 通过Handler发送消息到其绑定的消息队列
                workHandler.sendMessage(msg);
            }
        });

        // 点击Button3
        // 作用:退出消息循环
        button3 = (Button) findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandlerThread.quit();
            }
        });

    }
    
}

我们通过在主线程中使用工作线程的workhandler去发送消息,然后在工作线程中去处理耗时操作,处理完成之后通知主线程并更新界面,通过mainhandler.post将消息传回给主线程。

java
mainHandler.post(new Runnable() {
    @Override
    public void run () {
        text.setText("我不喜欢学习");
    }
});

源码分析

Android多线程:这是一份详细的HandlerThread源码分析攻略

1.创建HandlerThread对象

在创建HandlerThread对象的过程中主要完成了两件事:

  1. 创建一个工作线程
  2. 设置线程的优先级
java
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
java
public class HandlerThread extends Thread {
    // 线程优先级
    int mPriority;
    // 线程号
    int mTid = -1;
    // 线程内部的 Looper 对象
    Looper mLooper;
    private @Nullable Handler mHandler;

    // 有两个构造方法
    // 只指定线程名字并使用默认的线程优先级来构造 HandlerThread 对象
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    // 同时指定线程名字和优先级来构造 HandlerThread 对象
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    // 省略其他内容
    ...
}

2.启动handlerThread

HandlerThread本身就是一个Thread,所以也是通过start()方法启动,最终调用到run()方法。

java
/**
  * 具体使用
  */ 
   mHandlerThread.start();

/**
  * 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
  */ 
  @Override
    public void run() {
        // 1. 获得当前线程的id
        mTid = Process.myTid();

        // 2. 创建1个Looper对象 & MessageQueue对象
        Looper.prepare();

        // 3. 通过持有锁机制来获得当前线程的Looper对象
        //为什么要给HandlerThread加同步锁机制呢?
        synchronized (this) {
            mLooper = Looper.myLooper();
           
            // 发出通知:当前线程已经创建mLooper对象成功
            // 此处主要是通知getLooper()中的wait()
            notifyAll();
            
            // 此处使用持有锁机制 + notifyAll() 是为了保证后面获得Looper对象前就已创建好Looper对象
        }

        // 4. 设置当前线程的优先级
        Process.setThreadPriority(mPriority);

        // 5. 在线程循环前做一些准备工作 ->>分析1
        // 该方法实现体是空的,子类可实现 / 不实现该方法
        onLooperPrepared();

        // 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
        Looper.loop();

        mTid = -1;
    }
}

/**
  * 分析1:onLooperPrepared();
  * 说明:该方法实现体是空的,子类可实现 / 不实现该方法
  */ 
    protected void onLooperPrepared() {

    }

为什么要给HandlerThread加同步锁机制呢? 因为在后面我们需要创建一个workHandler,并为其指定一个Looper,而这个Looper就是HandlerThread的Looper,所以为了防止我们在getLooper()时,它还没创建好,所以通过锁机制,当Looper在创建的时候,让getLooper()等待wait(),直到Looper创建完成,通过notifyAll()通知Looper已经创建好了。