理解 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;
}
复制代码

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


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

查看所有标签

猜你喜欢:

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

C Primer Plus

C Primer Plus

Stephen Prata、云巅工作室 / 云巅工作室 / 人民邮电出版社 / 2005-2-1 / 60.00元

《C Primer Plus(第5版)(中文版)》共17章。第1、2章学习C语言编程所需的预备知识。第3到15章介绍了C语言的相关知识,包括数据类型、格式化输入输出、运算符、表达式、流程控制语句、函数、数组和指针、字符串操作、内存管理、位操作等等,知识内容都针对C99标准;另外,第10章强化了对指针的讨论,第12章引入了动态内存分配的概念,这些内容更加适合读者的需求。第16章和第17章讨论了C预处......一起来看看 《C Primer Plus》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器