Android消息机制(2)----工作原理

ThreadLocal的工作原理

​ ThreadLocal不是一个线程,他是线程内部的一个数据存储类,ThreadLocal中的数据就是以线程为作用域的,在不同的线程中取到的都是自己线程中的数据,而不是同一个数据的副本,在多线程环境下,应用ThreadLocal就可以防止自己的变量被其他线程篡改。比如对于Handler来说,他想获取当前线程的Looper,我们知道Looper的作用域是线程并且不同的线程具有不同的Looper,这时我们就要用ThreadLocal来存取Looper。

​ 如果不采用ThreadLocal存储可以吗?答案当然是可以,如果不应用ThreadLocal,那么系统就必须提供一个类似于LooperManager的类,提供一个全局的哈希表来查找指定线程的Looper。

ThreadLocal的简单实现

直接上代码:

  1. 定义一个ThreadLocal,选择为Boolean类型。
  2. 在主线程,子线程1和子线程2中设置和获取ThreadLocal的值。
  3. 打印结果。
public class MainActivity extends AppCompatActivity {

    private ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        threadLocal.set(true);
        Log.d("Thread#main","="+threadLocal.get());

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(false);
                Log.d("Thread#1","="+threadLocal.get());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("Thread#2","="+threadLocal.get());
            }
        }).start();
    }

}

打印结果:

在这里插入图片描述

  • 上述代码的实现结果证明了ThreadLocal的特别之处,我们在不同线程中访问同一个ThreadLocal对象,在不同线程中访问的ThreadLocal的值是不同的。从代码里看,在不同线程中访问的是同一个ThreadLocal的get方法,那么为什么访问出的值不同呢?因为ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。不同线程中的数组是不同的,这就是为什么ThreadLocal可以在不同的线程中维护一套互不干扰的数据的副本。

ThreadLocal的内部实现

  • ThreadLocal是一个泛型类,定义为public class ThreadLocal,对于ThreadLocal,我们主要用到它的get和set方法,了解了这两个方法的底层实现,就可以明白ThreadLocal的工作原理。

在ThreadLocal中有一个内部类ThreadLocalMap,ThreadLocal的操作和ThreadLocalMap息息相关,所以想学好ThreadLocal,就一定要了解什么是ThreadLocalMap。

ThreadLocalMap

在ThreadLocal的get和set方法中,有一个东西叫做ThreadLocalMap,想了解get和set方法,就要先了解ThreadLocalMap。ThreadLocalMap是ThreadLocal的内部类,这个map是由ThreadLocal维护。Map里面存储线程本地对象。ThreadLocal(key)和线程的变量副本(value)。ThreadLocal负责向map获取和设置线程的变量值。ThreadLocalMap里面有一个Entry类型的数组,用来存每个Entry,而Entry是什么呢?它又是ThreadLocalMap里面的一个静态内部类,它通过自己的构造函数将ThreadLocal和数据按照键值对的形式存下来,至于Entry在数组中如何存储,是根据ThreadLocal的哈希值与数组长度-1进行与运算,得到 i 值,i 就是数组的下标。我们先看一下ThreadLocalMap的set方法:

private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            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);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

ThreadLocalMap的set方法可能会有的情况:

  • 探测过程中顺利找到key所在的数组位置,直接替换即可
  • 探测过程中发现key为null,调用replaceStaleEntry(替换过期数据的逻辑),效果是最终一定会把key和value放在这个数组中。
    • 在replaceStaleEntry过程中,如果找到了key,则做一个swap把它放到那个数组中,value置为新值
    • 在replaceStaleEntry过程中,没有找到key,直接在原地放entry
  • 探测没有发现key,则在连续段末尾的后一个空位置放上entry,这也是线性探测法的一部分。
  • 放完后,调用cleanSomeSlots()做一次启发式清理工作,清理散列数组中Entrykey过期的数据,如果清理工作完成后,未清理到任何数据,且size超过了阈值(数组长度的2/3),进行rehash()操作,rehash()中会先进行一轮探测式清理,清理过期key,清理完成后如果size >= threshold - threshold / 4,就会执行真正的扩容逻辑。

ThreadLocal的set()方法

代码:

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
  • 如果map不为空则直接传入ThreadLocal和要存储的值
  • map为空则创建map

ThreadLocal的get()方法

代码:

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();
    }
  • 以当前线程为key得到map中的Entry
  • 如果map为null,或者获取到的entry为null,则调用setInitialValue()
private T setInitialValue() {
        T value = initialValue(); // null
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value); //如果当前线程的 map 已经存在,set 值为 null
        else
            createMap(t, value); //否则创建 map
        return value;
    }

消息队列的工作原理

  • 消息队列指的是Android中的MessageQueue,MessageQueue主要包含两个操作:插入和读取。读取本身也伴随着删除操作,插入和读取对应的方法分别是enqueueMessage()和next()。其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其移除。

注意:尽管MessageQueue叫做消息队列,但是他实际上是通过一个单链表的数据结构来维护消息列表的,因为单链表在插入和删除上比较有优势。

enqueueMessage方法

MassageQueue中的逻辑大概分为以下几步:

  1. 判断发送此消息的Handler是否为空
  2. 判断此消息是否正在被使用
  3. 判断此消息队列是否已经被放弃
  4. 获取此消息的延迟时间,也就是从系统开始的时间到调用这个方法的毫秒数 + delayMillis。(when字段)
  5. 如果非延时消息或者消息队列为空,则讲此消息插到链表头部,立即唤醒Looper进行获取,使Looper线程不再处于阻塞状态。
  6. 如果是延时消息,则根据其when字段的延时时长来进行在链表中的插入,时间长的放在后面。

关键代码如下:

boolean enqueueMessage(Message msg, long when) {
    	if (msg.target == null) {				//msg.target就是发送此消息的Handler
            throw new IllegalArgumentException("Message must have a target.");
        }
    	if (msg.isInUse()) {					//表示此消息正在被使用
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        synchronized (this) {
            if (mQuitting) {					//表示此消息队列已经被放弃了
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
            msg.markInUse();
            msg.when = when;						//将延迟时间封装到msg内部
            Message p = mMessages;					//消息队列的第一个元素
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //如果此队列中头部元素是null,此消息不是延时的消息,则此消息需要被立即处理,
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;		//如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理
            } else {	
                //如果此消息是延时的消息,那么会根据它的延迟时间来进行链表的插入操作,延迟时间越长越靠后。插入延时消息不用唤醒Looper线程。
                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;
                prev.next = msg;
            }
            if (needWake) {					//这就是唤醒线程的部分
                nativeWake(mPtr);
            }
        }
        return true;
    }

next()方法

MassageQueue的next()方法逻辑主要如下:

  • 当首次进入消息队列中读取或者所有消息队列都已经处理完成,由于此时消息队列中没有消息,mMessage为null,这是设置nextPollTimeoutMillis参数为1,既让线程阻塞,直到被主动唤醒。
  • 当读取消息列表中的消息时,如果发现消息屏障(及发送消息的Handler为null,通过postSyncBarrier方法发送来的),那么会跳过后面的同步消息,找到异步消息去执行。
  • 如果拿到的消息它的延迟时间还没有到,则存储它的延迟时间放入nextPollTimeoutMillis,线程此时会阻塞,当到达时间后会自动唤醒线程。
  • 如果读取的消息是及时消息,或者延迟时间到达了,则会返回此消息给Looper处理。

主要代码如下:

Message next() {
    
        final long ptr = mPtr;
        if (ptr == 0) {
           //从注释可以看出,只有looper被放弃的时候(调用了quit方法)才返回null,mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
           //如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
           //如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
           //如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
            nativePollOnce(ptr, nextPollTimeoutMillis);		//阻塞方法
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)
                    //如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 消息此刻还没有到时间,设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会进行阻塞;
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;			//设置mBlocked = false代表目前没有阻塞
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //没有消息,会一直阻塞,直到被唤醒
                    nextPollTimeoutMillis = -1;
                }

                if (mQuitting) {
                    dispose();
                    return null;
                }
				......
            }
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }
  • 从以上的分析以及源码的呈现我们可以发现,next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,会唤醒线程,并且在合适的时机将这条消息从单链表中移除并且返回给Looper。

Looper的工作原理

  • Looper在Android的消息机制中扮演一个消息循环的角色,顾名思义就是它负责不停的查看MessageQueue中是否有新消息,如果有新消息就会立即处理,否则就一直阻塞在那里。用一句话来说就是不断的调用 MessageQueue 的 next() 方法取出消息并交给 Handler 处理。

Looper的构造方法:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
  • 在Looper的构造方法中,新建了一个MessageQueue消息队列,然后将当前线程对象保存起来。

Looper的创建方法

其实很简单,我们通过Looper的prepare()方法就可以为当前线程创建一个Looper,下面是Looper类中的几个prepare方法的重载还有prepareMainLooper方法:

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));
}
@Deprecated
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
  • 由以上代码我们可以发现,Looper的prepare方法会创建一个新的Looper对象,并放入全局的sThreadLocal中。
  • Looper类还提供了一个prepareMainLooper方法,来让开发者在主线程ActivityThread中创建Looper,其本质也是prepare方法。
  • 由于主线程的Looper有些特殊,所以还提供了getMainLooper,通过这个方法,我们可以在任何地方来获取到主线程的Looper。

Looper的退出

Looper也是可以退出的,Looper提供了quit和quitSafely来退出一个Looper。这两个方法的区别是:quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕才会安全退出。Looper退出后,Handler发送的消息会失败,其send方法会返回false。

Looper的loop()方法

Looper最重要的方法就是loop()方法,获取完Looper的实例之后就要开启循环队列监听并接受&处理消息,也就是执行Looper.loop()方法。

下面就是loop()方法的主要实现:

public static void loop() {
    //获取当前线程的Looper对象实例
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取Looper实例的中的消息队列
    final MessageQueue queue = me.mQueue;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    ......
    //开启循环队列-不断
    for (;;) {
        //从队列中取出消息,有可能产生阻塞
        Message msg = queue.next(); 
        //如果获取不到消息那么退出
        if (msg == null) {
            return;
        }
        ......
        //将消息分发给注册它的handler
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ......
        msg.recycleUnchecked();
    }
}
  • loop()方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null,也就是说Loop必须退出。如果消息队列的next方法阻塞,那么Loop的loop()方法就会阻塞。
  • 如果得到了消息,Looper就会处理,msg.target.dispatchMessage(msg),调用Handler的dispatchMessage来处理。Handler的diapatchMessage方法是在创建Handler所使用的Looper中执行的,这样一来就成功的将代码逻辑切换到指定的线程中去了。

Handler的工作原理

Handler的作用是投递消息和处理消息的,它会绑定一个Looper,一个线程可以有多个 Handler,但只会有一个Looper。

Handler的工作主要是发送和接收消息,也即sendMessagedispatchMessage两个函数。在sendMessage是将消息队列中插入一条消息,即调用enqueMessageMessageQueue收到消息之后调用next()将消息传给Looper处理,然后Looper又将消息丢给该Handler来处理,也就是dispatchMessage

Handler对象的创建

  1. 可以创建Handler派生一个Handler的子类并重写其handleMessage方法来处理具体的消息。

  2. 如果不想派生子类,可以通过CallBack创建Handler对象:Handler handler = new Handler(callback),然后调用Handler的handleMessage方法来处理信息。

  3. 通过一个特定的Looper来构造Handler。

public Handler(Looper looper){
    this(looper,null,false);
}
  1. Handler的默认构造方法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()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    通过 Looper.myLooper() 绑定looper,通过 mLooper 间接绑定MessageQueue。

Handler的发送消息

Handler的发送消息时sendMessage方法,经过重载调用sendMessageDelayed,然后调用sendMessageAtTime,然后调用enqueueMessage将消息放到消息队列中。

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull 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);
}

Handler的接收消息

在Looper中,获取到消息之后会调用Handler的dispatchMessage方法来处理

dispatchMessage方法的实现如下:

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

其中callback是一个接口,是用来实例化Handler时候的一个Runnable对象。

public interface Callback {
    public boolean handleMessage(Message msg);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值