Handler 源码 走一走,看一看

Handler 源码 走一走,看一看 Handler 是如何发送和处理消息

文章将分为以下几步介绍 Handler
  • Handler 是做什么的?
  • 为什么使用Handler?
  • Handler 是如何使用的?
  • Handler 通过new 创建以后都做了什么?
  • Handler 是如何发送和处理消息的
  • 使用Handler 应该注意什么

Handler 是做什么的?

  • Handler 类的文档注释是这样形容 的:
    • Handler 允许程序用来发送和处理与 Runnable 和线程相关联的对象。每个Handler 都与单个 线程和改线程的消息队列相关联,当你创建一个新的 处理程序时,它将绑定到创建他的线程和线程消息队列,从那时候起,它将消息和可运行消息传递到该消息队列,并在消息出来时执行他们。
      一句话总结:Handler 是一个异步消息机制,用来执行 子线程发送的消息在主线程做处理。

为什么要使用 Handler?

  • 假设屏幕上有TextView 控件 ,我们需要开启线程请求数据并在数据回来以后跟新 TextView 文本(请求数据是耗时操作不可以在UI线程进行,否则会出现 ANR 程序无响应),我们在不使用Handler 情况下 直接操作 TextView 看看会出现什么?

     protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTips = findViewById(R.id.tv_tips);
        //开启线程模拟数据请求 并更新UI
       new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mTips.setText("Handler 消息机制");
            }
        }).start();
    
    }
    }
    

    点击 run 运行项目,直接崩溃,通过查看控制台 我们可以看到以下信息

     android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created 
     a view hierarchy can touch its views.
     at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6610)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:936)
        at android.view.View.requestLayout(View.java:18761)
     ......
    

    异常信息直接告诉了我们只能在创建这个 控件的 线程里面更新这个View,简介的说明我们可以发现 视图的更新只能发生在UI 线程,而不可以在子线程更新控件,原因是在子线程更新控件是不安全的。
    既然在子线程不能更新 UI ,那我们如何把消息 发送到 UI 线程呢?Handler 这时候就派上了用场了。正如第一节所说Handler是异步消息机制,用来发送和处理消息的。

Handleer 如何使用

  • 创建一个匿名的 Handler ,重写 handlerMessage()方法
    Java
    private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    super.handleMessage(msg);
    // 接受到消息并交给控件对消息做处理
    mTips.setText((String) msg.obj);
    Log.d("MainActivity", "handleMessage: " + (String) msg.obj);
    }
    };
  • 使用匿名创建的 Handler 对象来发送消息

     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTips = findViewById(R.id.tv_tips);
        //开启线程模拟数据请求 并更新UI
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message obtain = Message.obtain();
                obtain.obj = "Handler 消息机制";
                mHandler.sendMessage(obtain);
            }
        }).start();
    
    }

    通过以上两步就把一个消息从子线程 发送到了UI线程并做了处理。调用者如此简单,那么 Handler 类是如何做到呢?

  • Handler 是如何做到 在异步发送消息到 UI 线程的呢?

    • 在使用当中,我们首先 new Handle(),重写了 handleMessage();就这么简单我们就可以发送和接受消息了,既然这样 Handler 里面做了一些操作,才让调用者如此简单的使用,那么 Handler 如何做的呢?了解一个类 就要从类的构造方法开始以及 都有哪些方法?
      /**
       * Default constructor associates this handler with the {@link Looper} for the
       * current thread.
       *
       * If this thread does not have a looper, this handler won't be able to receive messages
       * so an exception is thrown.
       */
      public Handler() {
          this(null, false);
      }

    从上面可以看出 无参构造调用了 有两个参数的构造方法,第一个参数为 null,第二个参数为false,由此我们可以指直接看 带两个参数的构造方法

        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());
            }
        }
        // 获取Looper
        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.myLooper 返回了一个Looper 对象。问题接着就来了,Looper 是啥呢?做什么的呢?只有进到 Looper类 才能知道 looper是做什么的?`

      1. 首先 Looper 类的文档 是如何对 Looper 描述的呢?
        Looper 是用于为线程运行消息循环的一个类。线程中默认是没有消息循环的,所以为了循环线程的消息需要创建一个 Looper,请在要运行该循环的线程中调用 Looper.prepare(),然后在调用 Looper.loop 让它来处理消息,直到循环停止。 并且如何使用 文档注释也给了 用例
        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()创建 Looper ,并调用 Looper.loop来处理的。接下来我们看看 Looper.prepare() 是如何创建 Looper的呢?

      
           /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
      public static void prepare() {
      prepare(true);
      }

      从中可以看出 prepare() 方法 是一个重载方法() ,它有调用了 带有一个参数的 prepare() 方法。参数是一个Boolean 类型 并且传值为 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));
      }

      在prepare() 方法里面做了一件事就是 :

      1. 通过 变量 sthreadLocal.get() 来判断 Looper 是否存在,在抛出的异常里面可以看到 每个线程只能创建一个Looper,也就是每次在获取的时候会对当前线程的Looper 做一个判断,如果当前已经有了那么就对调用者抛出异常。通过定位sThreadLocal 变量 发现 是在Looper 里面定义了一个 static final ThreadLocal sThreadLocal = new ThreadLocal(); 对象,至于 ThreadLoal 是做什么在此就不解释了。我们只要跟踪 是如何获取到 Looper 对象的就可以了。通过 sThreadLocal.get() 方法返回了 Looper,如果返回的是null 那么就通过 sThreadLocal.set(new Looper(quitAllowed)) 来创建了一个 Looper,定位到 set()方法里面可以通过源码看到 就是获取当前的线程 的对象作为键 存储在了 Map 集合里面。

总结:Looper 其实 就是对当前线程的消息进行循环的,每一个线程只能对应一个Looper。通过 ThreadLocal 来获取和实例化 Looper的。

再次回到 带有两个参数的 Handler 方法里面 一步步走读 Handler 构造方法里面做了什么事?
1. 调用 Looper.myLooper() 通过 sThreadLocal.get() 来获取Looper,如果没有就返回null.
2. 判断在第一步里面获取的 Looper对象是否为null.如果获取的Loopr 是null 的话,就抛出 不能再没有 Looper.prepare() 里面创建Handler。
3. 调用 mLooper.mQueue() 返回了一个 mQueue 变量,定位 mQueue 是在Handler 里面定义的一个 final MessageQueue 对象。那么 MessageQueue 对象是什么呢? 为什么通过 Looper对象来获取的呢?
1. 定位到 MessageQueue 类里面看 类的文档注释:MessageQueue 是一个低级别的类拥有分派消息的集合。消息不会直接添加到MessageQueue,而是通过与Looper关联的对象。也就是说 MessageQueue 和Looper 是有关联的。
2. 通过 文档知道了 MessageQueue 是一个消息列表。而Handler 使用来发送消息的,由此可以知道Handler 发送的消息都发送到了 MessageQueue 对象里面。
既然知道了 MessageQueue 是和 Looper 有关联的 ,那么通过 mLooper.mQueue来获取也就一点都不奇怪了。但是Looper 又是如何 实例化 MessageQueue 对象的呢。通过定位到 Looper 里面可以看出在 Looper 实例化的时候同时也实例化了 MessageQueue 对象。有此可以看出 Looper 和 MessageQueue 是相伴相生的。既然 MessageQueue 是存储消息的那就只能在Handler 发送消息的时候看如何把消息添加到消息队列的。
通过 跟踪定位发现 Handler 做了一下事件:
1. 使用Handler 必须获取当前线程的Looper。通过 Looper.prepare().
2. 获取当前线程的 Looper,一个线程只能有一个Looper。
3. Looper 和 MessageQueue 是相伴相生的,一个 Looper 对应一个MessageQueue.
既然使用Handler 必须通过 Looper.prepare()方法获取,可是在示例中却没有调用 Looper.prepare()方法呢?并且也没有报出不能再没有 Looper.prepare() 里面创建Handler,既然没有抛出那就说明 我们可以获取到当前UI线程的Looper。而当前线程的Looper 肯定是系统帮我们调用了实例化方法。

Handler 是如何发送和处理消息的

  • 发送消息
    • 在示例中 通过 mHandler.sendMessage()我们发送了一个消息,我们定位到这个方法 看看消息是如何发送到 MessageQueue 里面的呢?
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

通过最终定位 都调用了 enqueueMessage() 方法:
1. msg.target = this; 给发送的消息做了一个标记,而这个标记引用的是 当前Handler对象。
2. 由于在 Handler 里面 mAsynchronous 赋值为false,这句代码就暂时不管是做什么的了。
3. 返回了
未完待续。。。。

插入链接 Ctrl + L
- 插入代码 Ctrl + K
- 插入图片 Ctrl + G
- 提升标题 Ctrl + H
- 有序列表 Ctrl + O
- 无序列表 Ctrl + U
- 横线 Ctrl + R
- 撤销 Ctrl + Z
- 重做 Ctrl + Y

Markdown及扩展

Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]

使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。

本编辑器支持 Markdown Extra ,  扩展了很多好用的功能。具体请参考Github.

表格

Markdown Extra 表格语法:

项目价格
Computer$1600
Phone$12
Pipe$1

可以使用冒号来定义对齐方式:

项目价格数量
Computer1600 元5
Phone12 元12
Pipe1 元234

定义列表

Markdown Extra 定义列表语法: 项目1 项目2
定义 A
定义 B
项目3
定义 C

定义 D

定义D内容

代码块

代码块语法遵循标准markdown代码,例如:

@requires_authorization
def somefunc(param1='', param2=0):
    '''A docstring'''
    if param1 > param2: # interesting
        print 'Greater'
    return (param2 - param1 + 1) or None
class SomeClass:
    pass
>>> message = '''interpreter
... prompt'''

脚注

生成一个脚注1.

目录

[TOC]来生成目录:

数学公式

使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.

  • 行内公式,数学公式为: Γ(n)=(n1)!nN Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N
  • 块级公式:

x=b±b24ac2a x = − b ± b 2 − 4 a c 2 a

更多LaTex语法请参考 这儿.

UML 图:

可以渲染序列图:

Created with Raphaël 2.1.2 张三 张三 李四 李四 嘿,小四儿, 写博客了没? 李四愣了一下,说: 忙得吐血,哪有时间写。

或者流程图:

Created with Raphaël 2.1.2 开始 我的操作 确认? 结束 yes no
  • 关于 序列图 语法,参考 这儿,
  • 关于 流程图 语法,参考 这儿.

离线写博客

即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。

用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。

博客发表后,本地缓存将被删除。 

用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。

注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱

浏览器兼容

  1. 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
  2. IE9以下不支持
  3. IE9,10,11存在以下问题
    1. 不支持离线功能
    2. IE9不支持文件导入导出
    3. IE10不支持拖拽文件导入


  1. 这里是 脚注内容.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值