1.HandlerThread特点及注意事项

  • HandlerThread本质上是一个线程类,继承自Thread。在线程内部,代码是串行处理的。
  • 其内部拥有自己的Looper对象,也就是说它可以自己进行消息的循环。通过getLooper()方法可以将这个Looper对象传递给Handler对象,这样就可以在handleMessage()方法中执行异步任务。HandlerThread将looper对象传递给子线程进行处理,目的是为了分担MainLooper的工作量,降低了主线程的压力,使界面更加流畅。
  • 开启一个线程起到多个线程的作用,处理任务是串行的,按消息发送顺序进行处理,但是由于因为每个任务都会以队列的形式被执行到,如果队列中某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
  • HandlerThread拥有自己的消息队列,因此它不会阻塞UI线程。
  • 创建HandlerThread后需要先调用handlerThread.start()方法,Thread会先调用run()方法,创建Looper对象。

2.应用场景

适用于会长时间在后台执行,并且间隔时间内会调用的任务。

3.Handler,Thread和HandlerThread的区别

  • Handler会关联一个单独的线程,Looper和消息队列,默认关联是UI线程。
  • HandlerThread继承自Thread,所以它本质就是一个Thread,只不过其内部有自己的Looper对象和消息队列,可以用来循环消息队列,并将消息回调到子线程中的Handler进行处理。

4.HandlerThread原理分析

public class HandlerThread extends Thread {
    int mPriority;//线程优先级
    int mTid = -1;
    Looper mLooper;//当前线程持有的Looper对象
    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() {
    }
    
    
 }

从源码可以看出HandlerThread继续自Thread,构造函数的传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。其中成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是注意重写时机是在Looper循环启动前,再看看run方法:

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

前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了**Looper.prepare()**代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。接着将执行代码:

synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }

这里在Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,最后执行Looper.loop();代码,开启looper循环语句。那这里为什么要唤醒等待线程呢?我们来看看,getLooper方法:

/**
     * 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;
    }

事实上可以看出外部在通过getLooper方法获取looper对象时会先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,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;  
   }

从源码可以看出当我们调用quit方法时,其内部实际上是调用Looper的quit方法而最终执行的则是MessageQueue中的removeAllMessagesLocked方法(Handler消息机制知识点),该方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送)还是非延迟消息。
  当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而最终执行的是MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。

5.HandlerThread使用方法

在讲解其具体使用方法前,还是先来看下对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.
 */
public class HandlerThread extends Thread { ... }

从这段声明里可以看到:HandlerThread能够很方便地启动一个带有looper的线程,而这个looper可以用来创建handler。这句话里隐含了几点重要知识:

  • HandlerThread是一个Thread线程,具有线程的特性。
  • Android中默认线程没有looper,如果想创建带有looper的线程需要在创建的过程中主动创造looper对象。
  • Handler中必须要有looper,它是整个消息查询、分发、处理的核心,在创建Handler的过程中可以指定任意线程的looper对象。

现在通过一个简单的示例演示下HandlerThread的使用方法:

public class MainActivity extends Activity {
    private static final String TAG = "Android_Test";

    private Button mButton;
    private TextView mText;
    
    // 新线程和与之相关联的 Handler 对象 
    private HandlerThread mHanderThread;
    private Handler mThreadHandler;
    
    // 和主线程相关的 Handler 对象
    private Handler mUiHandler;
    
    // 用于子线程和主线程中的消息分发
    private static final int MESSAGE_CODE_GET = 1;
    private static final int MESSAGE_CODE_SET = 2;

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

        mButton = (Button) findViewById(R.id.main_button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 主线程通过子线程 Handler 分发消息,以达到在子线程中处理耗时任务的目的。
                mThreadHandler.sendEmptyMessage(MESSAGE_CODE_GET);
            }
        });
        mText = (TextView) findViewById(R.id.main_text);
        
        // 创建 HandlerThread 并启动新线程
        mHanderThread = new HandlerThread("HandlerThread");
        mHanderThread.start();
        
        // 通过新线程中的 looper 创建相关的 Handler 对象
        mThreadHandler = new Handler(mHanderThread.getLooper()) {
          @Override
          public void handleMessage(Message msg) {
              Log.i(TAG, "mThreadHandler's thread: " + Thread.currentThread().getName());
              if (msg.what == MESSAGE_CODE_GET) {
                  try {
                      // 休眠 5 秒,模拟子线程处理耗时任务的过程。
                      Thread.sleep(5 * 1000);
                  } catch (InterruptedException ie) {
                      ie.printStackTrace();
                  }
                  // 向主线程 Handler 发送处理结果
                  mUiHandler.sendEmptyMessage(MESSAGE_CODE_SET);
              }
          }
        };

        mUiHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Log.i(TAG, "mUiHandler's thread: " + Thread.currentThread().getName());
                if (msg.what == MESSAGE_CODE_SET) {
                    // 主线程接收来自子线程的消息就行后续处理,这里是显示当前时间信息。
                    mText.setText(String.valueOf(SystemClock.uptimeMillis()));
                }
            }
        };
    }
}

这个示例的主要功能是主线程中发起任务,在子线程中处理这些耗时任务,处理完成后通知主线程并更新界面,并打印出运行过程,从下面的运行结果可以看到:耗时任务确实是在子线程中执行的。

03-11 10:04:57.311 30673 30723 I Android_Test: mThreadHandler's thread: HandlerThread
03-11 10:05:02.313 30673 30673 I Android_Test: mUiHandler's thread: main

从上面的示例可以总结得到HandlerThread的使用方法:

  1. 首先创建HandlerThread对象并运行它,在创建过程中需要指定线程名字;
  2. 获取HandlerThread对象中的looper并通过它来构造一个子线程Handler对象;
  3. 主线程通过子线程Handler对象向子线程分发任务;
  4. 子线程处理耗时任务并把处理结果分发到主线程,主线程进行后续的处理。