Handler机制运行原理

开始

在Android中不允许多个线程同时操作一个UI控件,因为多线程同时操作可能会造成UI有不可预知的结果,所以通常我们都会用Handler机制来进行UI更新。

 

Handler中的几个重要角色:

  • Handler

       用于向MessageQueue中发送消息,和处理Looper获取到的消息,在线程中创建Handler时当前线程中必须要有Looper

  • Looper

       用于从MessageQueue中循环获取Message,其内部是一个无限循环

  • MessageQueue

       用于存储Message,在创建Looper时会同时创建MessageQueue,它的内部是一个由Message组成的单链表。

  • Message
  1. Handler运行过程中传递的都是Message,Message中有很多属性 如:
  2. target 保存的发送该message的Handler,处理消息时会根据这个属性进行分派处理它的Handler
  3. next 保存的是下一个Messge的引用,通过它形成链表结构
  4. bundle 捆绑数据
  • ThreadLocal

         在Handler中ThreadLocal的作用就是,保存每个线程的Looper

 

Handler运行原理:

 

//Handler的构造方法
public Handler(Callback callback, boolean async) {
      ......
    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;
}
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

通过上面的源码可以看到,在创建Handler时,Handler内部会通过ThreadLocal获取当前线程的Looper,如果Looper为空就会抛出异常 "Can't create handler inside thread that has not called Looper.prepare()");

所以在创建Handler时必须要有Looper,细心的同学会发现,我们平时在主线程可以直接创建Handler并使用,那是因为UI线程在初始化到时候已经创建好了Looper,看下面的这段UI线程入口ActivityThread中的main方法源代码

 

public static void main(String[] args) {

        ......
    //实例化Looper并设置到ThreadLocal里面,赋值MainLooper
    Looper.prepareMainLooper(); 
     //实例化一个ActivityThread
    ActivityThread thread = new ActivityThread(); 
    //主要是获取AMS接口 然后设置ApplicationThread 的接口 给AMS和应用程序通信
    thread.attach(false);      
    if (sMainThreadHandler == null) {  
     //设置MainHandler 是一些接受生命周期的方法  
        sMainThreadHandler = thread.getHandler(); 
    }
    if (false) { 
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();  //开始接收消息
    throw new RuntimeException("Main thread loop unexpectedly exited");
    }

在代码中我们可以看到 Looper.prepareMainLooper();这段代码就是在创建Looper。我们看看这个方法是怎么创建Looper的

 

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException
                ("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
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的创建,先是在perpareMainLooper()中调用了perpare(false)这里传入false的意思是,MessageQueue不可以中途退出,就是不可以调用MessageQueue的quit()方法,我们来看一下prepare()方法中的代码,先是判断ThreadLocal中是否存在Looper实例,如果有就抛出异常当前线程已创建了Looper,如果没有就创建一个Looper实例并set到ThreadLocal中。

 

ThreadLocal是什么?

在这说说我对ThreadLocal的理解,之前看了看源码也没看太懂,然后又看了一些文章,我觉得ThreadLocal就像是一个HashMap集合,只不过它key值不是我们自定义的,它的key值好像是线程名字(能代表每个线程的标识),我们用ThreadLocal.set(Object)设置值的时候,它就是用线程名字做key,用我们传递进去的对象做值。

我们看到的:

sThreadLocal.set(new Looper(quitAllowed));

应该是:

sThreadLocal.put(Thread.currentThread().getName(),new Looper(quitAllowed));

上面这段代码是我的猜测,但它用起来确实是这样的效果。

同理:

sThreadLocal.get()

应该是:

sThreadLocal.get(Thread.currentThread().getName());

所以上面prepare()方法中的sThreadLocal.get()这句话就是在获取当前线程的Looper。

 

ok我们继续说Looper,刚刚我们说到了创建Looper,那么我们看看创建Looper时都做了什么事情

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

上面的代码可以看出,在创建Looper时,同时创建了MessageQueue。

到这呢Handler运行机制需要的东西差不多就都准备好了,接下来就是开始运行了。

 

刚刚我们都知道Looper是在主线程入口main方法中通过Looper.prepareMainLooper(); 创建的,在创建完Looper后,接着又调用了Looper.loop()方法(在上面的源代码中可以看到),我们看一下loop()方法具体做了什么

public static void loop() {
         // 拿到Looper
        final Looper me = myLooper();
        if (me == null) {
            // 没调用prepare初始化Looper,报错
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 拿到消息队列
        final MessageQueue queue = me.mQueue;
        ......

        for (;;) {
            // 从消息队列取出下一个信息
            Message msg = queue.next();
            if (msg == null) {
                // 消息为空,返回
                return;
            }
            .......
            try {
                // 分发消息到Handler
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            }
            // 消息回收,放入缓存池
            msg.recycleUnchecked();
    }

1.第3行,获取通过自身myLooper()方法获取当前线程Looper对象,myLooper()就是通过ThreadLocal获取当前线程对象

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

2.第4-7行,判断获取的Looper对象是否为空,为空则抛出异常,说明Looper没有创建

3.第9行,拿到跟当前线程一起的Looper中的消息队列

4.第12到27行,开始循环取出消息队列中的消息,如果有消息就通过Message中target变量指定的handler分发消息并处理

5.第26行,消息使用完进行回收,下次可以通过handler.obtainMessage()进行获取

 

ok,怎么获取消息和怎么处理消息我们都了解了,再说说如何把消息发送到MessageQueue消息队列中,一般我们发消息都是用handler.sendMessageDelayed(),

handler.sendMessage(),andler.sendEmptyMessage()等方法进行发送,但它们最终都会调用MessageQueue.enqueueMessage()向消息队列中发送消息,看下enqueueMessage()的源码

boolean enqueueMessage(Message msg, long when) {
        ......

        synchronized (this) {
            ......

            // 记录消息处理的时间
            msg.when = when;
            Message p = mMessages;
            // 唤醒线程的标志位
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 这里三种情况:
                // 1、目标消息队列是空队列
                // 2、插入的消息处理时间等于0
                // 3、插入的消息处理时间小于保存在消息队列头的消息处理时间
                // 这三种情况都插入列表头
                msg.next = p;
                mMessages = msg;
                // mBlocked 表示当前线程是否睡眠
                needWake = mBlocked;
            } else {
                // 这里则说明消息处理时间大于消息列表头的处理时间,因此需要找到合适的插入位置
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 这里的循环是找到消息的插入位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // 到链表尾,或处理时间早于p的时间
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        // 如果插入的消息在目标队列中间,是不需要检查改变线程唤醒状态的
                        needWake = false;
                    }
                }
                // 插入到消息队列
                msg.next = p; 
                prev.next = msg;
            }

            if (needWake) {
                // 唤醒线程
                nativeWake(mPtr);
            }
        }
        return true;
    }

1.第9行,开始先把这个消息队列中的消息赋值给p(由于是单链表存储形式,所以就是把第一个message对象赋值给p)

2.第12到22行,存在判断的这三种情况

(1)目标消息队列是空队列

(2)插入的消息处理时间等于0

(3)插入的消息处理时间小于保存在消息队列头的消息处理时间

存在以上三种情况,就把新传进来的Message插入到链表头部,优先处理。

3.第22到42行,把新的message插入到链表尾部,第27到38行,是通过循环移动链表指针,把指针移动到最后,p为null,prev为最后一个message时停止,然后把新的message插入到最后,prev.next = msg,msg.next = p

作者能力有限,文中若有误导内容,望指正。

文中部分代码内容借鉴https://www.jianshu.com/u/9cf1f31e1d09

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Granger_g

大爷,赏小女子口饭吃如何?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值