handler基本原理

简单回顾一下Handler机制中几个对象的主要作用

Handler机制中最重要的四个对象
Handler:负责发送消息及处理消息
Looper:复制不断的从消息队列中取出消息,并且给发送本条消息的Handler
MessageQueue:负责存储消息
Message:消息本身,负责携带数据
那么,一个消息从发送出去,到回到Handler自己身上,这个过程具体是怎样的?
这个就不得不去看源码了
我们从Android Handler之从主线程往子线程发消息(一)中的
四、怎么从主线程发送消息到子线程?(虽然这种应用场景很少)的示例代码看起

Thread thread = new Thread(){
            @Override
            public void run() {
                super.run();
                //初始化Looper,一定要写在Handler初始化之前
                Looper.prepare();
                //在子线程内部初始化handler即可,发送消息的代码可在主线程任意地方发送
                handler=new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                      //所有的事情处理完成后要退出looper,即终止Looper循环
                        //这两个方法都可以,有关这两个方法的区别自行寻找答案
                        handler.getLooper().quit();
                        handler.getLooper().quitSafely();
                    }
                };
            <span class="token comment">//启动Looper循环,否则Handler无法收到消息</span>
            <span class="token class-name">Looper</span><span class="token punctuation">.</span><span class="token function">loop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
    thread<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//在主线程中发送消息</span>
handler<span class="token punctuation">.</span><span class="token function">sendMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

选择这个蹩脚的demo是为了让大家更好的理解Handler机制,耐心看下去,最后你心中所有的疑问都会有答案

一、先来解释第一行代码
Looper.prepare();

这个很好解释,只要查看Handler的构造方法即可

//空参的构造方法,这个方法调用了两个参数的构造方法
  public Handler() {
        this(null, false);
    }

//两个参数的构造方法
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;
}

看到了没有,Handler的构造方法中会验证Looper,如果Looper为空,那么会抛出空指针异常

如果你足够细心,你会发现,Handler在构造方法中还做了一件事,
将自己的一个全局消息队列对象(mQueue)指向了Looper中的消息队列
即构造方法中的这行代码
mQueue = mLooper.mQueue;
先记住,有用

二、第二行代码是初始化了Hanlder并且重写HandleMessage()方法

这个没什么好解释的

三、我们调用了Looper.loop()方法,这个方法的具体原理随后解释

四、handler.sendMessage(message)的主要作用

最接近我们使用的就是handler.sendMessage(message);这行代码了,那么我们从这行代码看起,看看这行代码之后发生了什么,为了方便大家看到,我把代码执行流程画了出来

sendMessage()之后代码执行流程

红线画出来的代表代码是哪个类的,可以看到,我们sendMessage()之后代码通过图中所示的几个方法,最终执行到了MessageQueue的enqueueMessage()方法。也就是说,接下来我们要看的就是MessageQueue中的方法了。
上源码

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                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;
    }

  
  
   
   
  
  

为了方便阅读,我删减了部分代码
可以看到,MessageQueue是一个单向列表结构,而MessageQueue的enqueueMessage()方法主要做的事情就是将Handler发送过来的Message插入到列表中。
也就是说,当我们调用handler.senMessage()方法的时候,最终的结果只是将这个消息插入到了消息队列中
上面的流程图中有一个问题:
最后一行代码queue.enqueueMessage()中的queue对象是什么时候初始化的?
还记得本篇博客在解释Looper.prepare()方法部分的最后一段话吗?
不记得了就往上翻一翻

发送消息的工作已经完成,那么Looper是什么时候取的消息,取出来消息又是怎么送回给Handler的呢?现在我们就不得不看Looper.loop()方法了

Looper.loop()方法

public static void loop() {
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);
            } 
        }
    }

   
   
    
    
   
   

同样的,为了降低阅读难度,我删掉了大部分的代码,只留下了这么几行核心代码,现在我们来看这几行代码的逻辑

  • 这是一个死循环
  • 这个循环的目的是从MessageQueue中取出消息
  • 取消息的方法是MessageQueue.next()方法
  • 取出消息后调用message.target对象的dispatchMessage()方法分发消息
  • 循环跳出的条件是MessageQueue.next()方法返回了null

不过,看到这里我们应该自然会想到,message.target.是哪个对象?
该对象的dispatchMessage()方法都做了什么操作?
带着疑问,我们进入Message类中去寻找target

public final class Message implements Parcelable {
 /*package*/ int flags;
<span class="token comment">/*package*/</span> <span class="token keyword">long</span> when<span class="token punctuation">;</span>

<span class="token comment">/*package*/</span> <span class="token class-name">Bundle</span> data<span class="token punctuation">;</span>

<span class="token comment">/*package*/</span> <span class="token class-name">Handler</span> target<span class="token punctuation">;</span>

<span class="token comment">/*package*/</span> <span class="token class-name">Runnable</span> callback<span class="token punctuation">;</span>

<span class="token comment">// sometimes we store linked lists of these things</span>
<span class="token comment">/*package*/</span> <span class="token class-name">Message</span> next<span class="token punctuation">;</span>

}

看到了吧,Message类中有一个成员变量 target,而这个target是一个Handler,也就是说,在Looper从MessageQueue中取出Message之后,调用了Handler的dispatchMessage()方法。
这里我们不禁要问,这个target指向了哪个Handler
带着这个疑问,我翻遍了Message类,也没有看到我想要的答案。此时我就想,既然Handler发送了消息就能接收到消息,那么会不会是在发送消息的途中发生了什么细节是我不知道的,那么我只好仔细看发送消息过程中的代码,终于让我发现了这个

//Handler的方法
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

   
   
    
    
   
   

在这个方法的第一行代码就是,将message的target属性赋值为发送message的handler自身。如果读者忘记了这个方法的调用时机,请往上翻翻,查看一下第四部分sendMeeage()中的流程图

也就是说,Looper取出消息后,调用了发送消息的Handler的dispatchMessage()方法,并且将message本身作为参数传了回去。到此时,代码的执行逻辑又回到了Handler中。

接着看handler的dispatchMessage()方法

/**
    *handler的方法
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

   
   
    
    
   
   

看到这里面一个我们非常熟悉到方法了没有?---handleMessage()方法,对,这个方法就是我们经常要重写的handleMessage()方法,也是我们处理消息时候的逻辑。
到这里,Handler机制工作的主要流程就完成了。
再来一个系统的工作示意图

Handler工作示意图

如果读者认真看了博客,那么到这里就应该对Handler的基本工作流程比较清晰了。我也在前面详细讲解了流程图中的方法调用过程。不过还有一些问题我们没有处理
一个线程中最多可以有几个Looper和几个MessageQueue?
我们来看Looper的构造方法即初始化方法

//Looper暴露出的静态初始化方法
//这个方法会调用下面的私有静态方法
  public static void prepare() {
        prepare(true);
    }
//Looper私有的静态方法
   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));
    }
//私有的构造方法,禁止外界调用
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

//从sThreadLocal中获取一个Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

解释一下上面三个方法

  • 我们只能通过Looper.prepare()方法去初始化一个Looper
  • Looper.prepare(boolean)方法的逻辑是一个线程中只能有一个Looper对象,否则在第二次尝试初始化Looper的时候,就会抛出异常。
  • 以线程为单位存储Looper的主要逻辑是通过ThreadLocal实现的
  • 私有的构造方法,禁止外界任意new出一个Looper

通过这段逻辑我们可以看出,
一个线程中最多有一个Looper
接着看Looper的构造方法,里面有一行代码
mQueue = new MessageQueue(quitAllowed);
是的,我们的MessageQueue是随着Looper的初始化而初始化的。那么,MessageQueue能不能随意的被new出来呢?

//MessageQueue的构造方法
 MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

   
   
    
    
   
   

可以看到,MessageQuede的构造方法是default的,也就是说,只有跟MessageQueue同一个包下才可以实例化MessageQueue,换句话说,我们用户是无法直接new一个MessageQueue对象出来的。而因为Looper在一个线程中只能有一个,从而导致MessageQueue也只能有一个

欢迎关注我的公众号
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值