文章目录

  • 1、简介
  • 2、原理分析
  • 2.1 原理概述
  • 2.2 原理简图
  • 2.3 Handler
  • 2.3.1 Handler初始化
  • 2.3.2 Handler发送消息
  • 2.3.3 Handler处理消息
  • 2.4 looper
  • 2.4.1 looper的用途
  • 2.4.2 looper初始化与获取方式
  • 2.4.2 looper的运行流程
  • 2.5、ThreadLocal
  • 2.5.1 作用
  • 2.5.2 looper中ThreadLocal的工作方式
  • 3、Handler相关问题
  • 3.1、Handler存在的内存泄漏问题
  • 3.1.1 原因
  • 3.1.2 解决方案
  • 3.2、 Handler的post(runnable)和sendMessage的区别
  • 3.3 ThreadLocal vs Synchronized
  • 3.4 ThreadLocal造成内存泄漏?解决方案


1、简介

本文仅将记录下Handler消息机制的细节,具体可参考博文后的链接

2、原理分析

2.1 原理概述

handler 原理即整个handler实现主子线程切换请求数据更新UI的流程:
在子线程中执行耗时操作后,会通过handler对象在子线程中发送消息,这个过程会隐式的向消息队列MessageQueue中添加Message。而Looper对象又会调用loop方法不断的从MessageQuque中取出Message,再分发给Handler进行处理,那么之前发送的消息就会转到looper中,而looper运行于主线程所以方法处理也在主线程中了。
简要流程如下

2.2 原理简图

Android 消息机制 android handler消息机制_初始化


那么对于上面的描述我有这样几个问题

1、handler创建需要什么条件?handler与MessageQueue对应关系

2、Looper运行在哪里?

2.3 Handler

创建Handler对象最终会调用如下构造方法,初始化Looper成员变量 => mLooper,初始化MessageQuque => mQuque,而且会校验mLooper是否为空,为空抛异常

2.3.1 Handler初始化
// Handler.java
    public Handler(Callback callback, boolean async) {
        // 2.3.1 获取looper
        mLooper = Looper.myLooper();
        // 2.3.2 检验looper是否为空
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 2.3.3 获取MessageQuque
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
2.3.2 Handler发送消息

通过以下 Handler.sendMessage()方法,最终会执行queue.enqueueMessage()执行插入消息

// handler.java
public final boolean sendMessage(Message msg){
  return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
boolean enqueueMessage(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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

会按照 Message 的时间 when 来有序得插入 MessageQueue 中,可以看出 MessageQueue 实际上是一个有序队列,只不过是按照 Message 的执行时间来排序

2.3.3 Handler处理消息
// handler的方法
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    // 1、直接执行msg.callback 中的run方法
    private static void handleCallback(Message message) {
        message.callback.run();
    }

    
    // 3、继承handler 实现handlerMessage处理消息
    public void handleMessage(Message msg) {
    }

2.4 looper

2.4.1 looper的用途

1、Looper 内部维护一个无限循环,保证 App 进程持续进行
在应用进程启动时,会初始化looper,并执行looper.loop() 让while循环一直运行在主线程中

2.4.2 looper初始化与获取方式
// Looper.java
public final class Looper {
    // 静态成员  ThreadLocal类型sThreadLocal
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
	
	// 依据当前线程去获取对应looper	
	// 2.3.1 会依据此方法获取looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
	// 获取主线程的looper
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
	
	// 为当前线程缓存looper
    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));
    }
	
	// 设置主线程的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();
        }
    }
}

注意prepare方法,将looper绑定给线程时,会判断当前线程是否初始化过looper没,如果设置过,则抛异常,可以得出如下结论

  • looper.prepare 方法在一个线程中只会调用一次
  • looper的构造函数在一个线程中只能调用一次
  • MessageQuque在一个线程中只会初始化一次

通过以上代码可以分析得出:
1、构建Handler对象那么必须要为该handler设置looper,否则会抛异常
2、Looper 创建与获取依赖于ThreadLocal,其可以在自身线程中设置和获取类型数据,且数据独立于其他线程。
looper运行在创建looper的线程中。
3、handler -> looper -> message 都是一一对应的关系,也就是说同一个handler中 looper与messageQueue 只会存在一份

简要提一下ThreadLocal获取数据的方式,首先会获得先前所在线程的引用,通过线程获取线程内部成员变量mThreadLocals,这是一个 ThreadLocal.ThreadLocalMap 类型的的map,该对象是一个以ThreadLocal为键,具体类型值为值的map,那么就可以以当前ThreadLocal为键获取线程特定的值,也就是looper了。

thread -> threadLocal -> threadLocalMap -> entry(ThreadLocal 引用) -> values(Looper值)

2.4.2 looper的运行流程

通过如下源码我们可以得出如下结论

1、looper是运行在调用Looper.loop()方法的线程中的;
2、msg.target.dispatchMessage(msg) 即 handler.dispatchMessage(msg) 运行的线程和looper运行的线程保持一致;

looper的职责
不断从 MessageQueue 中取出 Message,然后处理 Message 中指定的任务

public static void loop() {
      // 依据ThreadLocal获取执行该静态方法的线程对应的looper
	  final Looper me = myLooper();
	   if (me == null) {
	       throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
	   }
	 // 获取当前looper的消息队列
     EnqueueMessage queue = me.mQueue;
     for(;;){
        // 轮询获得msg
        Message msg = queue.next(); // might block不是很懂啊
        //当message == null 时looper会退出循环...
        if(msg == null) 
           return;
        ....
        // msg的targrt就是handler
        // 不为空,转交给handler进行处理   
        msg.target.dispatchMessage(msg);  
     }  
 }

next() 方法是一个死循环,但是当没有消息的时候会阻塞,避免过度消耗 CPU。
nativePollOnce 方法是一个 native 方法,当调用此 native 方法时,主线程会释放 CPU 资源进入休眠状态,直到下条消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作,这里采用的 epoll 机制。

我们现在已经清楚,当MessageQueue中没有消息时,会调用nativePollOnce来使线程进入休眠状态;那何时唤醒呢?唤醒就是在插入消息时,如果检测到阻塞休眠的标志,那么就会执行nativeWake来唤醒messageQuque获取message的过程

Message next(){
      .......
      // 只有在退出时 才会返回null 
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
             }
     // 阻塞方法,主要是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。
     // 如果 nextPollTimeoutMillis = -1,一直阻塞不会超时。
     // 如果 nextPollTimeoutMillis = 0,不会阻塞,立即返回。
     // 如果 nextPollTimeoutMillis > 0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。    
          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) {
                  // 如果当前时间早于消息执行时间
                  // 设置定时器,然后去唤醒...
                   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;
           }
         }  
       }
}

2.5、ThreadLocal

2.5.1 作用

通过为线程提供成员变量的方式,通过它可以在指定的线程中存储数据和获取数据,其数据是与线程相关联的。独立于其他线程,线程安全的存放和读取数据。

2.5.2 looper中ThreadLocal的工作方式
public final class Looper {
   // 在应用进程内定义了 全局静态的 sThreadLocal 
   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
   
    public static void prepare() {
     prepare(true);
 }
    // 为线程设置唯一的looper
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 设置looper
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    // 获取looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
}

上面源码中定义了静态的ThreadLocal对象,该对象存活于整个应用进程中;通过set()以及get()方法可以为线程设置私有的looper对象。我们接下来去查看下ThreadLocal的api方法

public class Thread implements Runnable {
   ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
    // 获取当前线程存储的对象
    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();
    }
    // 设置数据
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    static class ThreadLocalMap {
        private Entry[] table;

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        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时需要设置范型类型,约定存储哪种类型的数据。
ThreadLocalMap是ThreadLocal静态内部类,内部维护了map,entry以ThreadLocal为键,对象值为值进行存储。

3、Handler相关问题

3.1、Handler存在的内存泄漏问题

3.1.1 原因

通常我们使用Handler时都是声明匿名内部类或者内部类的方式,那么该种方式这种内部类会隐式的持有外部类即Activity的引用,如果在运用handler执行一些耗时操作时退出Activity,而由于被handler持有会造成Activity资源无法被回收,那么就造成了内存泄漏

3.1.2 解决方案

静态内部类 + 弱引用持有Activity的方式

/**
 * 如何正确的使用handler
 */
public class HandlerUserActivity extends AppCompatActivity {
    private MyHandler mMyHandler;

    private static class MyHandler extends Handler{
        private WeakReference<HandlerUserActivity> mActivityWeakReference;

        public MyHandler(WeakReference<HandlerUserActivity> activityWeakReference) {
            mActivityWeakReference = activityWeakReference;
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                default:
                    break;
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMyHandler = new MyHandler(new WeakReference<>(this));
        setContentView(R.layout.activity_handler_user);

        // 1.handler.sendMessage & 重写 handler {void handleMessage(Message message)}
        // 2.handler.post(Runnable runnable)
        // 3.runOnUiThread(Runnable run)
        //    if(主线程 Thread.currentThread == mUiThread?? ){
        //        run.run();
        //    }else{
        //        mHandler.post(run);
        //    }
        // }

    }

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

3.2、 Handler的post(runnable)和sendMessage的区别

public final boolean post(Runnable r)
 {
    return  sendMessageDelayed(getPostMessage(r), 0);
 }

 private static Message getPostMessage(Runnable r) {
     Message m = Message.obtain();
     // 设置了message的callback
     m.callback = r;
     return m;
 }
 // 最终也会将该message插入messageQuque中

最终直接会在looper所在线程执行msg.callback.run()方法,与线程无关

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

3.3 ThreadLocal vs Synchronized

使用两者目的一致,都是为了保证线程安全的访问获取资源。
方式不同:
Synchronized是通过上锁下锁的机制使得多个线程竞争同一资源;节约内存资源
ThreadLocal即通过为每个线程分配独立副本对象的方式,让每个线程可以独立安全的访问资源;效率更高

3.4 ThreadLocal造成内存泄漏?解决方案

线程持有ThreadLocalMap,而map强引用持有value,如果此对象长时间不被使用想要被回收由于强引用持有而会造成强引用无法回收。
虽然 ThreadLocalMap 的每个 Entry 都是一个对 key 的弱引用,但是这个 Entry 包含了一个对 value 的强引用。
正常情况下,当线程终止,key 所对应的 value 是可以被正常垃圾回收的,因为没有任何强引用存在了。但是有时线程的生命周期是很长的,如果线程迟迟不会终止,那么可能 ThreadLocal 以及它所对应的 value 早就不再有用了。在这种情况下,我们应该保证它们都能够被正常的回收。

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
  }

解决方式通过remove方法移除

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

Qs:
子线程为什么不能直接new Handler()?
ThreadLocal作用以及简要实现?
Handler是如何切换线程的?
Looper.loop()死循环为什么不会导致主线程发生ANR?
ANR是如何发生的?
Activity的生命周期是如何在Looper.loop()死循环外执行的?