Handler就是一个简化的邮递系统么?

·  阅读 694

前置补充

  • 关于本文的初衷不是讲很多细节,主要像聚焦在Handler的设计理念上,主要想讲述计算机系统中的很多事情在现实中其实有现成的例子可以参考理解,当然现实生活比程序肯定更复杂。

  • 知行合一,想完全理解一个事物,肯定不能光靠看文章,还是要在实际的工作中去使用。

  • 学习是渐进的,可能文中的一些知识点,笔者已经掌握,可能会一笔带过,大家有疑惑 ,建议或者文中的错误,多多提出意见和批评。

  • ThreadLocal推荐一本书 任玉刚老师的 Android开发艺术探索

  • 关于Java线程相关知识,推荐 杨冠宝/高海慧 老师的 码出高效:Java开发手册

正文

网上关于Handler的的文章已经有很多了,可能大家看了很多有的同学还是云里雾里,我写这篇文章的理念就是怎样将Handler讲述成我们平常经常使用的事物。

大家已经点进来了,就应该知道Handler是做什么用的,关于它的定义不在多言。

  • 我们用一个爱情故事来模仿这个通信的流程。

  • 1:MainThread(一个人见人爱的女生,我们就叫她main)。

  • 2:BThread (一个很倾慕main的男生,我们简称他为B)。

  • 3:剧情设定两个人无法直接通信(具体原因不赘述,大家可以百度一下ThreadLocal,本文不讲这个了)。

有了设定和人物,那么假如B想给main通信他需要怎么办呢,写信是一种方式。那我们就用写信来比喻Handler。那让我们来分析一下这个通信系统,首先来看Handler

本文采用6.0源代码

Handler系统

我们平常说的通过Handler进行线程间通信,通常是指的是通过整个Handler系统进行通信,Handler.java只是这个系统的一个入口.

Handler

分析一个东西,我们先从构造函数开始。

    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback) {
        this(callback, false);
    }
    
    public Handler(Looper looper) {
        this(looper, null, false);
    }
    
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
    
    public Handler(boolean async) {
        this(null, async);
    }
    
    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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 Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
复制代码

上面是Handler所有的构造函数,4个是没有实际的逻辑的,有实际的逻辑只有两个,我们就从这两个构造函数开始分析。

   public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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 Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

复制代码

两者的差别就是Looper是否是外部传入的,一个Looper使用的静态函数myLooper() 赋值的,我们暂时先放过这个静态函数一步一步来(放到Looper的环节中讲述)。不过我们看到这个mLooper如果为null就会抛出一个异常,可能很多同志都见到过这个异常Can't create handler inside thread that has not called Looper.prepare(),这个异常就是从这里来的。

分析以上的构造函数,我们发现在Handler整个系统中Looper是必须存在的一个事物。(有的同学会说,我可以在创建Handler的时候手动的传一个null进去,是的,这样的话会得到一个空指针异常)。

如果我们如开头所说,Handler来类比我们现实生活中的通信系统,我们通过它的构造函数得知这个通信系统有4个必须存在的参数,mLooper,mQueue,mCallback,mAsynchronous(mQueue包含在Looper中)。那我们再来一个一个的分析这4个参数,他们究竟在这个通信系统中扮演什么角色。首先先看Looper

Looper

  • mQueue包含在Looper中,放在一起看。

按照惯例,还是先看构造函数。

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

是一个私有的构造函数,里面讲我们上文提到mQueue给赋值,还有就是将mThread变量赋值为当前所处的线程。Thread.currentThread()不理解请自行百度。

那我们看一下Looper对象既然外部无法通过new关键字直接创建,那么它通过什么方式创建的呢?

Looper源码中,函数返回类型为Looper的函数只有下面两个。 我们先分析getMainLooper()函数,函数中只是返回了一个sMainLooper

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
复制代码

我们先看sMainLooper

    private static Looper sMainLooper;  // guarded by Looper.class
    
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
复制代码

大家发现饶了一圈,怎么有回到了myLooper()函数,那接下我们看myLooper()函数中的sThreadLocal是什么东西?

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
    }
复制代码

我们发现sThreadLocal就是一个ThreadLocal,使用它来存储Looper对象。

  • (前文提过ThreadLocal,它是Java多线程中一个非常非常重要的概念,建议不懂这个的同志,先去看一下这个东西到底是什么?然后再回过头来看这篇文章)。

我们会发现创建Looper对象只能通过唯一入口prepare来创建它。创建Looper的时候,它顺手的将MessageQueue给创建了出来(在上文Looper的构造函数中)。

  • MessageQueue包含的任务是非常重要的,并且要写入一些c++代码来分析。我们暂且跳过,先得出一个结论之后,在来逆推MessageQueue到底做了什么。

mCallback && mAsynchronous

mCallback:可以从Handler中是可以为null,不传就默认为null,其实是比较容易理解的一个概念,回调函数,不多做解释了,非主线剧情。

mAsynchronous: 从名字来看就是是不是异步的意思,后面会解释一下这个异步的概念。

实际例子

我们上面将Handler想象成一个通信系统,设定了人物,也简单的分析了一下Handler,下面我们来看一个实际的写信流程。

public Thread MainThread = new Thread(new Runnable() {
    @Override
    public void run() {

    }
});

public Thread BThread  = new Thread(new Runnable() {
    @Override
    public void run() {

    }
});
复制代码

假如B想通过Handler通信系统给Main写信,那么第一步

  • 1: Main得在通信系统中创建Handler,这个时候Handler可以形容为一个地址。看如下代码:
public Handler mainHandler;

public Thread MainThread = new Thread(new Runnable() {
    @Override
    public void run() {
        if (mainHandler == null) {
            Looper.prepare();
            mainHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.e(TAG, "handleMessage: "+ msg.obj);
                }
            };
            Looper.loop();
        }
    }
});
复制代码
  • 2 我们看在创建Handler之前,需要现在线程中使用 Looper.prepare() 创建一个Looper出来之后才能创建Handler(前文提到过原因)。那么Looper可以形容为什么呢,这个通信系统中的后台系统,我们接着往下看,看这个形容是否准确。

  • 3 :B拿到Main的Handler,就使用sendMessage()去给Main传递信息,sendMessage必须发送Message类型的消息,那么Message在通信系统中是什么角色呢,可以理解为信封和邮票,必须以规定好的方式去包装你写得信,这样才可以去发送。这个时候Handler扮演了一个投递入口的角色。

public Thread BThread = new Thread(new Runnable() {
    @Override
    public void run() {
        if (mainHandler != null) {
            Message message = Message.obtain();
            message.obj= "I LOVE YOU";
            mainHandler.sendMessage(message);
            Log.e(TAG, "BThread sendMessage 发送了");

        }
    }
});
复制代码
  • 4:从上面的例子代码和上文对Looper的分析中,我们没有看到Looper.loop()的作用,并且还有一个疑问,B只是投递了信息,谁帮忙传信的呢?我们看下是不是Looper.loop()。只展示关键代码,想看完整代码的同志请自行查看源码。
   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;

         ...

        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);

            ...

        }
    }
复制代码
  • 5: 我们在Looper.loop()中看到了一句msg.target.dispatchMessage(msg),这个从名字看上去很像一个传信的人,但是这个msg.target是个什么鬼东西啊,完全看不懂。从源码得知msg是一个Message类型的对象,那我们去看一下msg.target
public final class Message implements Parcelable {
    ...
    /*package*/ Handler target;
    ...
}
复制代码

target就是一个Handler啊,那它是在哪里赋值的呢?其实sendMessage最终会调用到enqueueMessage,具体的调用函数栈,就不贴出来了,有兴趣自行查看源码。

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

在这里我们看到target被赋值为调用者。也就是mainHandler.sendMessage(message);target就是mainHandler,看了下面的代码你更好理解

Message message = Message.obtain();
message.setTarget(mainHandler);
message.obj= "I LOVE YOU";
mainHandler.sendMessage(message);
Log.e(TAG, "BThread sendMessage 发送了");
复制代码

Message每个信封都支持我们手动写地址的setTarget,但是很多人觉得麻烦,那么通信系统呢,就默认将拿到的地址作为你要传送的地址。也就支持了我们不需要必须调用setTarget()。(有的同学可能比较调皮,我用mainHandler,去发送,target写其他可以么,是可以的,但是系统会帮我们修正,大家可以尝试一下)

MessageQueue,隐藏在内部的工作者

看到这这里,如果不接着深入探究,基本上一个完整的链条已经存在,但是还是有很多疑点,之前提到的MessageQueue还没说到,整个链条就完整了么?其实MessageQueue已经出镜了。loop()函数虽然起了一个死循环,但是每一封信都是从MessageQueue.next()中取出来的。

   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;

         ...

        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);

            ...

        }
    }
复制代码

国际惯例,先看构造函数。

// True if the message queue can be quit.
    private final boolean mQuitAllowed;
    private long mPtr; // used by native code
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
复制代码

构造函数,从名字来看mQuitAllowed是否允许关闭。退出机制比较复杂不想看的可以跳过,包含的知识点有点多。

  • 1:大家都知道Java的线程机制,1.5之前提供了stop函数,后面移除,移除的原因不赘述,现在线程退出机制就是代码执行完之后就会自动销毁。

  • 2:我们回头看下我们的例子代码,在调用Looper.loop()函数之后会启动一个死循环不停的取消息,一直到消息为null,才会returen。我们知道了退出的条件,我们看下系统怎么创造这个条件的。

public Thread MainThread = new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        if (mainHandler == null) {
            mainHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.e(TAG, "handleMessage: "+ msg.obj);
                }
            };
            Looper.loop();
        }
    }
});

Looper.java

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
    ....
}
复制代码
  • 3:调用 Loooper.quit(),来主动退出这个这个死循环,下面就讲述一下这个退出死循环的流程
    public void quit() {
        mQueue.quit(false);
    }
    
    void quit(boolean safe) {
        //判断当前是否允许退出,不允许就抛出异常
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            //锁+ 标志位 ,防止重复执行,记住这标志位。 后面还要用到
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                //退出是这个,清除所有的消息
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            //native 函数。 从名字上看是唤醒。
            nativeWake(mPtr);
        }
    }

复制代码

大家看到了熟悉的一个主动异常"Main thread not allowed to quit.",简单理解主线程不可以退出。主线程创建Looper的流程在本文不赘述,我们接着看调用MessageQueuequit函数的地方,

  • 4: 从上面的代码我们就看到了清除了缓存队列中的所有未发送的消息,然后唤醒?唤醒什么呢?不是退出么? 带着这三个疑问,走向更深的源码。
android_os_MessageQueue.cpp

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

Looper.cpp

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}
复制代码

这是一个到达Native的一个简单的逻辑顺序,Looper.cpp是对epoll的一个封装,我简单的描述一下这个过程

就是有(三个人都活着(线程),要喝水(用CPU),那么三个人要把水给平分(平分Cpu时间片)。

两个人没事干也不累,但是不能die,(还有一些专属任务,需要等待通知),那不干活就不应该喝水,要不就是资源浪费啊,怎么办?

epoll就是干这个的pollOnce就是通知线程进入休眠状态,等到有消息来的时候就会通知对应的人(对应的线程)去干活了,怎么通知呢? 就是通过wake函数。贴一下pollOnce的相关的关键代码,有兴趣的看一下

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ...
       //这里
        result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif

    ...
     //这里 epoll出现 ,如果想把这个探究明白 建议读这个类的源码,是Android对epoll的封装了
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    
    // No longer idling.
    mPolling = false;

    // Acquire lock.
    mLock.lock();

    // Rebuild epoll set if needed.
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked();
        goto Done;
    }

    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error, errno=%d", errno);
        result = POLL_ERROR;
        goto Done;
    }

    // Check for poll timeout.
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = POLL_TIMEOUT;
        goto Done;
    }
    ...
}
    
epoll.h
    
extern int epoll_wait (int __epfd, struct epoll_event *__events,
		       int __maxevents, int __timeout);
 
复制代码
  • 5 :看到这里其实大部分人是很迷惑的,建议迷惑的同志单独深入探究,单独理解上层的同学就看到喝水的故事就好了。那么回到上文说的唤醒,我们知道唤醒之后的线程从休眠的地方开始执行,我们看看陷入休眠的时候在哪里呢?
    Message next() {
        ...
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //这里休眠的.
            nativePollOnce(ptr, nextPollTimeoutMillis);
            //唤醒之后从这里开始执行.
            synchronized (this) {
                ...
                //还记得这个标志么?在quit函数中赋值为ture的
                if (mQuitting) {
                    dispose();
                    //这里reture 一个 null 
                    return null;
                }

            ...
           }
        }
    }

复制代码

(主线程)不会卡死的原因即 Looper退出总结,线程退出机制.

上面描述了退出的一个过程。在简单总结一下

  • 1: Looper.loop启动死循环,然而实际干的活是从MessageQueue.next()中一直取Message,如果没有Message MessageQueue 会调用nativePollOnce 让当前线程休眠(这就是为啥死循环不会卡死的原因,很浅显啊,只是简单论述,epoll 可以写好几篇文章了)。

  • 2: 发起退出死循环,终结线程,调用Looper.quit(),然后还是要调用MessageQueue.quit().

  • 3: MessageQueue.quit(),先判断当前是否允许退出,允许了将退出的标志位mQuitting设置为true,然后调用removeAllMessagesLocked()清除现在队列中的所有消息。然后唤醒线程

  • 4: 线程被唤醒了就回到第一步,当前没有消息你却唤醒线程,且退出标志位mQuitting设置为true了,MessageQueue.next()就会返回一个null。

  • 5: Looper.loop的死循环如果取到了的Messagenull,就会returen跳出死循环了。这样一个线程所有的代码执行完成之后,就会自然死亡了,这也是我们AndroidMain ThreadMessageQueue 不允许退出的原因。

大总结

整个大的线程通信系统

  • Handler就是一个门面,可以理解为地址。

  • Message像一个传递员,规定了信的格式和最后一公里的取信和传信。

  • Looper是一个后台系统,注册什么,所有的入口发起全在这里,让大家以为它把所有的活都干了。

  • MessageQueue位居后台的一个分拣员,和通知传递员去送信,这个核心就是它,就是所有人都看不到。

-- ps 图片后面补,画图工具崩溃了,我也崩溃了

  • 下篇文章计划是把Binder描述为一个现实中的例子来更好的理解,动笔却发现有点难度。就多给自己一点时间。
分类:
Android
标签:
分类:
Android
标签: