Android Handler机制原理浅析

源码分析

1 Handler从创建对象到发送消息sendMessage()的过程做了什么

步骤:

  //1 创建handler对象, 这里使用小心内存泄露.  非静态内部类持有外部类不能及时被回收
  Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
          //  接收消息的回调函数,用来更新UI操作
            switch (msg.what){
                case 1:
                    LogUtils.logd((String) msg.obj);
                    break;
            }
            return false;
        }
    });

  //2  创建一个线程来发送消息
  new Thread(new Runnable() {
            @Override
            public void run() {
                //3 创建消息对象   参数1为消息标识  2为任意消息内容
                Message message = handler.obtainMessage(1, "消息内容");        
                //4  发送消息 
                handler.sendMessage(message);
            }
        }).start();

步骤1 创建handler对象,在源码中都做了什么

//  源码中的构造函数
 public Handler(Callback callback) {
        this(callback, false);
    }

   public Handler(Callback callback, boolean async) {
        //  FIND_POTENTIAL_LEAKS为true以检测匿名类、本地类或成员类扩展了这个处理程序类,它不是静态的,这类可能会造成泄漏。
      //  这里会给出警告  所以为了避免内存泄露,不要用匿名类去实现.
        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());
            }
        }
        //  关键代码1  这里绑定Looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 关键代码2  绑定消息队列MessageQueue
        mQueue = mLooper.mQueue;
        // Handler 的回调函数CallBack
        mCallback = callback;
        // 设置消息是否异步 异步为true
        mAsynchronous = async;
    }

从上面源代码中可以看出,当创建handler对象时,构造函数就自动绑定上Looper对象MessageQueue(消息队列)对象.但是在handler类中没有看到Looper和MessageQueue在哪里创建的.

下面就看看Looper对象MessageQueue对象是什么时候创建的.
Looper类发现:

//  构造方法
private Looper(boolean quitAllowed) {
        // 创建消息队列对象
        mQueue = new MessageQueue(quitAllowed);
        // 记录当前线程
        mThread = Thread.currentThread();
    }

 /**  翻译  注解:
     * 当前线程初始化为一个looper。
     * 这给了你一个机会来创建Handler关联一个Looper之前调用这个prepare方法。
     * 然后调用loop()方法,并且通过调用quit()结束。
     */
      // 当前线程(子线程)创建Looper对象
   public static void prepare() {
    // 设置允许退出
        prepare(true);
    }

    // 私有方法 来创建Looper对象  参数 true可以退出Looper false不可以退出,(子线程true,主线程false)因为主线程不能退出Looper循环器
    private static void prepare(boolean quitAllowed) {
    //每个线程只允许执行一次该方法,第二次执行的线程的TLS已有数据,则会抛出异常。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
       //创建Looper对象,并且保存到当前线程的TLS区域。
        sThreadLocal.set(new Looper(quitAllowed));
    }

  /**  
      翻译 注解:
     * 将当前线程初始化为一个looper,将其标记为一个
     *应用程序的主Looper。您的应用程序的主Looper
     *是由Android环境创建的,所以您永远不需要
     *自己调用这个函数, 另请参阅prepare()
     */
// 当前线程(主线程) 创建Looper对象
  public static void prepareMainLooper() {
        // 允许退出
        prepare(false);
        synchronized (Looper.class) {
           //将当前的Looper保存为Looper。每个线程只允许执行一次
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

Looper类 中可以看到创建 Looper对象MessageQueue对象是通过prepare()prepareMainLooper()来创建的; 那就好办了,我们可以通过快捷键AIT+鼠标点击prepareMainLooper(),就可看到在哪里调用了,然后可以看到在ActivityThread类中被调用.
这里说一下因为程序运行的是在主线程中的,所以就直接查看prepareMainLooper()方法,而prepare()方法是子线中被调用的,需要自己手动调用.

ActivityThread类 程序入口的main()方法

    // 在Android应用进程启动时,会默认创建1个主线程(ActivityThread,也叫UI线程)
    // 创建时,会自动调用ActivityThread的1个静态的main()方法 = 应用程序的入口
    // main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象

    public static void main(String[] args) {
        // 关键代码  创建Looper对象和MessageQueue对象
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
       // 关键代码  执行消息循环
        Looper.loop();    
    }

总结ActivityThread类:

  • 在创建主线程时,会自动执行ActiityThread类中的静态方法main(),而main()方法内部会调用Looper.prepareMainLooper()方法,为主线程创建一个Looper对象和一个MessageQueue对象, 然后调用Looper.loop()消息循环方法取出消息,怎么循环取出消息下面会讲解.
  1. 主线程Looper对象是自动被创建的,永远不需要我们手动创建.
  2. 在子线程中Looper对象是需要自己手动调用Looper.prepare()方法创建的,若不手动创建,则Handler无法创建对象, 会报错 Can't create handler inside thread that has not called Looper.prepare() : 无法在未调用Looper.prepare()的线程中创建handler
    用法:
new Thread(new Runnable() {
         @Override
           public void run() {
           Looper.prepare();
           Handler handler = new Handler();
           Looper.loop();
           }
      }).start();

消息循环 Looper.loop()

  /**
  * 作用:消息循环,即从消息队列中获取消息、分发消息到Handler
  * 特别注意:
  *       a. 主线程的消息循环不允许退出,即无限循环, 当主线程退出了也就自动退出了,也是[ActivityThread类中控制的].会调用quit方法.
  *       b. 子线程的消息循环允许退出:调用消息队列MessageQueue的quit()
     */
    public static void loop() {
      //  关键代码

        // 获取Looper对象
        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;

     
      // 消息循环,一个死循环,不停的处理消息队列中的消息,消息的获取是通过MessageQueue的next()方法实现
        for (;;) {
            // 从消息队列中取出消息 ,  可能会阻塞
            Message msg = queue.next(); 
            //  没有消息退出循环
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                )
                // 派发消息给对应的Handler,target就是当前你创建的对象.
                msg.target.dispatchMessage()

            // 3. 回收消息占据的资源, 并将消息对象加入Message池中,
            msg.recycleUnchecked();
        }
    }

/** 
  * 分析:queue.next()
  * 定义:属于消息队列类(MessageQueue)中的方法
  * 作用:出队消息,即从 消息队列中 移出该消息
  */
 Message next() {
  // 关键代码

 // 如果消息循环已经退出了。则直接在这里return。因为调用dispose()方法后mPtr=0
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

// native层用到的变量 ,如果消息尚未到达处理时间,则表示为距离该消息处理事件的总时长,
//nextPollTimeoutMillis>0表示还有消息待处理 否则处于等待状态
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
 // 调用native层进行消息标示,nextPollTimeoutMillis 为0立即返回,为-1则阻塞等待。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
               //  获取开机到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
             
                if (msg != null) {
                     // 判断该Mesage是否到了被执行的时间。
                    if (now < msg.when) {
                        // 当Message还没有到被执行时间的时候,记录下一次要执行的Message的时间点
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 得到了一个消息。
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1
                // 下次循环时,消息队列则处于等待状态
                    nextPollTimeoutMillis = -1;
                }

                // 关闭消息队列,返回null,通知Looper停止循环
                if (mQuitting) {
                    dispose();
                    return null;
                }
        }
    }

/** 
  * 分析2:dispatchMessage(msg)
  * 定义:属于处理者类(Handler)中的方法
  * 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作
  */
  public void dispatchMessage(Message msg) {

    //  若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息
    // 则执行handleCallback(msg),即回调Runnable对象里复写的run()
   
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息
            //  可以执行handleMessage接口回调方法传递消息
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);

        }
    }

总结 : 先获取 Looper对象, 然后获取 消息队列对象MesageQueue,再通过死循环,从消息队列中获取消息queue.next(),拿到消息就分发给对应handler的方法*dispatchMessage(Message msg);

图解:

图解.jpg

回到上面的步骤:
3创建Message消息对象和4发送消息handler.sendMessage(message);

  • 分析创建Message对象源码分析:
//  获取消息对象
Message message = Message.obtain();
                message.what=1;
                message.obj ="消息内容";


 /**
     * 从全局的pool(池)返回一个实例化的Message对象。这样可以避免我们重新创建冗余的对象。
     */
    public static Message obtain() {
      // 保证线程安全
        synchronized (sPoolSync) {
            // 若池内有消息,就返回消息对象
            if (sPool != null) {
              //  将消息对象池中的对象取出来
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
// 若池内无消息对象可复用,则还是用关键字new创建
        return new Message();
    }
  • 分析: 发送消息handler.sendMessage();
public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }

public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

/**
// 从上面顺序调用下来,关键方法,
* uptimeMillis : 以毫秒为基本单位, 延迟执行的时间
*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //  获取消息队列,  这是在Handler实例化对象的时候,就获取了消息队列对象.
        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);
    }

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 设置msg的target变量,并将target指向自己(当前Handler对象)
// 在Looper.loop()中取出的消息就是通过这个target指向对应的Handler分发消息(dispatchMessage(msg)来处理)
        msg.target = this;
//  如果当前是异步就设置为异步
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 加入到消息队列中
        return queue.enqueueMessage(msg, uptimeMillis);
    }



/** 
          * 分析:queue.enqueueMessage(msg, uptimeMillis)
          * 定义:属于消息队列类(MessageQueue)的方法
          * 作用:入队,即 将消息 根据时间 放入到消息队列中(Message ->> MessageQueue)
          * 采用单链表实现:提高插入消息、删除消息的效率
          */

boolean enqueueMessage(Message msg, long when) {
       //     关键代码 
        synchronized (this) {
           // 判断消息队列是被关闭,如果被关闭,则return false告诉消息入队是失败,并且回收消息
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
/*
 如果p==null则说明消息队列中的链表的头部元素为null;when == 0 表示立即执行;
when< p.when 表示 msg的执行时间早与链表中的头部元素的时间;
所以上面三个条件,那个条件成立,都要把msg设置成消息队列中链表的头部是元素
*/
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                 // 把msg的下一个元素设置为p
                msg.next = p;
                //   把msg设置为链表的头部元素
                mMessages = msg;
                 // 如果有阻塞,则需要唤醒
                needWake = mBlocked;
            } else {
             // 如果上面三个条件都不满足则说明要把msg插入到中间的位置,不需要插入到头部

                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
              // 不断遍历消息队列,根据when的比较找到合适的插入Message的位置。
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

总结 : 将发送消息加入到消息队列中,在加入前为消息对象Message的target属性设置指向当前的handler对象,在Looper.loop()取出消息队列中的消息,就可以找到对应的handler分发消息了.

最后总结图解

图解.jpg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容