文档章节

从源码角度深入理解Handler处理机制

 风过后
发布于 2016/01/13 10:13
字数 1850
阅读 82
收藏 4

        一开始接触android的同学应该都知道,一般情况下android是不能在子线程中更新ui的。要更新ui界面的时候我们就需要用到android给我们提供的Handler机制。

        Handler机制中核心的几个类有Handler、Looper、MessageQueen、Message这四个类,下面我们来一一剖析。

        首先来谈谈Handler的用法(HOW):

        Handler中发送消息的函数可以分为两类:一类是post系列,一类则是sendMessage系列。

        1)post系列中包括如下方法:

                                            post(Runnable)

                                            postAtTime(Runnable, long)

                                            postDelayed(Runnable, long)        

        方法的具体意义这里不做解释,不知道的朋友可以去查下文档。post方法是容许你排列一个Runnable的线程对象到主线程队列中,具体的实现我们待会儿再看。

        2)sendMessage系列中包括的方法也大致和post中的差不多

                                             sendEmptyMessage(int)

                                             sendMessage(Message);

                                             sendMessageAtTime(Message,long);

                                             sendMessageDelayed(Message, long);

        Handler类的部分源码:

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

    如上是Handler中的post(Runnable)的方法,我们可以看到实际上是调用的Handler中的sendMessagexx系列的方法,所以这两种实际上是相同的。然后下面我们来看看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);
    }

    请大家看最后一行,我们可以看到sendMessageAtTime方法最后的结果enqueueMessage,enqueueMessage我想不用说看字面意思都应该很好理解就是一个将msg放入消息队列,也就是MessageQueue中,还有大家看到上面的一个判断queue==null,如果queue为null的画则会抛出错误,这也就是说我们在sendMessage方法之前必须先新建一个messageQueue,在Android中UI线程在启动时就给我们建立了一个MessageQueue,大家不信可以去看看相关源码,这里不多做解释,然后如果我们要在自己的子线程中用到拥有自己的Handler处理,则需要在子线程中维持一个该线程的MessageQueue,那么这个MessageQueue是怎样产生的呢,下面来看到Looper类,对就是Looper类:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
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对象的时候往往都会这样使用

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

上面是Looper类中的注释,介绍对Looper的使用,注意顺序 Looper.prepare(); XXX; Looper.loop()这样的顺序,仔细看前面prepare的函数通过调用此函数我们可以得到一个MessageQueue的对象,也就是说MessageQueue的对象是在这里产生的,所以Android给我们的使用示例会先调用prepare然后再是loop,还有一点需要注意一个子线程最多只能有一个MessageQueue对象,如果你调用两次prepare则会产生Only one Looper may be created per thread的异常,我相信很多人应该都遇到过这个异常吧,反正本人初学Android之时是遇到过,后来解决了,只是不知道为什么会这样,这就是原因。

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

loop函数则是一个死循环不断的去取MessageQueue中的Message来,然后去执行里边的逻辑。那么还有一个很重要的问题没有解决,那就是当looper取到MessageQueue里边的Message之后,我们都知道这时候应该要执行我们的Handler类中的

handleMessage(Message msg) {
}

此方法了,最后由我们客户端去实现handleMessage的具体逻辑,对吧?那么问题是怎么来的,还有Looper取到的Message怎么知道应该执行哪一个handler的handleMessage方法呢?要知道我们可能在程序中声明很多个handler,那么我们接着往下看Message类

103 /*package*/ Handler target;

在Message类中的103行声明有一个target变量,对就是这个target!这个target变量存储的就是对这个消息是属于哪一个Handler的一个引用,也就是说这个消息需要哪个Handler来处理。就是它!这样就全部联系起来了是吧。

我们再次回过头去看Looper类中的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(); // 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
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(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();
    }
}

大家仔细看看上面的代码for(;;)里边的内容就是我上面所说的不断循环去读取Messagequeue中的Message当读取到其中一个Message后执行了这行代码:

msg.target.dispatchMessage(msg);

由上面的讲解我们知道msg.target是一个Handler对象这样我们就回到了我们Handler类的dispatchMessage方法,有人会问不对呀我们最后都是在HandlerMessage方法中处理的呀,先别捉急,心急可是吃不了热豆腐哦,我们来看这个dispatchMessage方法到底是何方神圣,好回到Handler类了:

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

这个函数很简单,大家看到没有这里有两种处理方法,第一种当msg.callback不为空则执行handleCallback,第二种当msg.callback为空的事后则执行了我们所熟悉的handlerMessage方法,我想第二种方式大家应该都知道,handleMessage大家应该都重写过这个方法。那么来说说第一种,回到最开始,我们最初的起点就是这里,绕了一圈又回来来的!我们最开始的时候说了,Handler发送消息分为两个系列,虽然这两个系统在发送消息时都会调用同一个函数,但是在回调处理的时候还是略有不同。第一种就是post(Runnable),我们都知道Runnable是一个线程,那么我们可以猜想handleCallback应该是执行我们线程里边的run方法,这样才合理嘛,回调回来。那我们下面就看看是不是这样的

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

看到没有我们的大Android系统果然没有让我们失望,就一句好执行回调的run方法。

最后回头来看我们handler发送消息的两种方式,分别对应上面说的两种回调方式,一目了然有木有!

handler.post(new Runnable() {
@Override
public void run() {
     //anything what you can do
}
})
mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
mHandler.sendMessage(msg)



© 著作权归作者所有

上一篇: android相机拍照
下一篇: android动画浅析
粉丝 1
博文 17
码字总数 16917
作品 0
成都
私信 提问
Android异步消息处理机制完全解析-Handler详解

参考资料 - 官方介绍文档 - Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系 - Android异步消息处理机制完全解析,带你从源码的角度彻底理解 - 慕课网课程-Androi...

javen205
2017/03/25
0
0
Android Handler异步通信:深入详解Handler机制源码

前言 在开发的多线程应用场景中,机制十分常用 今天,我将手把手带你深入分析 机制的源码,希望你们会喜欢 目录 1. Handler 机制简介 在多线程的应用场景中,将工作线程中需更新的操作信息 ...

carson_ho
2018/05/21
0
0
深入理解Android消息处理系统——Looper、Handler、Thread

熟悉Windows编程的朋友可能知道Windows程序是消息驱动的,并且有全局的消息循环系统。而Android应用程序也是消息驱动的,按道理来说也应该提供消息循环机制。实际上谷歌参考了Windows的消息循...

惊天
2011/07/26
0
0
Android AsyncTask完全解析

我们都知道,Android UI 是线程不安全的,如果想要在子线程里进行UI 操作,就需要借助Android 的异步消息处理机制。之前我也写过了一篇文章从源码层面分析了Android 的异步消息处理机制,感兴...

yid11
2014/08/03
478
0
Android 开发进阶指南

之前有人在朋友圈评论我说,现在学习Android 遇到了瓶颈,基本上一般的API,市面上大部分App UI 都能做出来,该怎么提高?然后我回复他了,我学习Android 也有一年左右,也是个菜鸟,也遇到这...

程序袁_绪龙
2016/03/15
39
0

没有更多内容

加载失败,请刷新页面

加载更多

【AI实战】手把手教你深度学习文字识别(文字检测篇:基于MSER, CTPN, SegLink, EAST等方法)

文字检测是文字识别过程中的一个非常重要的环节,文字检测的主要目标是将图片中的文字区域位置检测出来,以便于进行后面的文字识别,只有找到了文本所在区域,才能对其内容进行识别。 文字检...

雪饼
今天
5
0
思维导图XMind 8 Pro 绿化方法(附序列号)

按部就班: Step 1 -全新下载最新版本的 Xmind 8(注必须是英文官方的版本,中文代{过}{滤}理网站的版本修改过,无法使用pj); Step 2 -安装完毕后,点击文末的下载按钮下载pj补丁文件包,将...

一只小青蛙
今天
10
0
数据结构(ER数据库)设计规范

表命名规范 表命名的规则分为3个层级,层级之间通过_分割,例如b_r_identity、d_l_identity。规约为: [leavel]_[type]_[name] [leavel] 表示数据库表的层级和功能,分为: s:业务无关的系统...

随风溜达的向日葵
今天
5
0
阿里Sentinel控制台源码修改-对接Apollo规则持久化

https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel 动态规则扩展 https://github.com/alibaba/Sentinel/wiki......

jxlgzwh
昨天
8
0
在Linux系统中创建SSH服务器别名

如果你经常通过 SSH 访问许多不同的远程系统,这个技巧将为你节省一些时间。你可以通过 SSH 为频繁访问的系统创建 SSH 别名,这样你就不必记住所有不同的用户名、主机名、SSH 端口号和 IP 地...

老孟的Linux私房菜
昨天
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部