Handler(一)源码解析

前言

Android Handler基本是面试必问的知识点,也是了解Android源码的第一步,那么Handler到底是什么呢?接下来我们一探究竟。

Handler基础

Handler主要涉及4个类,分别是Handler、Looper、Message、MessageQueue。

Handler
Looper
Message
MessageQueue

Handler发送消息-处理消息流程

那么它们是如何相互作用,来实现Handler的功能的呢?

Handler发送消息,处理消息流程

1、通过调用sendMessage、post等方法,会最终调用到Handler的enqueueMessage方法,此方法会向MessageQueue的mMessages中新增一个节点。


发送消息

2、当线程中Looper.loop调用时,会启动一个死循环,轮询获取Message信息。

public static void loop() {
    ········
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // might block
        ········
        msg.target.dispatchMessage(msg);
    }
    ········
}

3、当获取到Message消息后,通过msg.target.dispatchMessage(msg)方法,回调给Handler。

初始化流程

初始化流程

1、Handler初始化的时候,需要传入Looper,或者通过Looper.myLooper()获取当前线程的Looper;并得到looper.mQueue对象。

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

public Handler(@Nullable Callback callback, boolean async) {
    ........
    mLooper = Looper.myLooper();
    mQueue = mLooper.mQueue;
    ........
}

2、Looper初始化的时候,会创建MessageQueue对象。

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

3、在Handler发送消息时,会将handler赋值给Message.target

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

Handler为什么会内存泄露

由此,我们可以看到如下的持有链,当长生命周期的对象(Thread)持有短生命周期的对象(真正处理回调的类,如Activity),内存泄漏便发生了。

Handler的持有链

Handler常见问题

1、Message可以如何创建,哪种效果更好?

message可以通过new Message()、Message.obtain()、handler.obtainMessage()创建,用后两种方式创建效果比较好。

Message回收池

在Message中,通过单向链表构建了Message的缓存池,防止对象频繁创建、销毁造成内存抖动。

public static final Object sPoolSync = new Object();// 锁
private static Message sPool;// 缓存池
private static int sPoolSize = 0;// 缓存的个数
private static final int MAX_POOL_SIZE = 50;// 最大缓存个数

// 通过缓存池获取Message对象,若缓存池为null,则创建新的Message
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();
}

// 回收Message对象
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recy
    // 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) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}


2、多个Handler往MessageQueue中添加数据(发消息时各个Handler处于不同线程),内部如何保证安全的?

我们翻看MessageQueue的源码,即可发现,在向MessageQueue的mMessages添加数据时,进行了加锁处理。

boolean enqueueMessage(Message msg, long when) {
    ········
    synchronized (this) {
    ········
    }
    return true;
}


3、Looper.loop为什么不会导致主线程卡死

app启动流程

当App启动时,Zygote进程会fork新的进程,然后通过ActivityThread的main方法进入应用程序。在main方法中,会启动Looper.loop,而在loop之前,会通过thread.attach绑定ActivityManagerService。当系统有事件响应时,便会通知ActivityThread的mH进行处理。

public static void main(String[] args) {
    ········
    Looper.prepareMainLooper();
    ········
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    ········
    Looper.loop();
}

// 用于处理AMS响应的事件
class H extends Handler {
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;
    ········
}

4、一个线程有几个Looper?可以有几个Handler?

一个线程只能有一个Looper,Looper.loop的时候,会形成死循环,此时即使有多个Looper也没有意义。那是怎么保证Looper的关系呢?通过ThreadLocal保证Looper和Thread的关系。

而一个线程可以有很多个Handler,只要在初始化的时候得到线程的Looper即可。

// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}

/**
 * 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() {
    return sThreadLocal.get();
}

5、Handler.postDelay后,消息队列会有什么变化?

根据Handler消息发送流程,我们知道,无论通过何种方法调用,最终都会调用到enqueueMessage方法。
观察一下源码,参数中有一个uptimeMillis参数;它是通过SystemClock.uptimeMillis() + delayMillis计算出来的。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 调用MessageQueue的enqueueMessage
    return queue.enqueueMessage(msg, uptimeMillis);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    // 计算更新时间
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

当把消息插入队列时:
1、如果消息队列为null 或 更新时间是0 或 当前消息更新时间<队头元素的更新时间,则将消息添加至队头,并设置唤醒标识。
2、否则遍历消息队列中的元素,按时间顺序将消息插入队列中,这种情况不需要唤醒。
3、如果最终唤醒标识为true,会进行唤醒逻辑处理。

boolean enqueueMessage(Message msg, long when) {
    ········
    synchronized (this) {
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        // 如果消息队列为null 或 更新时间是0 或 当前消息更新时间<队头元素的更新时间
        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 
            // up the event queue unless there is a barrier at the head of the que
            // 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.
        // 唤醒,Message msg = queue.next()停止阻塞
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345