从源码角度理解handler通信机制

handler 我们都知道,是android多线程的一种通信机制,很多人也知道怎么用它,并且也大致知道它的工作流程,但是如果有人问到子线程是怎么将消息发到主线程的?,消息循环会不会耗资源?为什么线程间的通信不会干扰?怎么保证每个线程的Looper与MessageQueue是唯一的
接下来我会从源码的角度来回答上面的问题

消息发送

public class MainActivity extends AppCompatActivity {
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

    private void startThread(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = mHandler.obtainMessage();
                mHandler.sendMessage(message);
            }
        }).start();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startThread();
    }
}

如上所示在ui线程创建了一个handler实例对象,然后我在子线程拿到这个handler对象之后就可以往主线程发消息了,这看起来很简单;但这个时候可能有人会问了,那子线程的消息是怎么发到主线程去的呢?首先我们来看下ui线程handler是怎么创建的:

  public Handler(@Nullable Callback callback, boolean async) {
   		//代码省略...
        mLooper = Looper.myLooper();
  
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

如上所示,handler在创建的时候调用来一个关键方法Looper.myLooper()

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

我们看到这个方法返回来一个Looper对象,而这个Looper对象其实就是ui线程的Looper对象,为什么这么说呢?我们可以看到这个Looper对象是从sThreadLocal这个变量里面获取的,而sThreadLocal其实就是ThreadLocal:

   // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

我们知道ThreadLocal在多线程的时候是做线程隔离用的,它保证来本线程的变量不会被其他线程所改变,而且我们可以看到它是一个static final的变量,也就是说在整个jvm中,这个变量是唯一的,任何线程都是可以访问的,那问题来了,你怎么知道这个Looper一定就是ui线程的Looper呢?我们来看一下源码:

   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

从源码我们看到,它是先拿到当前线程,因为我们的handler就是在ui线程创建的,所这个当前线程其实就是ui线程,然后它从ui线程中拿到ThreadLocalMap, 然后从这个map中得到了Lopper对象,既然是从ui线程中得到的Looper对象,那这个Looper对象当然是ui线程的;那这个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));
    }

可以看到在Looper里面的prepare()方法中,它将Looper实例化之后放到了ThreadLocal里面,还需要注意的是if (sThreadLocal.get() != null)这段代码,它会先获取looper,如果sThreadLocal里面有,那就直接抛异常,这就保证了线程中Looper的唯一性;那这个prepare方法是啥时候调用的呢?其实是在ActivityThread的main方法中调用的,大家都知道ActivityThread的main()是android app的入口方法,在我们启动app的时候它会首先走ActivityThread:

 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

      	//代码省略...
        Looper.prepareMainLooper();

      	//代码省略...
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

可以看到它调用了prepareMainLooper方法:

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

最终在prepareMainLooper里面调用了prepare方法,而Looper的初始化,前面已经说了,就是在prepare方法里面初始化的,并且将它放到了sThreadLocal里面;这样我们就搞清楚了,为什么子线程可以往主线程发消息了,因为子线程有主线程创建的handler对象,而handler对象在创建的时候通过sThreadLocal拿到了主线程的Looper对象,所以在发消息的时候,它是先得到了Looper中的MessageQueue对象,然后将消息发到了MessageQueue里面,而MessageQueue的创建又是在Looper初始化的时候创建的,由于Looper是UI线程的,那Looper中的MessageQueue自然也是ui线程的

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

接下来我们就看下sendMessage方法的调用过程:

在这里插入图片描述
可以看到它最终会调用到enqueueMessage这个方法,而enqueueMessage这个方法是属于MessageQueue的

 boolean (Message msg, long when) {
 		//代码省略...
        synchronized (this) {
        	//代码省略...
            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 {
                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;
            }
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

可以看到,在这个方法中使用了synchronized关键字,因为MessageQueue的这块区域是线程共享的,意思就是只要能拿到MessageQueue对象,任何线程都是可以访问这个方法的,所以这就牵扯到线程安全问题,为了让消息的加入有个先来后到,所以在将消息加入到消息队列的时候用synchronized做了线程安全访问,这样就保证了消息队列中的消息是有序的;好了,当子线程的消息加入到主线程的消息队列里面之后,消息循环Looper就是会从消息队列里面去消息,这时候有人会问了,Looper是怎么知道消息队列里面是有消息的呢?
我们看一下enqueueMessage方法,其中有这么一段代码:

  if (needWake) {
                nativeWake(mPtr);
            }

这个nativeWake方法其实就是线程唤醒方法,也就是说如果需要唤醒Looper中的操作的话它会执行nativeWake方法来做唤醒操作,那这个needWake什么时候才是true呢?

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

可以看到needWake是由mBlocked决定的,而这个mBlocked就是是否需要阻塞的boolean变量,那这个mBlocked什么时候变成true了呢?
我们看下looper的loop()方法:

public static void loop() {
        final Looper me = myLooper();
      	//代码省略...
        final MessageQueue queue = me.mQueue;
       //代码省略...
        boolean slowDeliveryDetected = false;
        for (;;) {
            Message msg = queue.next(); // might block
          
            msg.recycleUnchecked();
        }
    }

可以看到,它最终调的是MessageQueue的next方法:

  Message next() {
      //代码省略...
        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 (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;
                }

            }
        }
        //代码省略...
    }

可以看到,当pendingIdleHandlerCount <= 0的时候,mBlocked才是true, 也就是说,如果队列是空的话它就是true, 这里有个方法很重要就是线程等待的方法nativePollOnce(ptr, nextPollTimeoutMillis);这个方法和前面说的nativeWake(mPtr);方法对应;也就是说如果队列是空的话,那就不用继续循环,直接调用nativePollOnce方法等待,这个nativePollOnce就是线程等待的方法,这个时候for循环就不会走了,也就节省了一部分循环开销,因为线程等待的时候会让出cpu的资源,所以看样子是一个死循环,但是在空闲的时候,线程是等待的,基本上是不消耗资源的;所以在上面将消息加入到队列之后会判断需不需要唤醒looper中等待的操纵,如果需要的话就调用nativeWake方法唤醒,假如说队列中是有消息的并且不是等待状态的话就不需要唤醒,这个时候looper就会继续循环从消息队列里面取消息,知道队列里面没消息,然后调用nativePollOnce方法进入等待状态来等待新消息加入之后重新唤醒!

消息处理

在loop里面获取到消息之后就会调用dispatchMessage来做消息分发:

 public static void loop() {
        final Looper me = myLooper();
        for (;;) {
        	 try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
			msg.recycleUnchecked();
        }
    }

msg的target变量其实就是handler,我们在调用handler的obtainMessage方法的时候,就将我们创建的handler的对象传给了msg的target变量:

  public final Message obtainMessage()
    {
        return Message.obtain(this);
    }
   public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }

接下来看一下handler的消息分发:

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的callback是不是空的,如果都是空的话,就调用handleMessage,这样的话消息就回调到我们的handleMessage这里了;
另外在消息分发处理之后,在loop方法里面还调用了msg.recycleUnchecked();方法对消息进行回收,看样子是回收其实是将消息里面的成员置空,然后放到一个缓存池里面:

    void recycleUnchecked() {
   
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

这样有什么作用呢,还记得我们通过handler调用obtainMessage来获取消息的方法吗,这个方法取的就是这个缓存池里面的消息:

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

如果缓存池里面没有消息的话,就创建一个消息出来,这样的话就可以减少对象的频繁创建,减少内存碎片优化内存!

到此,消息从子线程发送到主线程处理的流程就说完了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值