Android 5.0 源码分析 Handler Looper MessageQueue 底层原理


 理解Looper类

 每个线程都只能有一个Looper类的实例对象,looper类通过实例方法prepare() 创建:

<span style="font-weight: normal;">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));
}</span>

在方法中,首先使用一个静态变量 sThreadLocal 它的类型是ThreadLoacal<T>  我们研究下为什么要使用这个模板类:

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

先了解一下ThreadLocal类提供的几个方法:

<span style="font-weight: normal;">public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }</span>
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。

下面是get方法的实现:

<span style="font-weight: normal;">public T get() {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
        }else {
            values = initializeValues(currentThread);
        }
    return (T) values.getAfterMiss(this);
}</span>
第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。

getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。

首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

通过sThreadLocal 中的get 与set方法 我们做到了一个线程只有一个Looper与之相对应

在调用完成prepare后我们就创建了一个Looper对象

在我们的Android 应用启动时我们会创建一个主线程 同时会有一个Looper与之相对应

在Looper中我们有一个接口 获取主线程的Looper:

<span style="font-weight: normal;">public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}</span>
在创建好Looper后可以调用 Looper类的loop()方法进入一个无限的循环。loop()类是一个静态的方法,它里面有一个无限的for循环:

<span style="font-size:14px;">    public static void loop() {
        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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }</span>
下面我们着重关注下                  
MessageQueue这个类的实现:
贴出构造函数的代码:
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
它的初始化工作都交给JNI方法nativeInit来实现了:
static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {  
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();  
    if (! nativeMessageQueue) {  
        jniThrowRuntimeException(env, "Unable to allocate native queue");  
        return;  
    }  
  
    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);  
}
在JNI中,也相应地创建了一个消息队列NativeMessageQueue:
NativeMessageQueue::NativeMessageQueue() {  
    mLooper = Looper::getForThread();  
    if (mLooper == NULL) {  
        mLooper = new Looper(false);  
        Looper::setForThread(mLooper);  
    }  
}
它主要就是在内部创建了一个Looper对象,注意,这个Looper对象是实现在JNI层的,它与上面Java层中的Looper是不一样的,不过它们是对应的。
看下这个Looper的构造函数:
Looper::Looper(bool allowNonCallbacks) :  
    mAllowNonCallbacks(allowNonCallbacks),  
    mResponseIndex(0) {  
    int wakeFds[2];  
    int result = pipe(wakeFds);  
    ......  
  
    mWakeReadPipeFd = wakeFds[0];  
    mWakeWritePipeFd = wakeFds[1];  
  
    ......  
  
#ifdef LOOPER_USES_EPOLL  
    // Allocate the epoll instance and register the wake pipe.  
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);  
    ......  
  
    struct epoll_event eventItem;  
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union  
    eventItem.events = EPOLLIN;  
    eventItem.data.fd = mWakeReadPipeFd;  
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);  
    ......  
#else  
    ......  
#endif  
  
    ......  
}
这个构造函数做的事情非常重要,它跟我们后面要介绍的应用程序主线程在消息队列中没有消息时要进入等待状态以及当消息队列有消息时要把应用程序主线程唤醒的这两个知识点息息相关。它主要就是通过pipe系统调用来创建了一个管道了:
管道是Linux系统中的一种进程间通信机制,具体可以参考前面一篇文章Android学习启动篇推荐的一本书《Linux内核源代码情景分析》中的第6章--传统的Uinx进程间通信。简单来说,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。这个等待和唤醒的操作是如何进行的呢,这就要借助Linux系统中的epoll机制了。 
现在我们回到Java层中Looper函数中的loop函数:
在loop函数中我们最重要的就是调用 MessageQueue中的next函数:
public class MessageQueue {  
    ......  
  
    final Message next() {  
        int pendingIdleHandlerCount = -1; // -1 only during first iteration  
        int nextPollTimeoutMillis = 0;  
  
        for (;;) {  
            if (nextPollTimeoutMillis != 0) {  
                Binder.flushPendingCommands();  
            }  
            nativePollOnce(mPtr, nextPollTimeoutMillis);  
  
            synchronized (this) {  
                // Try to retrieve the next message.  Return if found.  
                final long now = SystemClock.uptimeMillis();  
                final Message msg = mMessages;  
                if (msg != null) {  
                    final long when = msg.when;  
                    if (now >= when) {  
                        mBlocked = false;  
                        mMessages = msg.next;  
                        msg.next = null;  
                        if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);  
                        return msg;  
                    } else {  
                        nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);  
                    }  
                } else {  
                    nextPollTimeoutMillis = -1;  
                }  
  
                // If first time, then get the number of idlers to run.  
                if (pendingIdleHandlerCount < 0) {  
                    pendingIdleHandlerCount = mIdleHandlers.size();  
                }  
                if (pendingIdleHandlerCount == 0) {  
                    // No idle handlers to run.  Loop and wait some more.  
                    mBlocked = true;  
                    continue;  
                }  
  
                if (mPendingIdleHandlers == null) {  
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];  
                }  
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);  
            }  
  
            // Run the idle handlers.  
            // We only ever reach this code block during the first iteration.  
            for (int i = 0; i < pendingIdleHandlerCount; i++) {  
                final IdleHandler idler = mPendingIdleHandlers[i];  
                mPendingIdleHandlers[i] = null; // release the reference to the handler  
  
                boolean keep = false;  
                try {  
                    keep = idler.queueIdle();  
                } catch (Throwable t) {  
                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);  
                }  
  
                if (!keep) {  
                    synchronized (this) {  
                        mIdleHandlers.remove(idler);  
                    }  
                }  
            }  
  
            // Reset the idle handler count to 0 so we do not run them again.  
            pendingIdleHandlerCount = 0;  
  
            // While calling an idle handler, a new message could have been delivered  
            // so go back and look again for a pending message without waiting.  
            nextPollTimeoutMillis = 0;  
        }  
    }  
  
    ......  
}
一是当消息队列中没有消息时,它会使线程进入等待状态;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。
执行下面语句是看看当前消息队列中有没有消息:

  1. nativePollOnce(mPtr, nextPollTimeoutMillis);  






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值