Handler机制以及looper不卡顿问题分析

Looper:

  1. Android环境下通过Looper.prepareMainLooper():里面还是调用的prepare(false)方法,这个false表示不可退出标志为。
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
  1. Looper.prepare():通过prepare(true)创建Looper对象,并将实例set到threadLocal中。需要手动调用mylooper()获取looper对象。
public static void prepare() {
    prepare(true);
}
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));
}
  1. Looper.looper():开启循环
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;   获取到looper绑定的消息队列
......
    for ( ;; ) {
        Message msg = queue.next(); // 如果当前队列为空,则阻塞队列,na因为next是syn同步块
        if (msg == null) {//如果对象为空直接退出
            return;
        }
        ......
        msg.target.dispatchMessage(msg);  //核心:通过dispatchMessage(msg)分发消息;
        ......
        msg.recycleUnchecked();  //清空对象中的字段
    }
}

Handler :

第一步:new handler的时候拿到当前线程的looper对象和looper绑定的MessageQueue对象。

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

第二步:提交msg的方法:所有的sendMessage最终都是调用sendMessageAtTime(msg,time)方法,然后将handler实例对象封装到msg对象中,通过queue.enqueueMessage()方法入队。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;  
    ........
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    ........
    return queue.enqueueMessage(msg, uptimeMillis);
}

第三步:通过dispatchMessage方法对收到的消息进行分发;

vpublic void dispatchMessage(Message msg) {
    if (msg.callback != null) { //如果Message中封装了callback方法
        handleCallback(msg);
    } else {
        if (mCallback != null) {  //如果new handler的时候传入了callback方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg); // 如果在handler中重写了handleMessage方法
    }
}

Message:

消息对象,其中的target是用来绑定handler的,数据结构是栈结构,先进后出
方法一:Message.obtin() ,入栈操作。先 复用Message,没有在创建一个。

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; 
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

方法二: recycle(),清空并入栈

void recycleUnchecked() {
    flags = FLAG_IN_USE;
清空数据。。。
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

MessageQueue:

单链表式的消息队列;

Looper.prepare() —> new Looper(boolen) —> new MessageQueue(boolen) —> int nativeInit() 由native层创建一个native层的MessageQueue队列,将地址返回存在java层。
消息入队方法:
boolen enqueueMessage(msg , time) :通过synchronized(this)阻塞,通过判断比较time,根据time的先后顺序入队。
消息出队方法:
Message next():根据msg中的when阻塞队列的等待时间。通过.next判断是否有下一个,依次获取。
阻塞方法:
nativePollOnce() —> nativeMessageQueue.pollOnce() —> 底层MessageQueue中的looper对象中的pollOnce(timeoutMillis) —> pollInner(timeoutMillis) —> int eventCount = epoll_wait()

总结:

Java层的阻塞是通过native层的epoll机制来实现的因此looper死循环不会造成UI阻塞。阻塞结果三种:eventCount <0:错误返回;eventCount =0:超时返回;eventCount > 0消息事件返回。

主线程一直Looper为什么不会产生卡死?

  1. Looper是在ActicityThread的main入口函数中创建,并在方法的最后一步开启looper(),放在最后一步也就为了将ActivityThread先创建出来并attach()好,将该完成的工作都完成比如说binder机制。
  2. 根据Hendler机制,Looper在prepare的时候,先new了一个looer然后将其放入ThreadLocal中,ThreadLocal的作用就是线程独有,因此Looper最多只会阻塞当前主线程,进程间通信可以binder执行,binder是通过创建新线程去完成通信任务的,当接收到进程间的消息后,通过handler机制添加到mq中。
  3. Looper循环中只做了一件事,就是从MessageQueue队列中获取消并通过Handler机制分发出去并处理,获取消息的真正实现是在C层的MessageQueue中,底层是通过pipe/epoll机制实现的。
  4. 当messagequeue队列空闲时,通过epoll_wait()方法进行阻塞,阻塞在pipe管道的读端,当有message消息添加进来时,pipe管道的写缓存不为空,epoll的触发机制将发送指令触发写操作,epoll_ctl()监听并返回结果。Looper将被唤醒,并从队列中读取结果,最终消息从message队列中获取并通过handler机制分发出去。

pipe/epoll机制:

工作原理:

Linux来说一切皆文件。Epoll机制有三个方法,epoll_create()、epoll_ctl()、epoll_wait();

  1. epoll_create():创建epoll句柄,使用mmap机制(1拷贝)加速内核和用户之间的数据传递,红黑树和和就绪链表,如果新增socket句柄,将在就绪链表中查找,有就返回,没有就添加进去;
  2. epoll_ctl():epoll事件的注册函数,对事件添加增删改查的功能,以及监听器
  3. epoll_wait():epoll的等待超时机制,返回值-1:错误(永久阻塞),0:超时,>0正常

Epoll触发机制:

触发机器也就是通知机制,分两种:水平触发(level-triggered)+边缘触发(edge-triggered)。
level-triggered:当缓存区中数据由空变不空时,将触发可读信号,直到读完为止;当缓存区中数据由满变为不满时,将触发可写信号,直到写满为止。
edge-triggered:当缓存区中数据由空变不空时,将触发可读信号一次,程序将执行轮询读操作,直到读到EGAIN为止;当缓存区中数据由满变为不满时,将触发可写信号一次,程序将执行轮询写操作。

猜你喜欢

转载自blog.csdn.net/massonJ/article/details/107961957