美文网首页
Android Handler源码

Android Handler源码

作者: Jackson杰 | 来源:发表于2020-10-19 15:08 被阅读0次

    Hanlder的使用

    handler有两种使用方式,下面开始介绍

    方式一:使用Handler.sendMessage()

    在该方式中,又可以分为两种:新建Handler子类(内部类)、匿名 Handler子类。

    /**
    * 方式一:新建Handler子类(内部类)
    */
    
    // 步骤1:自定义Handler子类(继承Handler类),复写handleMessage()方法
        class mHandler extends Handler {
            // 通过复写handlerMessage() 从而确定更新UI的操作
            @Override
            public void handleMessage(Message msg) {
             ...// 需执行的UI操作 
            }
        }
    
    // 步骤2:在主线程中创建Handler实例
          private Handler mhandler = new mHandler();
    
    // 步骤3:创建所需的消息对象
          Message msg = Message.obtain(); // 实例化消息对象
          msg.what = 1; // 消息标识
          msg.obj = "AA"; // 消息内容存放
    
     // 步骤4:在工作线程中 通过Handler发送消息到消息队列中
     // 可通过sendMessage() / post()
     // 多线程可采用AsyncTask、继承Thread类、实现Runnable
          mHandler.sendMessage(msg);
                mHandler.sendEmptyMessage(0); // 发送空消息
            mHandler.sendMessageDelayed(message,1000); //延迟1000ms后发送携带数据的消息
    
    /** 
      * 方式2:匿名内部类
      */
    
    // 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
            private Handler mhandler = new  Handler(){
                  // 通过复写handlerMessage()从而确定更新UI的操作
                 @Override
                  public void handleMessage(Message msg) {
                            ...// 需执行的UI操作
                     }
              };
    
    // 步骤2:创建消息对象
        Message msg = Message.obtain(); // 实例化消息对象
        msg.what = 1; // 消息标识
        msg.obj = "AA"; // 消息内容存放
    
    // 步骤3:在工作线程中 通过Handler发送消息到消息队列中
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable
       mHandler.sendMessage(msg);
         mHandler.sendEmptyMessage(0); // 发送空消息
       mHandler.sendMessageDelayed(message,1000); //延迟1000ms后发送携带数据的消息
    

    方式二:使用Handler.post()

    // 步骤1:在主线程中创建Handler实例
        private Handler mhandler = new mHandler();
    
    // 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容
        // 需传入1个Runnable对象
        mHandler.post(new Runnable() {
                @Override
                public void run() {
                    ... // 需执行的UI操作 
                }
    
        });
    

    Hanlder源码分析

    Handler原理解析

    主要内容

    Handler的消息机制主要包含以下内容:

    • Message:消息
    • MessageQueue:消息队列
    • Handler:消息管理类
    • Looper:消息循环类

    Handler架构图

    Handler的架构图如下:


    创建Handler对象

    具体使用

     private Handler mhandler = new  Handler(){
            // 通过复写handlerMessage()从而确定更新UI的操作
            @Override
            public void handleMessage(Message msg) {
                    ...// 需执行的UI操作
                }
        };
    

    构造方法
    Handler构造方法,调用了另一个构造方法,this(null, false)。

    public Handler() {
        this(null, false);
    }
    

    几点说明:

    • Handler需要绑定线程才能使用;绑定后,Handler的消息处理会在绑定的线程中执行。
    • 绑定方式:指定Looper对象,从而绑定了Looper对象所在的线程,因为Looper对象本已绑定了对应线程。
    • 指定了Handler对象的Looper对象 = 绑定到了Looper对象所在的线程。

    ···
    public Handler(Callback callback, boolean async) {

    .....//省略代码
    //Looper.myLooper()作用:获取当前线程的Looper对象;若线程无Looper对象则抛出异常
    //即:若线程中无创建Looper对象,则也无法创建Handler对象
    //故若需在子线程中创建Handler对象,则需先创建Looper对象
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    // 绑定消息队列对象(MessageQueue)
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    
    //至此,保证了handler对象的MessageQueue关联上Looper对象中MessageQueue
    

    }
    ···

    从上面可以看出:

    • 当创建Handler对象时,通过构造方法自动关联当前线程的Looper对象和对应的消息队列对象(MessageQueue),从而自动绑定了实现创建Handler对象操作的线程。

    那么问题来了,到目前为止,我们不曾创建过Handler需要关联的Looper对象和MessageQueue对象,那当前线程的Looper对象和对应的消息队列的MessageQueue是什么时候创建的呢?

    创建循环对象Looper和消息队列对象MessageQueue

    • 创建Looper对象的方法:Looper.prepareMainLooper()和Looper.prepare()。
    • Looper.prepareMainLooper():为主线程(UI线程)创建一个Looper对象。
    • Looper.prepare()为当前线程创建一个Looper对象

    源码分析1:Looper.prepareMainLooper()

    在Android应用进程启动时,会默认创建1个主线程ActivityThread,也叫UI线程,创建时,会自动调用ActivityThread的静态的main()方法 = 应用程序的入口,main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时生成一个消息队列对象MessageQueue。源码如下:

    public static void main(String[] args) {
       ../// 省略代码
       // 1
        Looper.prepareMainLooper();
          ..///省略代码
          // 创建主线程
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        ..///省略代码
        // 开启消息循环,后面分析
        Looper.loop();
    }
    

    // 1处为主线程创建1个Looper对象,同时生成1个消息队列对象MessageQueue,继续看prepareMainLooper(),

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // 设置sMainLooper,sMainLooper是主线程的Looper,可以通过getMainLooper()获取
            sMainLooper = myLooper();
        }
    }
    

    可以看到,Looper.prepareMainLooper()内部其实是调用了 prepare()方法的。

    源码分析2:prepare()方法

    prepare()的作用是为当前线程创建1个循环器对象(Looper),同时也生成了1个消息队列对象(MessageQueue)。
    注意,如果要在子线程中创建Looper对象,则需在子线程中手动调用该方法。

    private static void prepare(boolean quitAllowed) { 
    
            // 判断sThreadLocal是否为null,否则抛出异常
            // Looper.prepare()方法不能被调用两次 = 1个线程中只能对应1个Looper实例
            // sThreadLocal = 1个ThreadLocal对象,用于存储线程的变量
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        
        // 若为初次Looper.prepare(),则创建Looper对象 & 存放在ThreadLocal变量中
        // Looper对象是存放在Thread线程里的
        sThreadLocal.set(new Looper(quitAllowed));
    }
    

    这里需要说明一下,是用ThreadLocal保存Looper对象。- Handler通过绑定Looper对象的方式绑定到当前线程。

    • 一个线程对应着一个Looper。
    • 一个Looper对应着一个MessageQueue。
    • 线程默认是没有Looper的,线程需要通过Looper.prepare()、绑定Handler到Looper对象、Looper.loop()来建立消息循环。
    • 主线程(UI线程),也就是ActivityThread,在被创建的时候就会初始化Looper,所以主线程中可以默认使用Handler。

    综上所述:Threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。ThreadLocal的作用是保证一个线程对应一个Looper,同时各个线程之间的Looper互不干扰。那么ThreadLocal是怎么保证的呢?

    实际上ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。这里再多提一下,ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的是,

    • Synchronized是通过线程等待,牺牲时间来解决访问冲突
    • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突

    源码分析3:Looper的构造方法

    以上在prepare()方法中,创建Looper对象并存放在sThreadLocal中,下面查看Looper的构造方法。

    private Looper(boolean quitAllowed) {
            
            // 创建1个消息队列对象(MessageQueue)
            // 即 当创建1个Looper实例时,会自动创建一个与之配对的消息队列对象(MessageQueue)
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    

    可以看到,在创建Looper对象的时候,创建了MessageQueue对象。
    即一个Looper对应着一个MessageQueue。
    ==根据以上总结==

    • 创建主线程时,会自动调用ActivityThread的1个静态的main();而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象。

      1. 即 主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象则需手动通过Looper.prepare()创建。
      2. 在子线程若不手动创建Looper对象 则无法生成Handler对象。
    • 根据Handler的作用(在主线程更新UI),故Handler实例的创建场景 主要在主线程

    • 生成Looper & MessageQueue对象后,则会自动进入消息循环:Looper.loop()。

    消息循环Looper.loop()

    Looper.loop()主要是消息循环,从消息队列中获取消息,分发消息到Handler中。

    public static void loop() {
    
            // ---  1.获取当前Looper的消息队列MessageQueue -----
            
            // 第一步
            // 获取当前Looper对象
        final Looper me = myLooper();
        // myLooper()的作用是返回sThreadLocal存储的Looper实例
        // 若me为null,则抛出异常
        // 所以在执行loop()方法之前,必须执行prepare()方法,prepare()            //的作用是创建Looper实例
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        
        // 第二步
        // 获取Looper实例中的消息队列对象MessageQueue
        final MessageQueue queue = me.mQueue;
    
       // ......代码省略
       
            //------ 2. 消息循环,无限循环 --------------
            
            // 第三步
        for (;;) {
                // 从MessageQueue中取出消息
                // 第四步
            Message msg = queue.next(); // might block
            // 如果消息为空,则退出循环
            if (msg == null) {
            // No message indicates that the message queue is quitting.
                return;
            }
      // ......代码省略
            final long dispatchEnd;
            try {
            
                    // 第五步
                    // 分发消息到对应的Handler
                    // 把消息派发到msg的target属性
                    //target属性实际上是一个handler对象
                msg.target.dispatchMessage(msg);
                
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            // ......代码省略
    
          // 第六步
                // 回收消息
            msg.recycleUnchecked();
        }
    }
    

    消息循环Looper.loop(),主要作用的取出消息,通过以上代码分析,大致分为六步:

    • 第一步,获取Looper对象
    • 第二步,获取Looper实例中的消息队列对象MessageQueue
    • 第三步,while()无限循环遍历
    • 第四步,通过queue.next()从MessageQueue中取出一个Message对象
    • 第五步,通过msg.target.dispatchMessage(msg)来处理消息
    • 第六步,通过 msg.recycleUnchecked()来回收消息

    其中第四,五,六三步涉及到消息的取出和消息的处理,在后面介绍。

    消息的发送、取出和处理

    对于消息的发送,取出和处理,参照下面的文章。

    消息发送

    消息的取出和处理

    总结

    最后再总结一下,Handler工作的流程图:


    相关文章

      网友评论

          本文标题:Android Handler源码

          本文链接:https://www.haomeiwen.com/subject/kuizpktx.html