Handler机制原理

        Handler、Looper、Message是Android线程间通信的重要概念,我们在项目中会经常用到,最常用的写法,创建一个Handler对象,在线程中通过Handler发送消息来更新UI,这是很常用的写法,那么有时候我们把Handler的创建写在线程里面,运行就会报错,错误信息大都是“Can't create handler inside thread that has notcalled ,Looper.prepare().”,意思是说我们没有调用Loope.prepare方法,那么Looper的prepare方法到底都干了什么,它和Handler又有什么关系呢,它们的实现原理又是怎样的呢,下面我们详细分析一下。

        1. Looper.perpare();

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

        ThreadLocal,用来存储以当前线程为键的一对键值对(可以理解为HashMap),它的键默认是当前的线程对象,通过set()方法设置对应的值,可以是任意对象,在这里是指一个Looper对象。get方法就是获取以当前线程为键对应的值,这里也是指一个Looper对象。

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

        Looper的构造方法,创建了一个MessageQueue对象赋给了Looper的MessageQueue成员变量mQueue,把当前线程赋给了Looper的Thread成员变量mThread。

        2.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;

        // 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();
        }
    }
        首先调用 myLooper方法返回 prepare方法中 sThreadLocal保存的 Looper对象
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
        然后从 Looper中拿到初始化 Looper时创建的 MessageQueue对象赋给 queue,MessageQueue消息队列的内部实现其实是基于单链表的数据结构,虽然它叫消息队列,其中主要有两个方法 next和enqueueMessage(Message msg, long when),分别是从消息队列中取出消息和将消息添加到消息队列中。

        接着进入消息循环,这个循环的作用就是不停的从消息队列queue中取出消息,并交给handler来处理,下面我们仔细来分析。

        Message msg = queue.next(); // might block

        通过queue的next方法从消息队列中取出消息,queue.next()这个方法是阻塞式的,如果消息队列中没有消息对象就一直等待,直到有消息为止,类似于Java Socket编程中ServerSocket.accept()方法,也是一直等待Socket去连接,如果没有连接就一直等待。如果取到的消息为空就直接return。我们看一下next()方法

    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    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(TAG, "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;
        }
    }
        这段代码有点长,不过我们主要看下消息是怎么从消息队列中取出来的。
        首先进入循环,这是个死循环,也即是说如果没有消息它会一直循环等待,直到有消息, Message msg = mMessages,这个mMessage应该是在添加消息时把一个Message对象赋给了它的,然后对它判空,如果不为空就再遍历消息链表,直到找到一个异步消息,接着做了一些变量更新操作,用于下一次取出消息。最后返回这条消息。

        继续到msg.target.dispatchMessage(msg);

        通过查看Message源码我们发现Message的target属性是一个Handler对象,为了了解清楚这个target是怎么被初始化的,我们从获取Message对象开始分析。
handler.obtainMessage();
public final Message obtainMessage(){
    return Message.obtain(this);
}

        走到Message.obtain(this);

public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;

    return m;
}
        这里传入了一个this对象,也就是handler,这个handler就是我们在代码中实例化的handler,接着又调用obtain()方法获取到Message对象m,把handler赋给m的target属性。我们再看一下Message的obtain()方法。
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}
        这块主要就是从消息池中取出消息,如果没有的话就直接new一个Message对象,所以我们在写项目创建Message对象的时候尽量用 handle.obtainMessage(),不要直接 new Message(),因为obtainMessage方法最后如果没有获取到Message对象时才会new出一个,这样处理会比较合适。

        回到上面msg.target.dispatchMessage();

        msg.target其实就是初始化的handler,然后调用Handler的dispatchMessage();

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

        首先判断msg.的callback是否为空,接着再判断handler的mCallBack是否为空,这些在对象初始化的时候都为空,所以执行handleMessage(),这就到了我们重写的方法了。

        说了这么多,可能大家不太明白,我们在代码中没有调用过Looper.prepare()和Looper.loop(),那这些代码是在什么时候调用的呢,其实在Activity启动的时候,系统就已经在主线程中调用Looper.prepare()和Looper.loop()了,不过在进入到消息循环后,没有Message对象,所以处于阻塞状态,直到Handler发送消息到MessageQueue,这时才开始处理消息。下面我们分析一下Handler的初始化以及发送消息的具体过程。

        首先看下初始化过程

public Handler() {
        this(null, false);
}
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&`11
            (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 static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

        Looper的myLooper方法发返回sThreadLocal中以当前线程为键的Looper对象,接着把这个looper对象赋给Handler的Looper成员变量mLooper

接着把looper对象的消息队列赋给Handler的MessageQueue成员变量mQueue。

        callback和async默认分别是null和false。到这Handler的初始化就结束了。

        3.handler.sendMessage();

        发送消息其实有很多个方法,sendEmptyMessage(),sendMessageDelay()等,这些方法大体上都差不多,只是msg一些属性的值不同,或者有一些延时,最终都会到sendMessageAtTime
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
        如果消息队列为空就会报错,不为空就到 enqueueMessage(queue, msg, uptimeMillis);
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
        这里把this对象赋给了msg.target,接着把消息保存到消息队列中。在前面初始化Message的时候,我们建议用handle.obtainMessage()方法获取Message对象,这时会把handler对象复制给msg.target属性,但是如果我们没有这样获取msg对象,那这个属性就为空了吗?,当然不,这里就有了合理的解释,在发送消息后,enqueueMessage方法中会将Handler赋给msg的target属性,也就是说每个消息都有对应一个Handler,一个Handler对应多个Message,现在这些看起来是那么的顺理成章。
        继续来看 enqueueMessage(Message msg, long when)
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            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 {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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的成员变量mMessage一直指向第一个消息,也就是最新加入的消息,插入的过程如下图所示
        第一次
第二次

第三次

这就是添加消息的大概过程,总结起来就是使用单链表结构存储,进行头插操作。

        4.handler.post()

       在讨论这个问题之前,我们先看一下下面这段代码
class TestThread extends Thread{
    	
        @Override
        public void run(){
                Runnable r = new Runnable(){
                        @Override
                        public void run() {
                                String currentThreadName = Thread.currentThread().getName();
                                System.out.println("currentThreadName:" + currentThreadName);
                        }
                };
                handler.post(r);
        }
}
        开启这个线程后,我们想一下它的输出结果,可能我们会觉得输出应该是”Thread-xxx”,但是实际结果却是main,明明在线程里面执行的怎么能是main呢,为了解决这个疑惑,我们就要求助于handler.post()的源码了。
public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
}
        继续看下 getPostMessage
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
        这里会得到一个Message对象m,然后把Runnable对象赋给m的callback属性,接着返回m,之后发送消息,同时添加到消息队列中,然后就又到了这段代码
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
        现在msg的callback属性就不为空了,刚把一个Runnable对象赋给了它,所以现在就要执行handleCallback()了
private static void handleCallback(Message message) {
    message.callback.run();
}
            看到这大家应该都明白了吧,在handleCallback中直接调用了msg.callback的run方法,所以这个Runnable线程体并没有开启,这也就解释了为什么开始那段程序的输出是main,那么这种代码设计又有什么意义呢,其实这样可以把一段代码块当成一个对象进行传递,使代码更加灵活。有点像iOS中的Block,(悄悄的装一下逼)我们在平时写代码时也应该多借鉴这种写法,这样写出来的代码才更加优雅~~~
       
        用这张图来总结一下
 

          追求优雅的代码

---End---

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值