Android基础:异步消息处理机制Handler


Android是大量的消息驱动方式来进行交互,Android某种意义上也可以说成是一个以消息驱动的系统。消息机制涉及MessageQueue/Message/Looper/Handler这4个类。
第三方框架的引入让Handler越来越没"用武之地"。然后掌握handler原理以及使用还是很有必要的。。阅读源码、面试装逼必不可少!也是程序员进阶的必经之路。

一、概述

1、定义

Android提供的一套消息机制

2、为什么子线程不允许访问 UI

假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。
传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。

子线程直接更新UI抛出的异常:

子线程更新UI抛出的异常.png
子线程更新UI抛出的异常.png

找到对应源码

ViewRootImol.java
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

在更新UI的时候都会调用ViewRootImpl的checkThread()方法。
checkThread 方法判断更新UI的线程是否是ViewRootImpl的mThread 线程,不是的话就会抛出异常。

二、相关概念

Message :分为硬件产生的消息和软件生成的消息
(拥有handler对象)
作用:可以携带少量信息,数据的载体

Message Queue :消息队列
(队列中拥有N个Message )
作用:用来存放通过Handler发过来的Message,按照先进先出执行

Looper :不断循环执行(Looper.loop),负责循环执行消息
(一个线程拥有一个Looper ,一个Looper拥有Message Queue )
作用:
1、Looper 所在的线程中循环取出Message Queue的Message,解决多线程并发问题
2、将取出的Message交付给相应的Handler去处理

Handler :Message的发送者、处理者
(拥有一个线程的线程Looper 和Message Queue 对象)
作用:
1、提供sendMessage方法,将任意线程的消息放置到队列中
2、提供dispatchMessage方法,在Looper 所在的线程处理消息

二、基本流程

1、启动App

App启动后会找ActivityThread main()方法(入口原来在这呢!)

ActivityThread.java
 public static void main(String[] args) {
     ...
     //初始化UI线程的Looper对象
     Looper.prepareMainLooper();
     ...
     //循环处理消息(死循环)
     Looper.loop();
 }

线程调用 Looper.loop()要放在方法的最后面

当在UI线程 调用Looper.loop()后、UI线程将处于一个死循环中!所有硬件产生的消息(如按钮、触摸)和软件生成的消息都会被加到死循环中依次执行。
UI线程本身就是个死循环,随时等待着消息的到来。这也是为什么程序启动后任何时间发送的消息UI线程都能够处理的原因。

2、Looper 相关方法:

一个线程只有一个Looper 对象,一个Looper 对象中包含一个Message Queue对象

1、Looper.prepare() 初始化Looper

    private static void prepare(boolean quitAllowed) {
        //sThreadLocal.get()会根据不同线程返回对应的Looper,一线程一个Looper
        if (sThreadLocal.get() != null) {
        //prepare方法调用二次直接抛异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。感兴趣的可以看一下它的get和set源码。

2、Looper.loop() 循环处理Message Queue里的Message

public static void loop() {
            ...
            //进入死循环
            for (;;) {
              //next()方法用于取出消息队列里的消息 (可能会阻塞线程)
                Message msg = queue.next(); 
                if (msg == null) {
                    //消息为空退出循环
                    return;
                }
            ...
              //调用对应的handler处理消息
                msg.target.dispatchMessage(msg); 
              //释放消息占据的资源
                msg.recycle();
            }
    }

loop()进入循环模式,不断重复下面的操作,直到没有消息时退出循环

  • 读取MessageQueue的下一条Message;
  • 把Message分发给相应的target(Handler);
  • 再把分发后的Message回收到消息池 msg.recycle(),以便重复利用。
3、Handler基础用法
3.1 创建Handler对象

Handler的构造方法

    //--------------------------------------------无惨构造方法--------------------------------------------
    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        ...
        //从当前线程的TLS中获取Looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //通过Looper获取消息队列
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    //--------------------------------------------有参数的构造方法--------------------------------------------
    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

通过构造方法可以发现、handler的创建时需要绑定一个Looper对象的(handler需要绑定在一个线程上,消息处理回调也就在绑定的线程中执行)。一般有2中方式指定绑定的线程:

1. 不指定Looper对象,那么这个Handler绑定到了创建这个线程的线程上,消息处理回调也就在创建线程中执行.
2. 通过Loop.myLooper()得到当前线程的Looper对象/通过Loop.getMainLooper()可以获得当前进程的主线程的Looper对象。

3.2 通过Handler发送消息

在任意线程中我们可以通过Handler发送消息到Message queue中,发送消息分为sendMessage()post(Runnable r)2大类:

        ---------------------sendMessage()---------------------
        //发送一个Message
        sendMessage(Message msg)
        //发送一个延迟处理的Message
        sendMessageDelayed(Message msg, long delayMillis)
        //发送一个指定时间处理的Message
        sendMessageAtTime(Message msg, long uptimeMillis) 
        //发送一个优先处理的Message
        sendMessageAtFrontOfQueue(Message msg) 
        sendEmptyMessage(int what)
        sendEmptyMessageDelayed(int what, long delayMillis) 

        ---------------------post(Runnable r)---------------------
        //其实也是发送一个Message
         public final boolean  post(Runnable r){
           return  sendMessageDelayed(getPostMessage(r), 0);
         }
        postDelayed(Runnable r, long delayMillis)
        postAtFrontOfQueue(Runnable r)
        }

源码太多就不贴了,通过源码可以发现

  • post(Runnable r)其实也是发送一个Message
  • 只有post方式发送的Message才有Runnable,msg.callback 对象才不为空(3.3会用到)
  • 无论是post还是send方式最终都是通过Handler.enqueueMessage()方法把Message添加到MessageQueue中
  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            //Message.target = 发送它的handler
            msg.target = this;
            //是否异步处理
            if (mAsynchronous) {
                msg.setAsynchronous(true);
               }
            //把Message添加到MessageQueue
            return queue.enqueueMessage(msg, uptimeMillis);
        }
3.3 Handler处理消息

Looper.loop()取出Message会调用msg.target.dispatchMessage(msg)方法msg.target就是Handler

    public void dispatchMessage(Message msg) {
        //msg.callback是一个Runable对象
        //post方式才Message有的Runable
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //handler构造方法传递的Callback(可选)
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                //屏蔽handleMessage方法
                    return;
                }
            }
            //一般我们重写的handleMessage
            handleMessage(msg);
        }
    }
    public void handleMessage(Message msg) {
    //由开发者重写方法
    }

由上面的代码可以总结出:

  • post的发送的消息、处理消息时候只会调用post传递的Runable方法
  • send方式发送的消息可以通过构造方法的Callback方法和重写Handler的 handleMessage处理消息
  • 可以通过构造方法中Callback.handleMessage(msg) 的返回值拦截handleMessage方法
3.4 创建Message
       //1、直接创建
        Message message1 = new Message();
       //2、从消息池复用废弃的Message,这里得到Message已经清空所有的数据
        Message message2 = Message.obtain();
3.5、子线程启动一个消息轮询机制

3.5.1 利用HandlerThread(推荐)

    //1: 创建handlerThread对象
    HandlerThread handlerThread = new HandlerThread("线程名称");
    handlerThread.start();
    
    //2: 创建Handler
    Handler handler = new Handler(handlerThread.getLooper());
    
    //3: 发送消息
    handler.sendEmptyMessage(1);
    ```
**3.5.2 在普通Thread启动**
```java
    class MyThread extends Thread {
        public Handler mHandler;
        public void run() {
            Looper.prepare();
            // 1: 创建Handler
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    //TODO  处理即将发送过来的消息
                }
            };
            //放在最后
            Looper.loop();
        }
        }
        //2: 启动LooperThread线程
        MyThread thread = new MyThread();
        thread.start();
        //3: 发送消息
        MyThread.mHandler.sendEmptyMessage(1);
    };

注意:这种方式不要把Handler实例化放在run()方法外面、否则可能Looper.prepare()还没执行就new Handler会导致程序崩溃

三、面试常见问题

问题1:子线更新UI的方式有哪几种?

  1. 3.3中的Handler方式更新
  2. runOnUiThread() ------内部也是通过handler发送的
  3. view.post(Runnable)------内部也是通过handler发送的
  4. 广播------Binder
  5. EventBus------handler

问题2:子线程一定不能更新UI嘛?

  1. 子线程在activity的获取焦点前(onResume之前)方法前可以更新ui(ViewRootImol是在onResume方法中创建的)
  2. surfaceView:可以在子线程中绘制和更新进度
  3. Progress(进度):相关的控件可以在子线程中更新进度

其实surfaceView Progress 内部也是通过handler去更新进度的

问题3:为什么非UI线程不能跟新UI?
假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。
传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。
问题4:Looper.loop()是死循环没有导致程序卡死?(见2.1启动App)
异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
一句话:程序运行的本身就是在这个死循环中、所以不存在卡死之说。

四、总结

消息发送执行流程图


handler_java.jpg
handler_java.jpg

图片来自于:http://gityuan.com/2015/12/26/handler-message-framework/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值