首页 > 代码库 > Android 并发二三事之 Handler 机制的妙用 HandlerThread

Android 并发二三事之 Handler 机制的妙用 HandlerThread

Android 并发第五篇

本篇会讲解如何利用 HandlerThread 实现异步操作。

HandlerThread 本身其实就是一个 Thread ,但是其内部还利用 Handler 机制。
对于提交的任务(或者说是信息 Message)依次处理。
所以在介绍 HandlerThread 原理以及如果使用之前,会首先说一个 Handler 异步机制。

当然 Handler, Looper, Message 之间的关系相信很多人都已经很熟悉了,这里会只着重介绍和本节相关的内容。

一 、Handler 机制:

1、 我们都知道,在子线程中通知主线程更新UI界面,需要使用Handler。
一般我们就直接在 Activity 中直接 初始化一个Handler 对象,像这样:

        Handler uiHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };

重写 handlerMessage() 方法,然后利用 uiHandler 对象在子线程中发送消息。

2、 或者我们也可以直接在主线程直接 new 一个 Handler 对象:

Handler handler = new Handler();

但在子线程中 new Handler()需要这样:

Handler handler = new Handler(Context.getMainLooper());

然后在子线程中:

        handler.post(new Runnable() {
            @Override
            public void run() {
        //执行在主线程中
                Log.d(TAG, "run on UI  Thread Id : "+Thread.currentThread().getId());
            }
        });

Handler 源码:

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

在handler 中会将Runnable 对象赋值给 message.callback 属性,封装成Message,调用 sendMessageDelaye() 将消息发送出去。
sendMessageDelaye() 方法最后在辗转几次后最终会调用sendMessageAtTime() 将消息放到消息队列中。

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

而 Handler.sendMessage()的源码为:

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

所以本质上,无论是利用Handler.sendMessage(),还是 Handler.post() 都是将消息添加到消息队列之中。

那么为什么在子线程中需要传入 MainLooper , 而主线程却不需要呢?

首先我们是要在子线程中通知主线程,那么我们便需要代码执行在UI 线程中。
如果在子线程中直接:

Handler handler = new Handler();

会抛出异常:
Can’t create handler inside thread that has not called Looper.prepare()
我们可以看一下源码:

Handler 源码:

    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();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can‘t create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

我们能够看到,无参的构造方法,会调用public Handler(Callback callback, boolean async) 。
在这个方法中,调用 Looper.myLooper(); 获取 Looper 对象,之所以抛出异常,一定是其为null了。
那么为什么没有获取到Looper对象呢?
接下来看Looper源码:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

从 ThreadLocal 变量中获取当前线程的值,那么这个值是在哪里设置的呢?

    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() 中设置的。
也就是说在子线程中直接 new Handler() 对象,需要先调用Looper.prepare() 方法。
而在主线程中是不需要的,因为在应用初始化时,已经调用 Looper.prepare() 了。
而Looper 中还有一个方法:Looper.loop() 方法

Looper.loop() 内包含一个死循环,不断的从队列中获取消息,如果没有消息时,会阻塞。
Looper.loop() 调用了 Handler.dispatchMessage() 方法:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        message.callback.run();
    }

在dispatchMessage() 方法中会调用 handleMessage() 方法,或者调用 handleCallback() 方法处理我们利用Handler post Runnable
所封装的消息。

3 、所以通过以上总结
我们知道如果Looper.loop()是在主线程中调用的,那么我们重写的 handlerMessage() 方法
和封装在消息中的 Runnable 都会在主线程中执行。
反过来说,如果Looper.prepare() 以及 Looper.loop() 是在子线程中调用的,那么基于子线程的Looper,所创建的Handler
所发送的消息都将会执行在子线程中,HandlerThread 便是利用了这个原理。

二 、HandlerThread

1 、我们首先看一下 HandlerThread 如何使用:

    private void requestWithHandlerThread() {
    //初始化一个 HandlerThread 对象
        HandlerThread handlerThread = new HandlerThread("HandlerThread");
    //调用start() 方法
        handlerThread.start();
    Log.d(TAG, "Main : "+Thread.currentThread().getId());
        Log.d(TAG, "HandlerThread : "+handlerThread.getId());
    //初始化一个Handler 对象,利用 HandlerThread 中的 Looper 对象
        Handler handler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //执行在子线程中
                Log.d(TAG,"Thread : "+Thread.currentThread().getId() +"   "+msg.obj);


            }
        };
        Message message = Message.obtain();
        message.obj = "From Message";
        handler.sendMessage(message);
        handler.post(new Runnable() {
            @Override
            public void run() {
        //执行在子线程中
                Log.d(TAG, "post : "+Thread.currentThread().getId());
            }
        });
    }

2 、结果:

11-15 17:20:15.634 12297-12297/com.loader.demo D/Demo: Main : 1
11-15 17:20:15.634 12297-12297/com.loader.demo D/Demo: HandlerThread : 26599
11-15 17:20:15.640 12297-12416/com.loader.demo D/Demo: Thread : 26599   From Message
11-15 17:20:15.640 12297-12416/com.loader.demo D/Demo: post : 26599

在这里 HandlerThread 需要和 Handler 一起配合使用,HandlerThread 提供一个在子线程中创建的 Looper 。
按照之前的推论,Looper.prepare(), 以及 Looper.loop() 都是执行在子线程中,那么在处理消息时也必然执行在子线程中。
所以其实现了异步的效果。

3 、接下来看一下 HandlerThread 的源码:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

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

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }


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

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

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

    public int getThreadId() {
        return mTid;
    }
}

4 、总结

可以看到 HandlerThread 本身是一个 Thread 所以其 run() 方法会执行在子线程中。在 run() 方法中首先调用了Looper.prepare(),
用于初始化消息队列以及Looper对象,紧接着调用 Looper.loop() 开始从消息队列中轮询,一旦有消息便将消息取出处理。
因为整个过程都执行在子线程中,所以当我们用在子线程中创建的Looper作为参数传给Handler时,其处理消息的代码就会执行在子线程中了。

以上便是 HandlerThread 的原理,主要还是利用 Handler,Message, Looper 之间的关系。

三 、自定义 HandlerThread:

当我们了解了其原理之后,其实我们也可以自定义自己 HandlerThread , 在线程之中处理消息。
现在我们自定义一个MyHandlerThread 同样继承 Thread。

1 、代码如下:

public class MyHandlerThread extends Thread {

    private Handler asyncHandler;
    private Looper mLooper;
    public MyHandlerThread() {

    }

    @Override
    public void run() {

        Looper.prepare();
        mLooper = Looper.myLooper();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        asyncHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                handlerThreadMessage(msg);
            }
        };
        Looper.loop();

    }

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

    //退出
    public void quit() {
        Looper looper = getLooper();
        if(looper != null) {
            looper.quit();
        }
    }



    /**
     * 发送消息
     * @param message
     */
    public void sendMessage(Message message) {
        if(asyncHandler != null) {
            Log.d("test","sendMessage");
            asyncHandler.sendMessage(message);
        }
    }

    /**
     * 处理消息
     * @param message
     */
    private void handlerThreadMessage(Message message) {
        Log.d("test","Message : "+message.obj+" Thread " +Thread.currentThread().getId());
    }
}

2、用法也很简单:

定义变量:

private MyHandlerThread handlerThread;

在onCreate() 中初始化:

handlerThread = new MyHandlerThread();
handlerThread.start();

在需要异步时调用:

    private void updateData() {
        Message message = Message.obtain();
        message.obj = "更新数据";
        handlerThread.sendMessage(message);
    }

这样我们便实现自定义 HandlerThread ,其中我们还可以根据需求封装不同发送消息的方法。
并且我们还将提交任务的代码和在子线程中处理任务的代码分开了,两块代码利用 MessageQueue 相连接,
那么这是不是也算是一种生产者消费者模式呢? 因为Handler 机制本身也算是一种生产者消费者模式啊。

四、

下一篇会讲解 Android 中另外一个可以实现异步的类: IntentService 。
IntentService 本身当然是一个 Service , 但是它可以做到完成任务后自动退出,下一篇一起看看其是怎么做到的。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android 并发二三事之 Handler 机制的妙用 HandlerThread