理解 HandlerThread 原理

栏目: 后端 · 发布时间: 5年前

内容简介:本人只是 Android小菜一个,写技术文档只是为了总结自己在最近学习到的知识,从来不敢为人师,如果里面有些不正确的地方请大家尽情指出,谢谢!这段

本人只是 Android小菜一个,写技术文档只是为了总结自己在最近学习到的知识,从来不敢为人师,如果里面有些不正确的地方请大家尽情指出,谢谢!

1. 概述

HandlerThreadAndroid 提供用来创建含有 Looper 线程的,其实在之前分析 IntentService 的博文中已经看到了它的应用,再来回顾下 IntentService 的启动过程:

public void onCreate() {
    // TODO: It would be nice to have an option to hold a partial wakelock
    // during processing, and to have a static startService(Context, Intent)
    // method that would launch the service & hand off a wakelock.

    super.onCreate();
    // 创建包含 Looper 的线程并启动之
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
    // 通过新线程的 Looper 创建 Handler 实例
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}
复制代码

这段 IntentService 的启动代码中直接使用到了 HandlerThread ,但当时只是一笔带过并没有仔细分析 HandlerThread 的使用方法和实现原理,本文将详细讲解如何在项目中使用 HandlerThread 和其内部的实现原理。

本文假设您对 Handler,Thread,Looper,Message 和 MessageQueue 相关知识有了一定的了解,所以涉及到它们的地方,只会稍作说明不再深入分析。

2. 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-01 10:04:57.311 30673 30723 I Android_Test: mThreadHandler's thread: HandlerThread
03-01 10:05:02.313 30673 30673 I Android_Test: mUiHandler's thread: main
复制代码

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

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

3. HandlerThread 原理分析

HandlerThread 和普通的 Thread 的区别就在于其内部是包含 Looper 的,所以我们分析的重点就是它是怎么创建使用 Looper 以及在使用后如何退出。首先来看下它的构造函数:

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;
    }
    
    /**
     * 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.
     */
    
    // 同时指定线程名字和优先级来构造 HandlerThread 对象
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    // 省略其他内容
    ...
}
复制代码

由于 HandlerThread 是直接继承 Thread 的,所以在通过 start() 启动线程后,其中的 run() 就会启动,这也是线程内部的核心方法,来看下其实现:

@Override
public void run() {
    mTid = Process.myTid();
    // 创建一个和当前线程有关的 Looper 对象
    Looper.prepare();
    synchronized (this) {
        // 得到当前线程的 Looper 对象后唤醒等待
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    // 调用回调方法,可以在开始消息轮询之前进行某些初始化设置,默认是空方法。
    onLooperPrepared();
    // 启动消息轮询,进行消息的查询分发和处理。
    Looper.loop();
    mTid = -1;
}
复制代码

这段代码就是 HandlerThread 中创建 Looper 对象并启动消息循环的核心,我们来一步步分析其重要逻辑。

3.1 创建 Looper 对象

在核心代码 run() 中首先看到的是 Looper.prepare() ,其作用就是创建当前线程的 Looper 对象:

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
复制代码

在使用 Looper.prepare() 创建 Looper 对象的过程中利用 ThreadLocal 把这个对象和当前线程建立了关联。

ThreadLocal 是一个可以存储线程局部变量的类,如果大家感兴趣可以自行查阅相关资料,在这里就不对其进行详细讲述了。

3.2 获取 Looper 对象

创建完 Looper 对象后会在同步代码块里去唤醒等待,那这个等待会发生在什么时候呢?记得示例中是通过 getLooper() 得到 Looper 对象的,来看下它的内部实现:

/**
 * 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() {
    // 线程没有启动或者已经死亡时返回 null
    if (!isAlive()) {
        return null;
    }
    
    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
        // 线程已经启动但是 Looper 对象还没有创建完成时等待
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    // 等待结束说明此时 Looper 对象已经创建完成,返回之。
    return mLooper;
}
复制代码

在这里看到当“线程已经启动但是 Looper 对象还没有创建完成”时会进行等待,当创建完成时会唤醒等待,这时 getLooper() 就可以返回已经创建完成的 Looper 对象了。之所以需要这个“等待-唤醒”机制,因为获取 Looper 是在主线程中进行的,而创建 Looper 是在子线程中进行的,必须使用这个机制来完成两者的状态同步。

3.3 开启 Looper 循环

前面已经讲了 Looper 对象的创建以及如何在主线程中获取,那么如何通过 Looper.loop() 开启循环呢?

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    // 获取Looper对象
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 获取消息队列
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    // Allow overriding a threshold with a system prop. e.g.
    // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
    final int thresholdOverride =
            SystemProperties.getInt("log.looper."
                    + Process.myUid() + "."
                    + Thread.currentThread().getName()
                    + ".slow", 0);

    boolean slowDeliveryDetected = false;

    // 开启一个无限循环来从消息队列中获取消息
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
        if (thresholdOverride > 0) {
            slowDispatchThresholdMs = thresholdOverride;
            slowDeliveryThresholdMs = thresholdOverride;
        }
        final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
        final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

        final boolean needStartTime = logSlowDelivery || logSlowDispatch;
        final boolean needEndTime = logSlowDispatch;

        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }

        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        final long dispatchEnd;
        try {
            // 获取到消息后,分发到 target 去处理。
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (logSlowDelivery) {
            if (slowDeliveryDetected) {
                if ((dispatchStart - msg.when) <= 10) {
                    Slog.w(TAG, "Drained");
                    slowDeliveryDetected = false;
                }
            } else {
                if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                        msg)) {
                    // Once we write a slow delivery log, suppress until the queue drains.
                    slowDeliveryDetected = true;
                }
            }
        }
        if (logSlowDispatch) {
            showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                       + msg.callback + " what=" + msg.what);
        }
        // 回收消息对象
        msg.recycleUnchecked();
    }
}
复制代码

这段代码非常长,在分析的时候不需要弄懂每一行的意思,只需要了解其中关于消息的大致处理流程即可,大家如果不想去看这大段代码,只需关注添加注释的几行即可,其基本流程是:通过一个无限循环从消息队列中查询 Message 消息,如果查询不到就等待,如果查询到就交给其 target 来处理,最后要回收资源。

3.4 退出 Looper 循环

在使用 HandlerThread + Handler 在子线程处理耗时任务后并且不再需要时,必须要退出 Looper 的消息循环,可以通过 quit()

/**
 * 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>
 */
public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quit();
        return true;
    }
    return false;
}
复制代码

这份方法可以退出 Looper 循环同时会把当前消息队列中的所有消息都抛弃,也无法再向该消息队列中发送消息。但有时我们并不想直接清空消息队列,这时可以使用另外一种方式:

/**
 * 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;
}
复制代码

这个方法可以更安全地退出,它会让消息队列中的非延迟消息继续得到处理,是更推荐的退出方式。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Little MLer

The Little MLer

Matthias Felleisen、Daniel P. Friedman、Duane Bibby、Robin Milner / The MIT Press / 1998-2-19 / USD 34.00

The book, written in the style of The Little Schemer, introduces instructors, students, and practicioners to type-directed functional programming. It covers basic types, quickly moves into datatypes, ......一起来看看 《The Little MLer》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具