Handler处理消息相关源码解析

Posted 涂程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler处理消息相关源码解析相关的知识,希望对你有一定的参考价值。

一、handler的用法

1.1、用于线程切换

Handler handler = new Handler(Looper.getMainLooper());
new Thread(new Runnable() 
    @Override
    public void run() 
        handler.post(() -> 
            binding.tv.setText("hello world");
        );
    
).start();

1.2、线程间通信

为了防止handler导致内存泄露,handler对Activity采用弱引用,因弱引用不会影响Activity的java生命周期。

static class MyHandler extends Handler 
    WeakReference<MainActivity> mActivityWeakReference;public MyHandler(MainActivity activity) 
        mActivityWeakReference = new WeakReference<>(activity);
    
    @Override
    public void handleMessage(@NonNull Message msg) 
        MainActivity activity = mActivityWeakReference.get();
        if (activity != null && msg!=null && msg.obj!=null) 
             //todo 
        
    

MyHandler myHandler = new MyHandler(MainActivity.this);
new Thread(new Runnable() 
    @Override
    public void run() 
        Message message = Message.obtain();
        message.obj = "aaa";
        mMyHandler.sendMessage(message);
    
).start();

1.3、任务延时执行

static class MyRunnalbe implements Runnable 
    @Override
    public void run() 
        Log.e("test", "test memory leak?");
    

//发送延时任务
Handler handler3 = new Handler(Looper.getMainLooper());
handler3.postDelayed(new MyRunnalbe(), 10000);

最后大家不要忘了,一定记得在Activity的onDestroy及时解除Message对Handler的强引用。

@Override
protected void onDestroy() 
    super.onDestroy();
    mMyHandler.removeCallbacksAndMessages(null);

二、Handler(Loope(MessageQueue))的创建

2.1、Handler创建

先看看handler的构造函数。

构造函数肯定是相互调用,只需要看最多的参数的那个

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async)  xxxx 
  • 从构造可以看出,Handler的创建需要Looper,没有Looper玩不转呐,我们之前写handler用法的时候,都是在主线程创建Handler,它构造里面会调用Looper.myLoop()获取Looper。

  • ThreadLocal可用于线程间的数据隔离,这是解决多线程并发的一种手段 (不共享可变资源)。将线程需要的Looper保存在ThreadLocal里面。

  • 我们App的入口函数就是ActivityThread的main方法,一般在main方法里面就做两件事,第一件就是启动创建主线程的Looper,启动Looper轮询;第二件事就是将ApplicationThread的binder对象注册给AMS(AMS拿到它后就可以调用App的方法)。loop轮询一旦跳出了死循环,App直接就抛异常了。

  • 如果最后一个参数async为true,那么通过handler.sendMessage的message都会打上异步消息的标签。

//from Handler
public Handler(@Nullable Callback callback, boolean async) 
   .....  
    mLooper = Looper.myLooper();
    if (mLooper == null) 
        throw new RuntimeException(
           "Can't create handler inside thread " + Thread.currentThread()
                                              + " that has not called Looper.prepare()");
    
  .......


//from Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//myLooper直接从Threadlocal中取
public static @Nullable Looper myLooper() 
    return sThreadLocal.get();

//创建主线程Looper,因为主线程Looper不支持退出
public static void prepareMainLooper() 
    prepare(false);
    synchronized (Looper.class) 
        if (sMainLooper != null) 
            throw new IllegalStateException("The main Looper has already been prepared.");
        
        sMainLooper = myLooper();
    

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


//from ActivityThread
public static void main(String[] args) 
    ....... 
    //创建主线程Looper
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    //向AMS注册,将app的binder对象给AMS
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) 
        sMainThreadHandler = thread.getHandler();
    
    .....
    //启动轮询
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");

Q: 如果直接在子线程里面new Handler()创建,从ThreadLocals里面肯定是取不到对应的looper的,所以会抛异常。

需要显式调用Looper.prepare()才行。

子线程中弹吐司也要先通过Looper.prepare()创建Looper,不然也会奔溃。

原因同上👆

2.1.1、ThreadLocal原理

都说ThreadLocal可用于线程间数据隔离,完美的解决并发问题,那啥原理呢? 先看看用法。

public void set(T value) 
    Thread t = Thread.currentThread(); //获取当前线程
    ThreadLocalMap map = t.threadLocals; //线程的t.threadLocals
    if (map != null)
        map.set(this, value);  //直接添加
    else
        createMap(t, value);   //那就给当前线程创建这个ThreadLocalMap类型的t.threadLocals成员

private void set(ThreadLocal<?> key, Object value) 
    Entry[] tab = table;
    int len = tab.length; 
    int i = key.threadLocalHashCode & (len-1); //斐波那契散列算法
    for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)])  //开放寻址法解决冲突
        ThreadLocal<?> k = e.get();    
        if (k == key) 
            e.value = value;
            return;
        
        if (k == null) 
            replaceStaleEntry(key, value, i); 
            return;
        
    
    tab[i] = new Entry(key, value); //key就是ThreadLocal
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)  rehash();

void createMap(Thread t, T firstValue) 
    t.threadLocals = new ThreadLocalMap(this, firstValue);

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) 
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);

threadlocal.set(xxx)时,会从当前线程(主线程/子线程)取出它的ThreadLocalMap类型的成员threadLocals,不为null就直接添加了,为空则先创建。ThreadLocalMap底层采用数组实现,不像HashMap采用数组+链表/红黑树实现。这里的元素的index采用斐波那契散列算法实现,采用开放寻址法解决散列冲突,具体参考ThreadLocalMap的hash算法。数组元素Entry很有特点。

static class Entry extends WeakReference<ThreadLocal<?>> 
    Object value;
    Entry(ThreadLocal<?> k, Object v) 
        super(k);
        value = v;
    

Entry是一个弱引用类,直接通过entry.get()可以获取到Threadlocal引用,因其作为entry的key,只能对应一个值value。同时也参与table数组index的hash算法(i = key.threadLocalHashCode & (len-1))。如通过entry.get()获取threadLocal为null时,此时value不为null,就有发生泄漏可能,一般建议就是用完及时进行remove掉。

2.2、Looper的创建

public static void prepare() 
    prepare(true);

public static void prepare() 
    prepare(true);

//quitAllowed 是否支持退出,默认是支持退出的。但是主线程的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));

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

Handler创建需要Looper,Looper创建需要MessageQueue。如果多次调用Loopr.prepare来创建Looper会报错,即在一个线程里面创建多个Looper会报错

主线程的Looper当然不支持退出,不然随便调用looper.quit,App就奔溃了。用户子线程中通过Looper.prepare()创建Looper默认是true,支持退出的。

2.3、MessageQueue的创建

private long mPtr; // used by native code
MessageQueue(boolean quitAllowed) 
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();

MessageQueue创建时,创建了一个long类型的成员,native可以通过这个long指针直接强转成native对象用,这里是底层的NativeMessageQueue的指针

从这里可以看出,一个线程可以创建无数个Handler,但只能有一个Looper,一个MessageQueue

三、Looper.loop() 与 handler.sendMessage(xx)

3.1、开启轮询消息Looper.loop()

入口函数ActivityThread.main()方法中,就开启了Looper.loop()。

//from Looper
public static void loop() 
    ......
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) 
        Message msg = queue.next(); // 可能阻塞
        ......
        msg.target.dispatchMessage(msg);
       ...... 
        msg.recycleUnchecked(); //消息进入循环池
    

Q: 如果让我们写个Handler分发、处理Message,你会怎么写?
因为Handler的分发事件可能在其他线程,被称为生产者,处理Message的Handler被称为消费者,典型的生产消费模型,肯定会考虑生产多了+消费慢,生产少了+消费快的场景,此时伪代码如下:

public synchronized void loop() 
    long now = System.currentTimeMillis();
    int count;                                   //mMessageQueue.top()就是获得一个Message
    while ((count = mMessageQueue.size()) == 0 || mMessageQueue.top().when > now) 
        long diff = (count == 0) ? 0 : (now - mMessageQueue.top().when);
        try 
            this.wait(diff);           //这里就类似Message msg = queue.next();
         catch (InterruptedException e) 
            e.printStackTrace();
        
    
    ......
    this.notifyAll();

系统肯定不会用wait/notify+synchronized这种性能欠佳的组合实现,对的,底层用的是Epoll机制实现
来看看MessageQueue的Next方法。

Message next() 
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) 
        if (nextPollTimeoutMillis != 0) 
            Binder.flushPendingCommands();  //被调用说明后面的代码可能会引起线程阻塞(此处不懂)
        
        nativePollOnce(ptr, nextPollTimeoutMillis)  //epoll_wait
        synchronized (this) 
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            /此处msg没有target(handler),那就表示这个msg为同步屏障消息,配合异步消息使用的
            if (msg != null && msg.target == null) 
                do 
                    prevMsg = msg;
                    msg = msg.next;    
                 while (msg != null && !msg.isAsynchronous());
            
            if (msg != null) 
                if (now < msg.when) 
                    //获取需要等待的时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                 else 
                     //终于拿到一个可用的Message了,单链表取出Message的操作,模板代码
                    mBlocked = false;
                    if (prevMsg != null) 
                        prevMsg.next = msg.next;
                     else 
                        mMessages = msg.next;  //所以mMessage就是下一个要处理的Message。记住了
                    
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                
             else 
                nextPollTimeoutMillis = -1;  //-1传给native的epoll_wait,表示永远不能唤醒,需要主动被wake
            
              //messageQueue为empty了 或者 mMessage是将来才处理的(when还没到)
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) 
                pendingIdleHandlerCount = mIdleHandlers.size();
            
            if (pendingIdleHandlerCount <= 0) 
                mBlocked = true;   //那就继续阻塞
                continue;
            
            //每次处理的handler最多只能是4个,多了不处理????
            if (mPendingIdleHandlers == null) 
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        
        for (int i = 0; i < pendingIdleHandlerCount; i++) 
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = idler.queueIdle(); //idleHandler的queueIdle返回值true/false处理不同
            if (!keep)                       
                synchronized (this) 
                    mIdleHandlers.remove(idler);
                
            
        
        pendingIdleHandlerCount = 0以上是关于Handler处理消息相关源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Android Handler消息机制源码解析

Handler与异步消息的源码解析

Handler与异步消息的源码解析

Android消息机制Handler解析(源码+Demo)

Android开发人员必看的 Handler 消息处理机制(源码实战)

Android开发人员必看的 Handler 消息处理机制(源码实战)