【Android源码分析】Threadlocal与looper+handler

xiaoxiao2021-02-28  43

前言

为什么会有这么一篇网上有很多种解说版本的博客?因为我看懂了很多次,都没有把自己的想法记下来,然后就忘了。那样不仅浪费时间、而且还有点伤积极性。

从一个异常出发开始

在《第一行代码》中看到了关于异步处理消息的用法时,有没有想过可以在子线程中去new一个Handler?现在就开始着手,从一个子线程中去new一个Handler,看看会有什么发生。

new Thread(new Runnable() { @Override public void run() { new Handler(); } }).start();

结果就出现了RuntimeException异常,仔细看它的信息说明。

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

那么可知,在每个线程new Handler()时,都必须先调用Looper.prepare()或者调用一个能够达到相同效果的函数。那么在主线程中可以new Handler()的原因,想必就是已经调用过了。以下代码位于AcitivityThread.java中,是一段初始化主线程的内容。

Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging( new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop();

其它的都忽略,就看Looper相关的。Looper.prepareMainLooper()想必是达到了相同的效果吧。那么,这个效果到底是什么呢?让我们慢慢拨开云雾。

寻找那个异常

于是,我们很自然地在子线程中加入了Looper.prepare(),并随手按着Ctrl,左键点击鼠标,进入了prepare()函数中。

/* Initialize the current thread as a looper. This gives you a chance to create handlers that then reference this looper, before actually starting the loop. Be sure to call {@link #loop()} after calling this method, and end it by calling{@link #quit()}. */ 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)); } ... private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }

当初的那个异常不就在眼前?但是,这sThreadLocal又是什么?它是什么暂时抛开,这时我们知道了我们的线程中已经有了一个Looper,并且为这个Looper设置好了一个MessageQueue。因为一个线程只能有一个Looper,所以一个Looper也就只能拥有一个MessageQueue。但是AcitivityThread中,经过Looper.loop()后就再也没有下文了?所以,这个loop()又是干啥的呢?

繁忙的loop()

/** 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(); } /** * 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) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; ... for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ... try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } ... msg.recycleUnchecked(); } }

因为其中一个大大的死循环,所以调用了loop()之后,其后就没有实际代码了。这个死循环就是用来处理Message,不断地从队列中取,然后不断地进行分发到相应的Handler,进行处理。此时,这个for(;;)所处的线程,就是你调用Looper.loop()时所在的线程。因此,它分发msg给了相应的Handler的handleMessage之后,还是在此线程中执行。然后,在想想,发送Message时所处在的线程,就焕然大悟这个异步操作了。

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

handleMessage(msg)不正是我们创建Handler时候,所覆盖的方法吗?

进一步思考,如果我只在主线程中new Handler,那么Looper就是主线程,所有的msg都会在主线程中被处理;那如果我想让msg在子线程中被处理呢?当然可以Looper.prepare()巴拉巴拉,然后Looper.loop()。但是Android还为我们提供了一个更为便捷的封装。那就是HandlerThread。

子线程处理msg的封装HandlerThread

@Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }

源代码比较短。它继承自Thread,并在run方法中初始化好了Looper,可以通过其getThreadHandler()方法,获取到与该Looper所绑定的Handler,然后sendMessage(),最后在该线程中处理msg。

小结

因此,一个Thread可以有一个Looper和一个MessageQueue,一个Looper却可以与多个Handler绑定,但是一个Handler只能与一个Looper绑定。原因可以从Handler的构造方法中寻找的。

ThreadLocal是什么

回过头,想想ThreadLocal实现了什么样的功能。举个例子,当不同的线程都去执行同样一个语句以获得当前线程的Looper时,要怎么实现?或许吧,ThreadLocal就实现了这样一个功能。

在Looper中,申明了一个如下的静态变量,说明只有一个。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

然后在Looper中调用myLooper()时,都执行了

public static @Nullable Looper myLooper() { return sThreadLocal.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(); }

在每个Thread中,有个ThreadLocal.ThreadLocalMap。获取到当前线程的map之后,又将sThreadLocal自身作为一个Key,去获取到相应的结果。再看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); }

相同的套路,同样是先获取到当前线程的map,然后用ThreadLocal自身作为Key,存进去。

小结

因此对于ThreadLocal,可以这样理解:它自身就是一个Key,然后可以以这个Key在不同的线程中存储/获取一个值,值得类型就是声明时,尖括号里面的类型。

转载请注明原文地址: https://www.6miu.com/read-1599997.html

最新回复(0)