Handler原理,源码,常见问题,从Java到Kernel解析,面试题详解

导读:
本文打算分2步骤来讲解Handler,首先要有一个整体的流程说明,看看他的从Java 到kernel的完整调用过程,由于Handler还算是代码比较简单,逻辑比较清楚,所以这个过程也是相对清晰。
第二个步骤就是习题时间,作为训练,解答市面上关于Handler比较好的问题。
面向读者:好奇Handler具体实现流程的人,需要寻找Hook点,所以必须了解整个流程的人,准备面试的人。

第一步:Handler机制的整体流程

Handler API的调用就类似拿着一个handler的对象到处去发消息,然后在我们创建的Handler会实现一个接受消息的方法,并在这里写好我们的处理逻辑。

Handler所扮演的角色就类似一个管理类,提供给调用方使用,而他的api主要目的也很明确,就是提供给你发消息和接受处理消息的能力,但这些能力的实现却跟handler没有什么关系。

1.1 首先我们看发消息的流程。

Handler的构造器有很多,分别有三个参数,Looper,Callback, async:Boolean, 具体的意思,我们第二步骤再看,先看跟Looper相关的.

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

可以指定Looper,那么不指定的肯定就是当前线程的Looper了,因为Looper使用了TreadLocal来保证每个线程都是唯一性的嘛。

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

可以看到Handler上来就保存了Looper和Looper中的MsgQ对象,接着可以发现所有发消息的逻辑最后都会会聚到这个方法:

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

有一个msg.target 用Handler对象自身this赋值了,So,所有的Handler发送的msg都携带着发送者的信息,也就是作为发送方的Handler对象,这个作用后面再说,我们先看主流程。
最后都是走的MsgQ的方法,看来真正的发送逻辑和Looper关系不大,而是全靠MsgQ.
我们继续看MessageQueue的这个入队方法。

boolean enqueueMessage(Message msg, long when) {
   
		//对象锁,保证多线程发消息的同步性
        synchronized (this) {
   
            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;
                    }
                }
                //p是上面遍历跳出的正确插入位置,在p前面插入msg消息
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            //暂时先忽略needWake
            if (needWake) {
   
            //调用native层传入的mPtr指针代表着这个MsgQ的对象,提供给cpp调用
                nativeWake(mPtr);
            }
        }
        return true;
    }

这个nativeWake调用的是frameworks/base/core/jni/android_os_MessageQueue.cpp

这个文件。

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
   
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
   
    mLooper->wake();
}

刚才注释说过了,这个ptr代表着Java层的MsgQ对象,强转一下,最后调用了Looper.cpp的wake方法。


void Looper::wake() {
   
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif
    ssize_t nWrite;
    do {
   
    //这里使用了pipe管道,mWakeWritePipeFd代表管道的写端描述符,可以理解为一个flag
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);
    if (nWrite != 1) {
   
        if (errno != EAGAIN) {
   
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

至此发送的工作结束,一会儿分析接收消息的流程,小总结一下:
发送消息Java层只跟MsgQ有关,Handler -> MsgQ.java -> MsgQ.cpp -> Looper.cpp -> linux kernel的管道,而MsgQ本质是一个按照时间从前到后排序的单链表,由于MsgQ是Looper的,Looper是线程私有的,所以除了Handler的调用,后面的所有流程都是线程私有的,代表了一个线程。

1.2 我们再看接受消息的流程。

我们都知道再Handler类实现的方法fun handleMessage(Msg)可以接受到消息,但这个消息是从哪里来的呢, 发送的时候最后Looper.cpp调用了Linux 内核的管道,这里需要了解一下pipe和epoll的知识,这是一种多复用监听IO机制,描述一下就是,当管道里有人写入了,会唤醒线程并通知epoll来读数据,如果管道被读光了,而暂时也没有写入,那么读的线程会BLOCK,释放掉CPU资源。那么读到了,kernel就会通知到native层,在通知到Java层,我们反过看源码,从handleMessage来逆向观察,首先寻找handlerMessage是在哪调用的:

 /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
   
        if (msg.callback !=
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值