声明:原创文章,转载请注明出处https://www.jianshu.com/p/48976893bd11
我们知道在App中一般多会有多个线程,多线程之间难免需要进行通信。在我们平时开发中线程通信用的最多的就是Handler,例如子线程进行数据处理,在主线程中进行UI更新。当然了除了Handler这种通信方式外,线程间的通信还有其他几种方式:管道Pip、共享内存、通过文件及数据库等。这里由于篇幅有限,我们主要来看下Handler以及其实现原理。
相信做过Android的朋友对Handler一定不陌生,我们先来回顾下Handler的用法:
public class MainActivity extends AppCompatActivity {
private Handler mHanlde;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHanlde = new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String){
Log.d("主线程接受到的消息:",(String)msg.obj);
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.obj = "这是从子线程发送的消息";
mHanlde.sendMessage(message);
}
}).start();
}
}
可以看到我们在主线程中建了一个Handler,又创建了一个子线程,并在子线程中利用主线程创建的Handler发送了一条消息。运行起来看下打印日志:
06-12 16:08:31.618 25807-25807/simplae.handledemo D/主线程接受到的消息:: 这是从子线程发送的消息
可以看到子线程中的消息被顺利发送到了主线程。
接下来我们来尝试下将主线程中的消息发送到子线程中去,注意Handler都需要创建在接受消息的线程,这一点在下面源码分析的地方会提到:
public class MainActivity extends AppCompatActivity {
private Handler mHanlde;
private Handler subThreadHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
subThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String) {
Log.d("子线程接受到的消息:",(String)msg.obj);
}
}
};
}
}).start();
SystemClock.sleep(1000);
Message message = new Message();
message.obj = "这是从主线程发送的消息";
subThreadHandler.sendMessage(message);
}
}
这里我在发送消息前延迟了1秒,主要是防止发送消息时这个子线程还没初始化好,导致拿到的handler为空,这里是为了演示方面,实际开发中切记不要在主线程中加这么多骚操作。运行下看下:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:203)
at android.os.Handler.<init>(Handler.java:117)
at simplae.handledemo.MainActivity$1$1.<init>(MainActivity.java:22)
at simplae.handledemo.MainActivity$1.run(MainActivity.java:22)
at java.lang.Thread.run(Thread.java:764)
我们发现程序抛异常了,说是在线程中如果不调用Looper.prepare()就无法创建Handler。
那么Looper对象是什么,是干嘛的呢?一般源码中在类的开头都会有对类的功能作用做一个简单的说明,我们打开Looper这个类,可以看到开头有这样一段注释:
/**
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
*
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
*
* <p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
*
* <pre>
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }</pre>
*/
可以看到这里第一句就对Looper的作用做了说明:
Class used to run a message loop for a thread.
即为一个线程做消息循环的一个类,说白了Looper就是一个调度器,可以简单理解为负责消息的存取。然后第二句也很重要:
Threads by default do not have a message loop associated with them;
to create one, call prepare in the thread that is to run the loop,
and then loop to have it process messages until the loop is stopped.
意思是线程默认是没有Looper的,可以在线程中调用prepare方法来创建,之后可以通过它来循环处理消息直到Looper退出,稍后会对其进行详细的分析。这样我们在创建Handler之前先添加这句话试下:
...
Looper.prepare();
subThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String) {
Log.d("子线程接受到的消息:",(String)msg.obj);
}
}
};
...
运行后,果然不报错了,不过子线程并未接受到主线程发送的消息。这里如果我们在子线程中创建Handler,创建完之后还需调用Looper.loop()方法,完整代码如下:
public class MainActivity extends AppCompatActivity {
private Handler mHanlde;
private Handler subThreadHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
subThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String) {
Log.d("子线程接受到的消息:",(String)msg.obj);
}
}
};
Looper.loop();
}
}).start();
SystemClock.sleep(1000);
Message message = new Message();
message.obj = "这是从主线程发送的消息";
subThreadHandler.sendMessage(message);
}
}
这样我们就成功接受到消息了。可以看到我们在子线程中创建Handler问题还是很多的,相比主线程,子线程在创建Handler前后分别得调用Looper的prepare和loop方法,这到底是怎么回事?不要着急接下来我们就一起来看下它背后究竟隐藏着怎样不为人知的秘密。
注意分析源码的时候一定要按程序执行流程往下看但是也不能太扣细节,要从整体上来把控,而且一定要自己亲自去看代码,如果只是去网上看别人的源码分析文章,自己没有实践,那是很难看懂的。即使看懂了,过几天就没印象了。所以接下来我分析源码的时候,还是建议你自己在编辑器中跟着一起查看源代码,这里只是起到一个引导的作用。
好了言归正传,我们就从第一句Looper.prepare()开始吧
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));
}
点开Looper.prepare()方法,我们发现里面就只有一句代码,调用了prepare(true)的重载方法,再点进去我们发现代码很简单主要是给ThreadLocal设置了一个Looper对象,这里出现了一个新的对象就是ThreadLocal,这个对象对于Handler可以说是非常重要了。所以要想接下去分析Handler还是先把这个理解下。
ThreadLocal说白了就是用来存放数据的,不过它有个与众不同的特点,就是它的存取是和当前线程关联的,换句话说,用ThreadLocal存放的数据是放在调用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 对象,我们数据就存在这个对象中。那么我们看下这个getMap方法是怎么获取这个 ThreadLocalMap对象:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
额。。有没有一种被耍了的感觉,原来这个对象就是直接从当前线程中来的。到这里你大概就明白了,原来通过ThreadLocal的set方法保存的数据就是直接存放在当前线程中的threadLocals变量中,这个变量是一个ThreadLocalMap 对象,而ThreadLocalMap 对象具体是怎么保存数据的,可以点开map.set(this, value)
方法看下:
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();
}
可以看到数据其实是以数组的形式保存的,具体的存储细节大家可以好好看看代码,这里就不过多讲解了。
知道了ThreadLocal如何存数据后,我们再来看下如何取数据,读取数据只用调用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();
}
很简单,就是获取当前的线程的ThreadLocalMap对象,然后获取ThreadLocalMap中保存的数据。
到这里,我们可以对ThreadLocal下个结论了:通过ThreadLocal保存的数据,其实就是保存在当前线程中,其他线程是无法访问到的。当你在某个线程中调用ThreadLocal的get方法,获取的数据也就是该线程中的数据。
好了,对ThreadLocal有了一个大致的了解,我们再回过头来看下Looper.prepare()的源码:
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));
}
可以看到通过ThreadLocal的set方法,在当前线程保存了一个Looper对象,我们进入Looper的构造方法看下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper的构造方法中实例化了一个MessageQueue对象,这个MessageQueue对象其实就是一个队列,你可以看成是一根水管,我们的消息就像里面的水,只能从一端流入,从另一端流出,在水管里的消息都是等待处理的,而消息的流入和流出都是由Looper这个对象控制的。
这样Looper.prepare()我们简单归纳下,主要做如下几件事情:
- 1.在当前线程保存了一个Looper对象
- 2.在Looper对象中创建了一个消息队列
好了,对Looper.prepare()了解了后,我们再来看下下面的代码:
subThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String) {
Log.d("子线程接受到的消息:",(String)msg.obj);
}
}
};
主要就是对Handler对象的实例化,我们进去看下:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到,通过Looper.myLooper();
方法获取一个Looper对象,我们看下这个方法内部:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
。。。就是通过ThreadLocal的get方法,来获取当前线程保存的Looper对象,也就是要获取前面通过Looper的Looper.prepare()方法保存的Looper对象。接下来就是判空,如果获取的Looper对象为空则直接抛出一个异常:
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
是不是很熟悉,就是我们文章开头在子线程中没有调用Looper.myLooper()就是直接创建Handler抛出的异常。
接下来通过 mQueue = mLooper.mQueue;
来获取Looper中的消息队列,至于其他代码我们暂且放下,因为先理清主要流程,不必太拘泥于细节。
好了,创建完Handler实例,接下来我们来看下接下来的Looper.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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " + time + "ms on "
+ Thread.currentThread().getName() + ", h=" +
msg.target + " cb=" + msg.callback + " msg=" + msg.what);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
在这个方法里可以看到,主要做了这么这么几件事,先通过myLooper()方法获取当前线程保存的Looper对象,然后获取该Looper对象中的消息队列MessageQueue ,之后会调用该消息队列的next()方法,调用这个方法线程就进入阻塞状态,一直判断这个消息队列中有无消息,有消息就取出来,并且执行如下语句来处理该消息,如果没有消息,则一直处于阻塞状态
msg.target.dispatchMessage(msg);
这里有个疑问msg的target是什么,调用完 Looper.loop()之后,我们知道线程现在一直处于阻塞状态,如果消息队列中有消息就取出来处理。那么接下来就是等着消息进入到这个队列。很显然这肯定和消息的发送有关,我们赶紧来看下,消息是如何发送的,这个消息到底跑哪去了,我们点开Handler的sendMessage方法,结果发现最终都会执行这个方法:
public boolean sendMessageAtTime(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);
}
注意这里的mQueue就是Handler实例化时从当前线程获取到的Looper中的消息队列,最终会调用enqueueMessage方法来使发送的消息进入这个消息队列。我们再进入到这个方法看下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
第一句就解决了我们上面的疑惑,原来这个msg的target就是Handler本身,再接下来就是调用这个队列的
enqueueMessage方法来使消息入队:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
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;
Message p = mMessages;
boolean needWake;
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 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.
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.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
这里其实就是队列的算法实现,这里由于篇幅有限就不深入分析具体的细节了。
这样消息队列中就有消息了,我们之前一直阻塞的线程就可以获取到消息来处理了,其处理的代码就是这句;
msg.target.dispatchMessage(msg);
刚才也看到了这个target就是Handler本身,我们赶紧来看下这个Handler的dispatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到里面做了一些非空判断,当msg.callback和mCallback 都为空的时候会执行handleMessage方法,这个方法进入看下:
public void handleMessage(Message msg) {
}
我们发现是空的,如果要处理需要Handler的子类实现,也就是我们上面新建Handler时覆写的handleMessage方法。可能有的朋友会疑惑这里的msg.callback和mCallback 什么时候不为空呢?
首先我们来看下msg.callback这种情况,说这种情况之前我们先来回顾下,还有哪些方式我们可以在子线程中更新UI,这里我们一般用的比较多的就是如下几个:
- 1、调用主线程中Handler的post或postDelayed等方法:
mMainHandler.post(new Runnable() {
@Override
public void run() {
}
});
- 2、直接调用Activity的runOnUiThread方法:
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
我们先来看第一种,我们进入Handler的post方法:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
原来还是发送一条消息,这里是通过getPostMessage方法把Runnable 封装成一条消息,我们进入看下:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
相信已经不用做过多解释了,这个Message的callback就是一个Runnable 对象,这里Message的callback也就被赋值了,回到上面当msg.callback不为空的时候,会调用handleCallback(msg);方法,我们进入看下:
private static void handleCallback(Message message) {
message.callback.run();
}
非常简单,就是调用通过post方法传进去的Runnable 对象的run方法。接下来我们看下runOnUiThread内部代码:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
可以看到也是很简单,如果当前线程不是UI线程,就调用Handler的post方法把Runnable发送到UI线程,如果是主线程就直接调用Runnable 的run方法。
好了到这里我们简单分析了Message的callback不为空的情况,接下来我们看下Handler的mCallback什么时候不为空,这个Callback是一个接口:
public interface Callback {
public boolean handleMessage(Message msg);
}
里面定义了处理消息的方法,它在什么时候被赋值的呢,Handler除了空参数的构造方法外还有其他几个构造方法,这些构造方法允许你直接传入这个callback的实现,也就是对消息的处理方法,这样就不用重新再像文章开头那样覆写Handler的handleMessage方法。比如:
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
这里我们创建Hanlder时就传入了一个CallBack,直接在这里就可以处理消息。
好了到这里我们就随着流程,分析了下消息传递的原理,可能大家对流程还不是非常清晰,不过没关系,我把有关的对象画成一个图:
上面的图可以很直观的反应消息传递时有关对象之间的关系,可以看到,在需要接受消息的线程A中创建了一个Handler,这个Handler对象中有一个Looper对象,而这个Looper对象中有个消息队列,里面存了待处理的消息。然后在需要发消息的线程B中调用线程A中HanlderA的sendMessage方法,这样线程B中的消息就已经被传入线程A了。这里需要注意的是,每个线程只关联一个单例的Looper对象,这个Looper对象是通过Looper.prepare创建,如果没有调用这句就直接创建Handler就会初始化失败而报错,这样就实现了线程的通信,怎么样是不是很巧妙!
可能有些朋友还有疑惑,为什么在子线程创建Handler前后还要分别调用Looper.prepare()和Looper.loop(),而在主线程中却不用调用,其实并不是主线程不用调用,而且在主线程一启动就已经调用了,我们来看下主线程中的代码:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
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();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
有两句代码很明显:
...
Looper.prepareMainLooper();
...
Looper.loop();
...
可以看到在主线程开始的时候,Looper就已经初始化好了,并且从Looper.loop()也可以看到,主线程在阻塞状态,等待处理消息。如果需要在主线程接受消息就只需要创建一个Handler就可以了。
到这里有关Handler的机制就分析的差不多了,如果看得还不是很懂,还是建议你自己在编辑器中阅读源代码,因为只有自己亲自看了,敲了才会印象深刻。