Android 深入讲解Handler机制

1 前言

众所周知,在我们android中有主线程和子线程之分,我们对它们做一下区分:

1.在主线程中刷新UI,但不能做耗时操作,否则很可能报ANR异常
2.在子线程中不能刷新UI,但可以做耗时操作

这是二者的区别,也是一对矛盾。有没有办法解决这种问题呢?google官方给出的方法就是:

在子线程中进行耗时的业务逻辑,然后利用Handler通知主线程刷新UI。

学习Handler机制意义:
在android中只要牵扯异步加载+子线程更新UI,它背后的原理都是用Handler机制来做的。如:AsyncTask,Volley网络框架等等。所以说掌握了handler机制,我们再去看关于“子线程更新UI”的框架源代码是不是就简单多了呢?!真是一本万利的事啊!

下面我们对handler进行一下深入的分析。

2 ThreadLocal简介

讲解之前我们先要讲解一下ThreadLocal,因为在Handler中这个东西很重要。

大家先不要深究ThreadLocal,这只需要记住下面的特点即可:

    1.ThreadLocal中可以存放一个变量A,供多个线程使用。
    2.ThreadLocal会给每一个线程一个变量A的副本,这样多个线程通过ThreadLocal使用这个变量时,各个线程之间互不影响。也就是说A线程更改了变量A的值,并不会改变B线程中的变量A的值。

在本章讲解中大家对ThreadLocal掌握到此即可,如果想进一步学习可以参考我这一篇文章《Android开发之ThreadLocal的使用》
明白了ThreadLocal的这个特性之后,我们再去理解Looper的工作机制就会容易得多了,我们继续。

3 Handler探究

在我们的工作中Handler经常用,但是你发现没有:

我们一定要在主线程里面new一个handlder,然后在子线程中handler.sendMessage(message)来发送消息至主线程,最终消息在handleMessage(Message msg) {}得到相应的处理。

3.1 创建Handler

大家想一下,为什么一定要在主线程中new 一个handler呢,难道在子线程中不行吗?!好我们这就试一下,代码如下:

private class LooperThread  extends Thread{
        @Override
        public void run() {
            super.run();
            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) {
    this(callback, 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");
        }
    }

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

看17-18行,因为Looper.myLooper()获取一个Looper对象mLooper 为null,所以报错了。我们再看一下20行的错误代码。这不正式我们报的错嘛!错误的意思是:

    不能在thread中创建handler,因为没有调用Looper.prepare()方法。

既然错误明确了让我们调用Looper.prepare()方法,那我们就调用一下试试,代码如下:

private class LooperThread  extends Thread{
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            Handler handler=new Handler();
            System.out.println("add code : Looper.prepare()");
            //doing something
        }
    }

再运行,果然不报错了!!

小结:
这里我们先做一个小结,说的太多怕大家消化不了:

    1.new一个handler之前首先要调用 Looper.prepare();否则会报错。

那么你可能有疑问,骗人的吧,为什么我用handler从来就没有调用过呢?!在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。在ActivityThread中的main()方法中,代码如下:

public static void main(String[] args) {  
   ...
   上面很多代码省略
   ... 
   Looper.prepareMainLooper();  
   ...
   下面很多代码省略
   ... 
    Looper.loop();  
}  

在prepareMainLooper()方法中又去调用了Looper.prepare()方法。代码如下:

public static final void prepareMainLooper() {  
    prepare();  
    setMainLooper(myLooper());  
    if (Process.supportsProcesses()) {  
        myLooper().mQueue.mQuitAllowed = false;  
    }  
}  

因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。

3.2 Looper讲解

好到此为止,我们解释了为什么在子线程中直接new Handler()会报错。但是还有点小尾巴要处理一下:

 Looper.prepare(); 里面是什么东西我还没看呢?!
 Looper.myLooper();为什么在线程中取出的值是null?我还不知道呢!
 Looper到底是个什么玩意,还没告诉我呢!

其实这二个方法正是一对,我们看一下它们的源代码:

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

public static final void prepare() {  
    if (sThreadLocal.get() != null) {  
        throw new RuntimeException("Only one Looper may be created per thread");  
    }  
    sThreadLocal.set(new Looper());  
}  

由代码可以看出:

1.myLooper()方法是从ThreadLocal取出一个Looper对象,因为在线程中第一次取,肯定是null。
2.prepare()是向ThreadLocal中添加一下Looper对象,它会先判断是否有一个Looper了,如果没有就设置,否则会报错。如果你调用2次Looper.prepare()就会报错误。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象

好,到现在为止,我们的ThreadLocal开始露出庐山真面目了,ThreadLocal中存放一个Looper对象,这个Looper对象供很多子线程使用,但是子线程之间相互不影响。这个后面会通过源码再讲解,大家在这里先记着,有个印象。

下面我们来说一下Looper是个什么东西:
我们先来看一下Looper的构造方法

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

可以看出,Looper中有一个消息队列MessageQueue和一个当前的线程。 在构造方法中初始化了它们大家注意,对线程的初始化,其实就是把当前的线程赋给它。所以,Looper,MessageQueue(消息队列)和线程是一一对应的,也就是说:“一个线程对应一个Looper和一个MessageQueue”或者“一个Looper对应一个线程和一个MessageQueue”,或者“一个消息队列对应一个Looper和一个线程“。
总结:

1.一个线程对应一个Looper
2.一个Looper对应一个消息队列
3.一个线程对应一个消息队列
4.线程,Looper,消息队列三者一一对应
5.当收到消息Message后系统会将其存入消息队列中等候处理。Looper在Android的消息机制中担负着消息轮询的职责,它会不间断地查看MessageQueue中是否有新的未处理的消息;若有则立刻处理,若无则进入阻塞。

3.3 Message的发送讲解

在讨论完Looper、线程、消息队列这三者的关系之后我们再来瞅瞅Android消息机制中对于Message的发送和处理。
看看我们最常用的handler使用方式是不是这样的:

handler.sendMessage(message)——>发送消息 
handleMessage(Message msg){}——>处理消息

另外我们还可以通过post()、postAtTime()、postDelayed()、postAtFrontOfQueue()等方法发送消息。不管那种方法,除了postAtFrontOfQueue()之外这几个方法均会执行到sendMessageAtTime(Message msg, long uptimeMillis)方法,我们来看看它的源码:

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

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    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, 0);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

我们重点看enqueueMessage方法,这个方法分2个步骤:

1.第一步: 
给msg设置了target,请参见代码第24行,这个target赋值,大家要记得,后面的loop()方法中要用到。
此处的this就是当前Handler对象本身。在这就指明了该msg的来源——它是由哪个Handler发出的,与此同时也指明了该msg的归宿——它该由哪个Handler处理。不难发现,哪个Handler发出了消息就由哪个Handler负责处理。

2.第二步: 
将消息放入消息队列中,请参见代码第28行    
在enqueueMessage(msg,uptimeMillis)中将消息Message存放进消息队列中,距离触发时间最短的message排在队列最前面,同理距离触发时间最长的message排在队列的最尾端。若调用sendMessageAtFrontOfQueue()方法发送消息它会直接调用该enqueueMessage(msg,uptimeMillis)让消息入队只不过时间为延迟时间为0,也就是说该消息会被插入到消息队列头部优先得到执行。

上面我们说过:

1.一个线程对应一个Looper
2.一个Looper对应一个消息队列
3.一个线程对应一个消息队列
4.线程,Looper,消息队列三者一一对应

那么:

24行:msg.target = this;中的this就是当前handler本身。
28行:queue.enqueueMessage(msg, uptimeMillis);中的queue就是该线程所对应的消息队列.

为什么这么说呢?我们回头看看handler的构造方法

 public Handler(Callback callback, boolean async) {
      ...
      ...
        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;
    }

(1) 获取Looper,请参见代码第4行
(2) 利用Looper的消息队列为mQueue赋值,请参见代码第9行
(3) 为mCallback赋值,,请参见代码第10行
(4) 为mAsynchronous赋值,,请参见代码第11行

看到了吧,这个mQueue就是从Looper中取出来的。在之前我们也详细地分析了Looper、线程、消息队列这三者的一一对应关系,所以此处的mQueue正是线程所对应的消息队列。
总结:
发送消息时:
第一:给msg标记上要处理它的handler(每个msg对应不同的handdler)
第二:将消息放入消息队列queue中,这个消息队列是我们在handler构造方法中利用Looper早就建立好了。
再次请大家记住:Looper、线程、消息队列是一一对应的。
在28行消息队列调用enqueueMessage(msg, uptimeMillis)方法,我们再来看看,消息队列中是如何对消息进行排序的,代码如下:

final boolean enqueueMessage(Message msg, long when) {  
    if (msg.when != 0) {  
        throw new AndroidRuntimeException(msg + " This message is already in use.");  
    }  
    if (msg.target == null && !mQuitAllowed) {  
        throw new RuntimeException("Main thread not allowed to quit");  
    }  
    synchronized (this) {  
        if (mQuiting) {  
            RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");  
            Log.w("MessageQueue", e.getMessage(), e);  
            return false;  
        } else if (msg.target == null) {  
            mQuiting = true;  
        }  
        msg.when = when;  
        Message p = mMessages;  
        if (p == null || when == 0 || when < p.when) {  
            msg.next = p;  
            mMessages = msg;  
            this.notify();  
        } else {  
            Message prev = null;  
            while (p != null && p.when <= when) {  
                prev = p;  
                p = p.next;  
            }  
            msg.next = prev.next;  
            prev.next = msg;  
            this.notify();  
        }  
    }  
    return true;  
}  

观察上面的代码的16~31行我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。下面我们来是讲解一下出队列。

3.4 Message的处理讲解

消息是如何被处理的呢?其实Looper的消息队列不断的被轮询。在Looper中有这样一个方法:loop();它是一个死循环,专门为了从消息队列中取消息,如果消息队列中没有消息了,它就堵塞。我们看一下它的代码:

public static void loop() {
        final Looper me = myLooper();
        ...省略很多代码....
        final MessageQueue queue = me.mQueue;

        //重点在这里,for死循环,不雄姿英发的从消息队列中取消息
        for (;;) {
            Message msg = queue.next(); // 取出一条消息
            if (msg == null) {//判断消息是否为空
                return;
            }
            ...
            省略很多代码
            ... 
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            省略很多代码
            ...
            msg.recycleUnchecked();
        }
    }

为了节省篇幅,我省略了一些代码。
第4行,我们从Looper中取得消息队列。
第7行,这是一个for循环,而且是一个死循环,目的是不断的轮询消息队列。
第8行,Message msg = queue.next() ,这行代码是从消息队列中取出一条消息。
第16行,msg.target.dispatchMessage(msg);我们在上面发送消息的enqueueMessage方法中讲过,msg.target是该mes对应的handler。然后handler调用dispatchMessage方法来分发消息,然后进入handler的handlemessage()方法来处理。好,下面我们看看dispatchMessage()方法中都写了啥?

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

哈哈,看到这里是不是晴朗多了,这个方法之所以写了这么多if语句,是因为我们创建handler的方式不一样,它要兼容一下:

 第一个判断:
 if (msg.callback != null) {
        handleCallback(msg);
    }
这个if判断是处理Message的回调callback
比如调用handler.post(Runnable runnable)时,该runnable就会被系统封装为Message的callback。 
关于这点在源码中也有非常直观的体现:
private static Message getPostMessage(Runnable r) {
   Message m = Message.obtain();
   m.callback = r;
   return m;
}

第二个判断:
if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
}
这是处理Handler的回调callback,比如执行Handler handler=Handler(Callback callback)时就会将callback赋值给mCallback

第三个判断:
调用handleMessage()处理消息Message,比如:
Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };

3.5 梳理Handler机制

Android异步消息机制中主要涉及到:Thread、Handler、MessageQueue、Looper,在整个机制中它们扮演着不同的角色也承担着各自的不同责任。

1.Thread
负责业务逻辑的实施。 
线程中的操作是由各自的业务逻辑所决定的,视具体情况进行。

2.Handler
负责发送消息和处理消息。 
通常的做法是在主线程中建立Handler并利用它在子线程中向主线程发送消息,在主线程接收到消息后会对其进行处理

3.MessageQueue
负责保存消息。 
Handler发出的消息均会被保存到消息队列MessageQueue中,系统会根据Message距离触发时间的长短决定该消息在队列中位置。在队列中的消息会依次出队得到相应的处理。

4.Looper
负责轮询消息队列。 
Looper使用其loop()方法一直轮询消息队列,并在消息出队时将其派发至对应的Handler.

我做了一张图帮助大家理解:
这里写图片描述

3.6 Handler正确的使用方式:

通过上面的讲解,我们来做一个总结,看看正确的handler如何使用:

class LooperThread extends Thread {  
      public Handler mHandler;  

      public void run() {  
          Looper.prepare();  

          mHandler = new Handler() {  
              public void handleMessage(Message msg) {  
                  // process incoming messages here  
              }  
          };  

          Looper.loop();  
      }  
  }  

大家可能心生疑问,为什么我使用handler从来没用过Looper.prepare(); 和 Looper.loop(); 这二句,这是如果你在主线程中创建handler,主线程已经给你调用了,不需要我们自己去调用,但是如果你在子线程中创建,那么必须要调用这二句。

我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。

3.7 Handler的一点延伸

Handler不但可以发送消息,而且还可以在线程中进行UI操作,使用方式如上:

1.getActivity().runOnUiThread(Runnable)
2.View.post(Runnable)
3.View.postDelayed(Runnable, long)
4.new Handler(Looper.getMainLooper()).post(Runnable);
6.handler.post(Runnable);

如:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap =
                    loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

其实之所以可以在线程中进行UI操作,是因为handler都是在主线程中创建的。既然是在主线程中创建的,那么一定不要在其中做耗时操作!

4 结尾

好了就讲到这里吧,希望对大家有所帮助,在技术上我依旧是一个小渣渣,加油勉励自己!

5 参考文档

1.深入探讨Android异步精髓Handler
2.Android异步消息处理机制完全解析,带你从源码的角度彻底理解

猜你喜欢

转载自blog.csdn.net/mffandxx/article/details/71248486
今日推荐