android handler简介

272 阅读6分钟

Handler, MessageQueue, Looper概述

众所周知,Handler的运行需要底层MQ和lopper的支撑,它主要用来实现线程切换。比如读取本地文件内容,读取完毕之后将其展示到UI,如果我们直接使用主线程去读物文件,当文件很大时,则会出现ANR错误。那么对于这种耗时操作我们必须在一个新的线程中去执行,执行完毕之后将结果通知给主线程即可。

MessageQueue简称MQ,直译为消息队列,虽然名为队列,但他使用了单向链表来存储消息。

由于MQ仅仅用来存储消息,那么我们如何操作消息呢?Looper应用而生。Looper会以死循环的形式去查询MQ中是否包含新的消息,如果有,则将该消息发送给创建Handler的线程。

想一想,Android是如何限制在非UI线程中访问UI的呢?为什么要限制呢? android中使用了ViewRootImpl对UI操作做校验。至于为什么要限制,则是因为UI操作不是线程安全的。如果对UI操作加锁,一则效率低下,二则处理复杂。


Handler实现原理

Handler只能在拥有Looper的线程中创建(因为在UI线程中,系统已经自动为我们创建好Looper了,所以我们可以直接新建Handler来实现线程切换;系统还提供了HandlerThread来创建带有Looper的线程,这样我们也可以在HandlerThread中直接创建Handler)。

也就是说Handler中有Looper,Looper中又有MQ,那么三者的关系就很明确了。Handler先发送消息给MQ,然后Looper遍历MQ将查找到的Message发送给Handler。

泛泛而谈太过空洞,我们结合实例来说说吧~之前我们提到过HandlerThread,它自带Looper,那么我们可以通过分析它的原来来弄懂Looper的具体使用过程及原理。HandlerThread的源码不长,我们一段一段来分析。

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

    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() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    /**
     * 返回当前线程对应的Looper,如果线程还没有start,或者线程死亡了,则返回null。
     * @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;
    }

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

HandlerThread首先是一个Thread,在构造函数中初始化了当前线程的名字和优先级。每当启动Thread时,则必定调用run方法(废话),那么我们看看在run方法中到底干了什么呢?

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

首先调用Looper.prepare()方法为当前线程准备好Looper,那么我们来看看prepare的源码

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中使用了ThreadLocal来保存每个Thread对应的Looper对象。换句话说,一个线程对应一个Looper,而在Looper中又使用了ThreadLocal类型的类对象保存了多个Looper。

然后调用Looper.myLooper()来获取当前线程对应的Looper对象:

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

紧接着就直接调用了Looper.loop()方法,我们来看看loop()中究竟做了什么

    public static void loop() {
        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();

        for (;;) {
            Message msg = queue.next(); // 如果队列为空,则阻塞(next方法内部中一死循环形式实现)
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            
            msg.target.dispatchMessage(msg);
            msg.recycleUnchecked();
        }
    }

可以看到,loop()很简单,遍历MQ,如果MQ中有Message,则将message分发给对应的Handler,如果没有对退出。

至此,Looper本身的而逻辑就很清楚了。Looper仅负责保存各个线程的looper对象(使用ThreadLocal)和遍历MQ并将MQ中的消息分发给target。

那么我有一个疑问,既然当MQ为空时,loop()方法会结束并返回,那么如果我在在loop()方法结束之后发送了一个message给MQ,Looper是怎么监听到我发送了这个消息呢?

这里需要注意的一点事queue.next()方法在队列为空时会阻塞当前线程,直到队列中有新的消息到来或者要线程结束时释放锁。

那么我又有一个疑问,既然已经阻塞在MQ.next()中,那么此时当前线程即处于阻塞状态,怎么继续往下执行呢?呵呵哒。

我们再来详细捋一遍handler的运行过程:

首先在Thread-1中准备Looper,将Thread-1保存在Looper#ThreadLocal对象中,然后调用Looper .loop()方法,让Thread-1进入死循环中去持续查找MQ中是否有消息。如果MQ为空,则将Thread-1阻塞在MQ.next()方法中。那么Thread-1处于blocking状态。回想一下,Handler的作用是完成线程间切换的。我们来看看下面的代码:

//按钮点击响应事件
public void startHandlerThread(View view) {
        Log.i("HandlerThread", "startHandlerThread");
        HandlerThread ht = new HandlerThread("handlerTest") {

            @Override
            public void run() {
                Log.i("HandlerThread", "run: Thread-1 is starting");

                try {
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                super.run();
            }
        };
        
        ht.start();
        Looper looper = ht.getLooper();
        Handler handler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.i("HandlerThread", "handleMessage: " + msg.obj);
            }
        };
        
        //启动Thread-2
        Log.i("HandlerThread", "startHandlerThread: 阻塞了主线程");
        Thread t = new Thread(new Thread2(handler));
        t.start();
}

private class Thread2 implements Runnable {
        private Handler mHandler;

        Thread2(Handler handler) {
            mHandler = handler;
        }

        @Override
        public void run() {
            Message message = new Message();
            message.what = 2;
            message.obj = "我来自Thread2";
            mHandler.sendMessage(message);
        }
    }

先摆出这段代码的执行结果吧!

08-31 10:06:31.988 15642-15642/cn.codemperor.summary I/HandlerThread: startHandlerThread
08-31 10:06:31.990 15642-16752/cn.codemperor.summary I/HandlerThread: run: Thread-1 is starting
08-31 10:06:51.994 15642-15642/cn.codemperor.summary I/HandlerThread: startHandlerThread: 阻塞了主线程
08-31 10:06:51.994 15642-15642/cn.codemperor.summary I/HandlerThread: Thread2 is accessed
08-31 10:06:52.002 15642-16752/cn.codemperor.summary I/HandlerThread: handleMessage: 我来自Thread2

可以看到第2行和第3行之间的相差20秒左右。这是因为我们在HandlerThread中sleep了20秒,而HandlerThread#super.run()就一直被阻塞,导致HandlerThread#Looper无法被初始化,那么ht.getLooper()就一直处于wait状态(因此主线程也处于阻塞状态,所以如果在sleep期间点击了屏幕,则会出现ANR),直到Looper被实例化。那么下面的Handler无法被初始化,Thread2也无法启动。也就是说我们无法在 HandlerThread中做耗时操作吗?哈哈,还是太天真了,Google怎么会开发出如此鸡肋的东西。HandlerThread是具有Looper的子线程,Handler的初始化使用了HandlerThread中的Looper,也就是说Handler中的handleMessage是执行在子线程中的,那么我们就可以在handleMessage中执行耗时操作呀!哈哈,代码如下:

 Handler handler = new Handler(looper) {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Toast.makeText(HandlerActivity.this, "HandlerActivity", Toast.LENGTH_LONG).show();
        try {
            Thread.sleep(10000);
            Log.i("HandlerThread", "handleMessage: " + msg.obj);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

至此,关于Handler, HandlerThrad, Looper, MQ的知识点基本就已经清楚了。