关于Handlerd的一些解析

Handler 机制

Q1、Handler的作用:在源码的解释中,handler有两个作用。第一个,是在未来的某一个时间,执行一个任务。第二,在不同的线程之间执行action,简而言之,就是线程间通信。

(1) to schedule messages and
* runnables to be executed at some point in the future; and (2) to enqueue
* an action to be performed on a different thread than your own.

Q2、Handler send出去的message 或者post出去的runnable,最后都去了哪里?
在handler层,不管是sendMessage还是post,最终都走到了hadler的enqueueMsg方法里面。然后再由MessageQueue执行enqueueMessage方法。把它加入到消息队列中。(send 和post有一点小区别,就是post执行后,handler会通过msg.obtain给它裹上一层msg。)
Q3:dispatchMessage和handleMessage的区别:两者并没有本质上的关联,调用上存在先后顺序。B在A的里面,如果是直接post,被msg包装上以后,会调用到dispatchMessage -> handleCallback(msg),这个handleCallback,其实就是调用的runnable的run方法,从这个角度讲,跨线程来说,postRunnable方便一些。如果是handler的sendmsg,一般来说,handler这时候的mCallback是空的(怎么不为空?构造里传进去),最后会走到handleMessage(msg);如果重写handler,通过super.dispatchMessage,或者直接在重写的handleMessage里。效果是一样的。

/**
 * Handle system messages here.
 */
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

Q4:为什么叫做message队列,以及在enqueueMsg的时候,做了什么?-》消息队列
1、通过msg.when来确定msg放在链表的哪个位置。

//死循环遍历,一直到排好序为止,才break 
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;

2、为什么叫做队列:在message里面,还有一个next的对象,也是message,当存在后续的message进来是,当前的message就会把这个next指向它,由此形成一个链表。并且在入队的时候,会根据执行时间排序。
3、MessageQueue在enqueueMessage的时候,做了什么?
首先会加速,避免多个线程同时操作MessageQueue,然后判断当前链表是否为空,也就是mMessage是否为null,如果是null,就把当前enqueue的Msg当做表头,如果链表不为空,通过msg的when,也就是msg的执行时间来排序,把msg插入到链表当中。
Looper:
1: Looper的构造方法里做了什么?
looper的构造方法是私有的,外部无法new,这样的好处是,可以让Looper自己来管理相关数据,并且保证每个线程的Looper是唯一的。

Looper的创建:Looper.prepare(true)
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
然后交由当前线程的sThreadLocal保存。

怎么保证Looper是线程唯一:

//如果当前线程里的sThreadLocal已经存了Looper,就会报错,如果没有,就会创建一个Looper,并且保存在Tl中。
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));
}

ThreadLocal是怎么保存Looper,并且保证它是唯一的?
ThreadLocal:线程隔离的存储工具类。
保存数据的并不是ThreadLocal,而是它的静态内部类ThreadLocalMap,ThreadLocalMap里有一组构造函数为Key/Value结构的Entey数组。其中key就是当前ThreadLocal本身(弱引用),value要存的值。
当ThreadLocal要set值时,会先获得当前线程的ThreadLocalMap,然后调用map的set方法,最后走到ThreadLocalMap的Entey数组里面,如有没有这个值,就加一个,如果有,就替换掉。set步骤如下:

public void set(T value) {
	//先获得当前线程
    Thread t = Thread.currentThread();
    //然后获得当前线程的ThreadLocalMap,一个线程只有一个ThreadLocalMap,这个map对象是在线程初始化的时候就声明了(创建还是在ThreadLocal里面创建的)。
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
    //如果是null,就创建一个ThreadLocalMap。
        createMap(t, value);
}

一个Thread怎么存储多个线程变量副本:一个线程里只有一个ThreadLocalMap,但是却可以存在多个ThreadLocal,因此可以通过生成不同的ThreadLocal作为key,来保存value。
ThreadLocal怎么保证唯一:因为它是key/value结构的,一个它的key就是自己本身,并且在set value的时候,进行了判断,如果存在就替换,不存在就添加到TLMap的tab里面去。所以当Looper.prepare调用以后,ThreadLocal会以自身作为key,把Looper当做value存起来,下次再取的时候不为空,就是抛出异常。
Q:MessageQueue是属于哪个线程的?
queue是在Looper线程创建的,但是并不是只属于这个线程的,它是一块内存,并且是可以通过Looper.myLooper().getQueue()或者这个队列来操作的,因此并不专属于某个线程。
Q:一个线程可以有几个Handler:可以有N个,Handler的作用的发送和处理消息,在线程中并不限制Handler的数量,当时一个线程里,只有一个Looper,通过prepare创建,通过Looper.myLooper得到,通过Looper.loop开启循环。
Q:Handler造成的内存泄漏:
如果才去匿名内部类Handler的形式去收发消息,因为匿名内部类默认持有外部内的引用的,如果在acty里,就持有acty的引用。加入handler发送了一个消息出去,十分钟以后执行,这时候它是作为msg的target被持有的,存在引用关系,因此handler持有的acty也被引用着,无法回收,这时候就会造成内存泄漏。为什么RecyclerView的adapter持有vh的引用却不会呢?因为生命周期一致。
Handler内存泄漏的解决办法:不使用匿名内部类,才去弱引用式的handler,或者考虑HandlerThread。
Handler在发送消息的时候,最后走向enqueueMessage方法,这是msg.target = this,因此handler的引用被msg所持有,但是msg的生命周期和handler不一定是同步的,可能是很久以后执行,因此handler不能被及时回收,而这时如果handler持有了外界的引用,会导致其他的对象也不能被回收,因此造成内存泄漏。
Q:msgqueue里面的msg发送出去以后会怎么办?
1、Looper.loop(msg)以后, 创建一个死循环,走到loopOnce里面,取出msgqueue的next,然后给target去分发(dispatchMessage),最后调用msg的recycleUnchecked()方法。这里会回收。
2、msgqueue在enqueueMessage的时候,如果这时候线程已经被设置为mQuitting = true的quit状态,也会回收msg。
这个回收是怎么样的呢:是一种享元模式的操作,系统并不会把它置为null,而是把它的各项属性都设置为默认值,如what,arg1,arg2等。然后保存起来(spool) 最多可以存50个。recyclerView里面的缓存机制,缓存的viewholder,有一层也是这种模式,把viewholder里的各项数据都擦除,然后重新赋值来复用。

synchronized (sPoolSync) {
    if (sPoolSize < MAX_POOL_SIZE ) { //最大是50
        next = sPool;
        sPool = this;
        sPoolSize++;
    }
}

Message是怎么对外提供msg的,也就是说,怎么取msg?
通过Msg.obtain来取。(ps:msg的构造方法是公开的,也可以直接new,但是这样就不能复用以前的,系统是建议通过msg.obatin()来取)

public static Message obtain() {
    synchronized (sPoolSync) {
    //先看sPool存不存在,如果存在,就直接取
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    //如果不存在,就new
    return new Message();
}

Q:子线程new Handler需要做什么操作?
需要先prepare,创建Looper,然后通过looper.loop开启循环。
Q:为什么主线程可以直接用呢?
整个程序的所有操作,都是以looper.loop的方式来发送消息的,App通过launcher启动以后,首先启动ActivityThread方法,然后再ActivityThread的main方法里面,创造mainlooper(私有静态的,因此伴随着整个应用程序的生命周期存活)
Q:子线程的MessageQueue里,没有了消息,开发者应该怎么办?
调用Looper.myLoop.quit(),将looper从消息循环中打断,将所有消息设置为null,最后再dispose。去nativeDestroy。
Q:如果Message里面的消息还没到时间执行,或者Messagequeue是空的时候,要怎么办?
这里要引入两个本地方法:

//休眠
private native void nativePollOnce(long ptr, int timeoutMillis); 
//唤醒
private native static void nativeWake(long ptr);

情况1:存在消息但是还没有到执行时间,在queue的next方法里面,会计算出下一个msg的轮询时间,msg.when - now,然后再调用nativePollOnce去阻塞等待。直到时间结束后再重新唤醒。
情况2:加入没有消息了呢?这时候的timeoutMillis会是-1,让当前线程一直处于阻塞状态,一直到有新的消息进来。那么新的消息进来以后,怎么唤醒的呢?在enqueueMessage的时候,会进行nativeWake的操作。
Q:多个handler往messagequeue里面塞信息时,怎么保证线程安全?
会进行加锁,锁的是messagequeue本身,并且在MessageQueue的next方法里面,取消息的时候(next方法)也会加锁(ps:因为取的时候可能别的handler在插入)。synchronized (this),系统内置锁,由JVM内置,系统自动完成。同时它锁的是this,不是某个对象,因此它类里面所有对象的调用都会受限。
一个线程只有一个msgqueue,同时操作的时候加了锁,因此其他handler想要操作时,就会受限。
Q:Looper死循环会不会导致应用卡死?
首先,Looper.loop并不是毫秒或者微秒级别的无限循环,它首先会从messagequeue里面去取消息,这时候如果下一个msg的执行时间还没到,或者是msgqueue里面没有消息的时候,它会调用nativePollOnce(ptr, nextPollTimeoutMillis),让looper进入休眠状态,到了时间以后,醒来执行取出msg的操作,然后等待下一次执行的时间。如果msgqueue里面是空的,它的nextPollTimeoutMillis为-1,也就是下一次取出时间为-1,会一直休眠,等待enqueueMsg的时候来唤醒。(quit的时候也会唤醒,但是quit会改变标志位的值,mQuitting = true,退出循环)
还有一种情况,这时候发送过来一个异步消息,也会马上唤醒。

Q:Handler的同步屏障?
handler的msg是发送到msgqueue里面处理的,而msgqueue是一个时间优先级的,待执行时间越短的msg排在越前面。是一个同步队列。所有消息按照顺序执行,这样形成了一个同步屏障。那么假设有一个消息,我发出去以后,需要马上执行,应该怎么办呢?
这个时候需要制造这个同步屏障,我们先来看一下,msgqueue是怎么enqueueMessage的?
1、如果池子是空的,把传进来的msg,变成当前msg,让它处于链表头部。
2、如果池子里有msg,就开始排序,把当前放入的msg,按照时间顺序,插入到合适的位置。
3、如果插入的是一个异步消息,调用nativeWake,唤醒休眠的looper。

synchronized (this) {
    if (p == null || when == 0 || when < p.when) {
        // New head, wake up the event queue if blocked.
        //如果池子是空的,把传进来的msg,变成当前msg,让它处于链表头部。
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
    } else {
        // Inserted within the middle of the queue.  Usually we don't have to wake
        // up the event queue unless there is a barrier at the head of the queue
        // and the message is the earliest asynchronous message in the queue.
        //判断当前looper是否需要唤醒,两个主要的条件,1、target为null,2、设置了异步标签。
        needWake = mBlocked && p.target == null && msg.isAsynchronous();
        Message prev;
        for (;;) {
            prev = p;
            p = p.next;
            //如果池子里有msg,就开始排序,把当前放入的msg,按照时间顺序,插入到合适的位置。
            if (p == null || when < p.when) {
            //插入的msg时间,少于排序后的当前msg的执行直接,就退出,代表排序完成。
                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.
    //如果needWake,就唤醒。enqueueMsg唤醒Looper的条件比较苛刻,必须是异步线程,而且要在队列头。(因为我也可能不是马上插队,而是排一会,再插队)
    if (needWake) {
        nativeWake(mPtr);
    }
}

再让我们来看下Looper是怎么取msg的。Message msg = me.mQueue.next() -> Message.next()
取消息的时候会发生什么呢?直接上代码:

//判断当前msg不为null,但是它的target是null,人为制造。可以把这行理解成一个信号
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());
}

怎么发出这个屏障呢?通过MessageQueue的postSyncBarrier方法,制造一个handler为null的消息,并且将它变为当前的mMessage,这样在next的去取的时候,就会触发同步屏障,找到设置标签为isAsynchronous的msg,然后将它变为当前的mMessage,开始执行。这就是所谓的同步屏障。
如何移除呢?有一个removeSyncBarrier方法,可以移除,但是不管是postSyncBarrier还是removeSyncBarrier,移除和制造,系统都是不建议使用的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值