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

Posted MyLero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 , 但是它可以做到完成任务后自动退出,下一篇一起看看其是怎么做到的。




























以上是关于Android 并发二三事之 Handler 机制的妙用 HandlerThread的主要内容,如果未能解决你的问题,请参考以下文章

磁盘性能压测二三事之——性能参数和指标

Redis二三事一把LOL的时间让你了解Redis的主从复制机制(超详细步骤图解)

Redis二三事一文了解Redis的哨兵机制(超详细步骤图解)

我与nginx那些不得不说的私密二三事!!!

Redis二三事一把LOL的时间让你了解Redis的主从复制机制(超详细步骤图解)

深入浅出的来谈一下硬盘扩容的二三事