Handler源码分析(一)

87 阅读4分钟

1. Handler相关问题(一)

  • Handler主要是用来处理Android线程间通信的,可以说Handler贯穿了整个Android系统,所以Handler的重要性不言而喻了,下面带着以下几个问题来分析下Handler机制(基于Android11 SDK30
    1. Handler消息机制原理
    2. 一个线程有几个Looper,如何保证?
    3. Looper.loop方法是死循环为什么不会造成卡死(ANR)
    4. 子线程中使用Handler需要注意的问题
    5. 子线程中维护的Looper,消息队列无消息时的处理方案
    6. 为什么建议使用Message.obtain()来创建Message
    7. Handler内存泄露场景、原因分析、如何避免

2. Handler消息循环

  • Handler的消息循环主要是由Handler、Looper、MessageQueue以及Message四个类来协同完成的

2.1 Looper初始化

  • ActivityThread(主线程)的main方法中会对Looper进行初始化并开启消息轮询
public static void main(String[] args) {
   ...
   // 主线程的Looper在这里进行初始化的
   Looper.prepareMainLooper();
   ...
   // 主线程中的Looper开启循环
   Looper.loop();
   // 正是由于Looper.loop阻塞住才不会退出并抛下面的异常
   throw new RuntimeException("Main thread loop unexpectedly exited");
}
  • 看一下Looper.prepareMainLooper方法
/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. See also: {@link #prepare()}
 *
 * @deprecated The main looper for your application is created by the Android environment,
 *   so you should never need to call this function yourself.
 */
@Deprecated
public static void prepareMainLooper() {
    // 调用prepare方法初始化looper
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        // 这里获取主线程的looper
        sMainLooper = myLooper();
    }
}

// 使用Looper构造器创建Looper对象并存在主线程的ThreadLocal中,
// ThreadLocal可以先简单理解为每一个线程单独拥有的资源,key为线程名,用value存放looper
// quitAllowed参数最终赋值给MessageQueue的成员变量,表示主线程的Looper、MessageQueue不允许quit
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));
}

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    // 获取线程中存放的Looper对象
    return sThreadLocal.get();
}
  • MessageQueue是在Looper构造器中进行初始化的
// Looper构造器中会初始化MessageQueue,并将quitAllowed传递给MessageQueue
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
  • 从以上就可以看出,Looper与MessageQueue是一对一的关系,而ThreadLocal通过与线程的一对一关系就保证了线程与Looper/MessageQueue的一对一关系

2.2 Looper.loop()

  • 接下来分析Looper.loop消息轮询的机制,它是上面ActivityThread类的main方法中倒数第二行调用的
/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
         // 这里就是常见的子线程中使用Looper不调用Looper.prepare会抛异常的原因
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    ...
    // 开启无限循环不断获取MessageQueue中的message
    for (;;) {
        // 通过queue.next不断获取Message中的message,这个方法可能会阻塞在nativePollOnce中
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            // 调用了queue.quit会进入这里(子线程才行,主线程不允许quit)
            return;
        }
        ...
        try {
            // target是Message的成员变量,是Handler类型的对象(Handler target)
            // 通常是在handler.sendMessage方法中将handler赋值给msg.target
            msg.target.dispatchMessage(msg);
            ...
        } catch (Exception exception) {
            ...
        } finally {
            ...
        }
        ...
        // 消息处理完成之后会调用recycleUnchecked将消息进行一系列的回收,下面会讲
        msg.recycleUnchecked();
    }
}

2.3 queue.next()

  • 由上可见Looper.loop实际上是利用queue.next来获取消息的
  • 注:代码先剔除异步消息以及IdleHandler相关的代码
Message next() {
   ...
   // 消息需要等待的时长,0为立即执行,-1为无消息无限阻塞,其他正值表示等待时长
   int nextPollTimeoutMillis = 0;
   for (;;) {
       // 一个Native的阻塞方法,阻塞时长由nextPollTimeoutMillis决定
       // 或者调用另一个Native方法nativeWake,它会唤醒nativePollOnce放开阻塞
       nativePollOnce(ptr, nextPollTimeoutMillis);

       // 这里使用synchronized锁住queue对象来保证线程安全
       synchronized (this) {
           final long now = SystemClock.uptimeMillis();
           // mMessages是queue的成员变量,在enqueueMessage中赋值(sendMessage)
           // 可以理解为Message链表的头指针
           Message msg = mMessages;
           ...
           if (msg != null) {
               if (now < msg.when) {
                   // 延时消息,时间未到,根据msg.when - now获取要等待的时长
                   nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
               } else {
                   // 获取到了一个立即执行的消息
                   mBlocked = false; // 是否阻塞的标志,在enqueueMessage中会使用
                   ... 
                   // 指针右移
                   mMessages = msg.next;
                   
                   // 切断要处理消息的链条
                   msg.next = null;
                   msg.markInUse();
                   return msg;
               }
           } else {
               // 无消息时置nextPollTimeoutMillis = -1会一致阻塞,这也是子线程使用Handler需要注
               // 意的点;子线程Handler处理完后不调用quit,子线程便会被nativePollOnce阻塞住无法退出
               nextPollTimeoutMillis = -1;
           }

           // 调用quit后会执行
           if (mQuitting) {
               dispose();
               return null;
           }
           ...
       }
   }
}
  • 由此可见有需要马上处理的消息时会取出该消息立即返回给looper.loop中去处理,延迟消息会根据msg.when计算出相应的延迟时长nextPollTimeoutMillis从而阻塞在Native方法nativePollOnce中
  • 消息循环大致分析完了,接下来查看消息的发送过程,发送消息主要有sendMessage和post

2.4 handler.sendMessage()

// 发送消息
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
  • 实际调用的是sendMessageDelayed发送延时消息,只不过延时时长为0
/**
 * 发送延时消息
 * @param msg Message
 * @param delayMillis 延时时长
 * @return 是否发送成功
 */
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        // 延时时长小于 0 时直接置0
        delayMillis = 0;
    }
    // 实际上调用的是sendMessageAtTime,参数为当前时间 + 延时时长
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
  • 实际上又是调用的sendMessageAtTime
// 发送消息,uptimeMillis表示执行执行时间点
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    // 获取handler绑定的messageQueue,具体是在Handler的构造器中绑定的,待会儿分析
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    // 最终执行的是enqueueMessage
    return enqueueMessage(queue, msg, uptimeMillis);
}
  • 接着看enqueueMessage,它主要是将msg与handler绑定,并最终调用了queue.enqueueMessage
// enqueueMessage中主要做了
// 1、将msg与handler绑定
// 2、实际调用了queue.enqueueMessage
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

2.5 handler.post

  • 另一种发送消息的方式是利用handler.post(Runnable r)
// 使用post方式发送消息需要传递Runnable对象
// 虽然跟Thread线程的Runnable是同一个接口,但这里跟线程并没有半毛钱的关系
public final boolean post(@NonNull Runnable r) {
   return sendMessageDelayed(getPostMessage(r), 0);
}
  • 实质上依然是调用的sendMessageDelayed跟上面一样就不再重复分析,只是这里并没有传递Message对象,而是使用getPostMessage方法获取了一个Message对象
// 本质上是利用obtain方法创建的对象,并将Runnable类型的r赋值给Message的成员变量callback
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

2.5 queue.enqueueMessage

// message入messageQueue队列
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) {
            // 当前入队的msg为FrontMessage(通过sendMessageAtFrontOfQueue发送时when=0)
            // 立即执行或比queue中的头还早,就将当前msg置为头节点
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; // 是否是阻塞状态,mBlocked是在next方法中赋的值
        } else {
           ... // 异步消息暂不考虑
            Message prev;
            // 遍历消息根据when大小进行消息排序入队
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                ...
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) { 
            // 当需要唤醒时调用nativeWake方法唤醒
            // nativeWake会唤醒next方法中的nativePollOnce执行
            nativeWake(mPtr);
        }
    }
    return true;
}
  • 上面分析了消息的发送入队列以及消息的轮询过程,接着分析拿到消息进行回调处理的过程
  • 在2.2中looper.loop中取出消息最终调用的是msg.target.dispatchMessage(msg);
  • msg.target实际上就是指与msg绑定的handler,在2.4 handler.enqueueMessage方法可以追溯到

2.6 handler.dispatchMessage

  • 这是一个消息的处理分发的方法
public void dispatchMessage(@NonNull Message msg) {
    // 当msg绑定有callback时会优先执行
    // callback是message的一个成员变量,类型为Runnable
    // 虽然跟Thread线程的Runnable是同一个接口,但这里跟线程并没有半毛钱的关系
    // 你只需要知道callback的绑定是在handler.post方式发送消息中去绑定的
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 这个mCallBack是一个Handler的内部接口,只有一个handleMessage的抽象方法
        // 是在Handler的构造器中去赋值的,具体见3
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
             // 当callback的实现类中重写的handleMessage返回true时,代码直接return
             // 不再执行后一个handleMessage
                return;
            }
        }
        // 这个handleMessage跟上面的不一样,她只是一个没返回值的普通方法
        // 通常写一个handler的静态内部类,重写实现这个方法就可以了
        handleMessage(msg);
    }
}

3. Handler构造器

@Deprecated
public Handler() {
    this(null, false);
}

@Deprecated
public Handler(@Nullable Callback callback) {
    this(callback, false);
}

public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}

public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}

@UnsupportedAppUsage
public Handler(boolean async) {
    this(null, async);
}

public Handler(@Nullable Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
       // 这就是在子线程中使用Handler但没调用Looper.prepare创建Looper对象而抛异常的原因
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    // mCallBack就是在这里赋值的,最终会在dispatchMessage中生效
    mCallback = callback;
    mAsynchronous = async;
}

@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

4. Message的结构与创建

  • message主要包含几个成员变量,包括常用到的存储数据的whatarg1arg2obj
  • message本身包含了Handler类型的成员变量target,以及一个Runnable类型的callback
  • Message本身还是一个单链表结构,next表示下一个节点,spool表示缓存对象的头节点,使用享元模式来缓存之前用到的msg,达到重复利用避免频繁GC;缓存的对象最多为50
  • Message的创建方式主要有直接new无参构造,但更建议是Message.obtain()

4.1 Message.obtain()

// 使用obtain获取Message可以有效地避免创建新对象
public static Message obtain() {
    synchronized (sPoolSync) {
        // 当缓存对象池中存在sPool缓存对象,会使用该对象
        if (sPool != null) {
            // 缓存对象池是以链表的形式存在的,spool表示链表头
            Message m = sPool;
            // 拿出spool引用的对象后,将下一个节点置为新的头节点spool
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--; // 缓存池缓存的Message数量-1
            return m;
        }
    }
    // 只有缓存对象池中没有对象才会new新对象
    return new Message();
}
  • 而spool则是在recycleUnchecked方法中赋值的,这个方法是在2.2 Looper.loop中消息处理完毕后进行调用的

4.2 message.recycleUnchecked

// 主要做Message对象的回收,在Looper.loop中消息处理完毕后进行调用
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    // 指空方便回收对象
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        // 当缓存对象池未满(< 50)时将当前需要回收的message添加到缓存池中,而不是指空
        // 使得下次使用时可以直接使用无需重新创建,也避免了频繁回收,典型的享元模式
        if (sPoolSize < MAX_POOL_SIZE) {
            // 将新回收的msg添加到链表的头部,并将spool指向新的头节点
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

5. 总结

  • 针对开头提出的问题进行逐一解答

5.1 Handler消息机制原理

  • Handler主要用来进行Android线程间的通信的,常用来利用子线程与主线程间的通信来解决Android无法在子线程更新UI的问题
  • Handler消息机制主要是由Looper、MessageQueue、Handler以及Message四个对象协同完成的
  • 消息发送:主要是通过handler.sendMessage/handler.post方法发送的,而sendMessage/post本质上是调用的sendMessageAtTime,将携带了执行绝对时间when的Message最终通过messageQueue.enqueueMessage方法执行Message入队,messageQueue维护了一个按时间先后顺序的Message优先级队列,故在执行Message入队时会根据执行时间将Message插入到合适的位置.
  • 消息获取:当主线程ActivityThread类的main方法执行时会执行Looper.prepareMainLooper进行主线程中Looper的初始化,Looper构造器中也会对MessageQueue进行初始化操作;main方法中的倒数第二行还会执行Looper.loop开启消息的轮询,loop方法会通过queue.next获取MessageQueue中存放的消息,消息需要立即执行时取出消息,否则阻塞在nativePollOnce方法中,取出消息后调用msg.target.dispatchMessage,将message交给发对应handler中的dispatchMessage方法进行处理
  • 消息处理:handler中的dispatchMessage中会先判断message是否绑定有Runable类型的callback,有就回调run方法,没有的话再看是否绑定有CallBack类型的参数,有便回调CallBack接口的实现方法handleMessage,实现方法只有返回false时,才会继续调用本身的handler本身的handleMessage普通方法。

5.2 一个线程有几个Looper,如何保证?

  • 一个线程只有一个Looper,首先Looper的构造器是私有的,你无法通过new的方式创建,Looper只提供了一个prepare方法构造对象(prepareMainLooper实质上也是调用的prepare),而prepare方法是将Looper对象保存在ThreadLocal中,利用ThreadLocal来保证线程与Looper间一对一的关系
  • ThreadLocal内部主要是维护了一个ThreadLocalMap的key-value型内部类,调用set时将key设置为当前线程的ThreadLocal对象(ThreadLocalMap是Thread类的一个成员变量一对一的关系),value为Object类型的值
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else  
        createMap(t, value);
}

// 将ThreadLocalMap赋值给Thread类的threadLocals成员变量达到绑定的目的
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}


/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
// ThreadLocalMap为Thread类的的一个成员变量
ThreadLocal.ThreadLocalMap threadLocals = null;

5.3 主线程中Looper.loop方法是死循环为什么不会造成卡死(ANR)

  • ActivityThread的main方法中正是由于Looper.loop死循环才不会导致程序抛异常退出
  • 主线程卡死ANR并不代表主线程阻塞而是主线程的消息循环出现异常,需要马上响应处理的消息未及时处理才会导致卡死ANR
  • 当没有需要马上处理的消息时,Looper.loop方法中的queue.next会进入一个阻塞的Native方法nativePollOnce,此时主线程会释放CPU资源进入休眠直到下一个需要处理的消息的到达,所以也并不会消耗很多的CPU资源
  • nativePollOnce不阻塞的原因是epoll机制,他是linux里面的,在native层会有一个读取端和一个写入端,当有消息发送过来的时候会去唤醒读取端,然后进行消息发送与处理,没消息的时候是处于休眠状态。

5.4 子线程中使用Handler需要注意的问题

  • 主线程本身是创建了Looper并开启了消息循环Looper.loop的,但子线程并没有,所以必须在子线程中手动的调用Looper.prepare()来创建子线程中的Looper,以及需要调用Looper.loop()开启子线程中的消息循环
  • 子线程任务执行完毕之后记得调用looper.quit()方法,否则该子线程将由于looper.loop阻塞线程的原因导致子线程即使执行完毕也不会退出
  • 不建议自己在子线程中这样干,容易出错也比较麻烦,可以直接使用官方封装好的HandlerThread

5.5 子线程中维护的Looper,消息队列无消息时的处理方案

  • 子线程中消息队列无消息时,会一直阻塞在nativePollOnce中而无法终止线程;
  • 如没有消息,可调用Looper.quit(内部调用messageQueue.quit将消息回收,并执行nativeWake方法唤醒nativePollOnce退出循环)释放线程。
  • 主线程调用quit会抛异常"Main thread not allowed to quit"
// Looper.quit
public void quit() {
    mQueue.quit(false);
}

// messageQueue.quit
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.
        // 可以唤醒nativePollOnce往下运行的另一个Native方法
        nativeWake(mPtr);
    }
}

5.6 为什么建议使用Message.obtain()来创建Message

  • Message维护了一个单链表结构的缓存对象池(缓存最大数为50),用于缓存之前创建但现在已使用完毕的Message对象,而不是直接置空回收,避免了Message对象的频繁创建与回收。
  • spool指向的是Message缓存对象池链表的头节点,每次调用obtain会先判断spool是否为空,即是否已存在缓存对象,为空则使用new的方式构造新对象,不为空就直接复用该缓存头节点sPool,避免重复创建Message对象 (可查看上面的4.1和4.2)

5.7 Handler内存泄露场景、原因分析、如何避免

  • 场景1:当发送的是延迟消息,在延迟消息未处理之前关闭了Activity
    • 此时Handler作为Activity的非静态内部类持有了Activity的引用,而Handler有完整的到主线程的应用链Activity<—Handler<—Message(target)<—MessageQueue<—Looper<—ThreadLocal<—ActivityThread(主线程),由于主线程不会被回收就会导致Activity不会被回收从而导致内存泄漏
  • 场景2:子线程中使用了Handler时,没有调用quit,子线程无法结束或子线程未执行完毕
    • 子线程作为非静态内部类持有了外部类Activity对象的引用,而运行中的子线程不会被回收就会导致Activity不会被回收从而导致内存泄漏
  • 避免方式1:使用静态内部类弱引用
    • 单独使用静态内部类就使得Handler无法更新Activity中的非静态视图对象,从而无法更新UI,所以一般会配合弱引用一起使用
  MyHandler(WeakReference(this)).sendEmptyMessageDelayed(0, 20000) 
  //kotlin中内部类默认为静态内部类 
  class MyHandler(var mActivity: WeakReference<HandlerActivity>):Handler(){ 
    override fun handleMessage(msg: Message?){ 
        super.handleMessage(msg) 
        mActivity.get()?.changeBtn() 
            } 
   }
  • 避免方式2:在Activity的onDestroy中移除未来得及处理的延迟消息,调用handler.removeCallbacksAndMessages(null)取消handler对message的监听(本质上是message.recycleUnchecked,将msg.target置null,见4.2)防止内存泄漏