在初学Android的时候我们都碰过这个问题,要从服务端获取数据,这时候,我们知道在主线程不能做耗时操作,否则会引起ANR,更不能在主线程中联网,Android3.0以后会报一异常,或者在子线程中更新UI报出了一个经典异常,我们都知道解决方法是在子线程用Handler发消息给主线程进行更新就可以解决上述两个问题,但是我们会不会疑惑,Handler干了什么事情就可以自由切换线程执行,由于Android帮我们封装好了我们日常开发的时候只要跟Handler打交道就可以办成这些事,但是了解整个Android消息机制的原理有助于提升自己对整个Android的了解,所以我就写了这个博客,把我自己对Android消息传递机制的理解记录下来。
Android的消息机制主要指Handler、MessengeQueue和Looper的工作机制,有时我们会发现当我们主线程中进行联网操作时,会发现可能在2.3运行时是正常,但是在3.0以后的版本之后运行就会报一个android.os.NewWorkOnMainTHreadException,这是因为Android在API Level9之后就对这个进行了处理,如下所示。
/**
* For apps targetting SDK Honeycomb or later, we don't allow
* network usage on the main event loop / UI thread.
*
* Note to those grepping: this is what ultimately throws
* NetworkOnMainThreadException ...
*/
if (data.appInfo.targetSdkVersion > 9) {
StrictMode.enableDeathOnNetwork();
}
Android的设计者并不希望也不允许我们在主线程进行进行联网操作,接着就是在子线程中进行更新UI的操作,我举个栗子,如下所示:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv_hello);
new Thread(new Runnable() {
@Override
public void run() {
// SystemClock.sleep(3000);
tv.setText("aa" + i);
}
}).start();
}
}
我们发现这时候竟然是可以运行,不是说不能在子线程中更新UI吗,其实是这样的这个线程是在onCreate里创建的,当线程运行结束的时候界面还没有出来,所以就可以更新UI了,要证明这个只要在子线程添加一个sleep方法睡一下,之后就会报出这个经典异常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
这是由于在ViewRootImpl的checkThread方法进行了验证,如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这个异常基本上是每个Android开发者都曾碰过的,其实想想Android这样设计也是很靠谱的,我们知道Android的UI控件并不是线程安全的,如果多线程并发访问可能会产生不可预估的问题,举个栗子,比如一款游戏,打怪掉血跟吃血瓶操作都放在了子线程中进行,他们也都要更新血条这个UI,这时候如果子线程可以操作血条,那血一直掉,用户就吃了血瓶,但是我们知道线程是有不确定性的,有可能我是先吃的血瓶但是线程执行却没有掉血的快,等到血瓶执行更新UI的时候角色已经死了等等...,由于上面的两个矛盾,如果交由我们自己处理怕是处理不来,所以Android提供了一套基于Handler的运行机制去解决在子线程中无法访问UI的矛盾。还是上面这个例子,当角色被怪打了,这时掉血线程发个信息告诉UI线程让UI线程去更新血条,喝血瓶这个线程要更新血条也发信息让UI线程去更新,这样就解决了子线程并发修改UI控件的矛盾了。
虽然使用Handler进行UI更新操作并不复杂,但是还是用个示例来说明:
public class MainActivity extends AppCompatActivity {
private ProgressBar pb;
private TextView tvPb;
private static final int FLAGS = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case FLAGS:
pb.setProgress(msg.arg1);
tvPb.setText(String.valueOf(msg.arg1));
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = (ProgressBar) findViewById(R.id.pb);
tvPb = (TextView) findViewById(R.id.tv_pb);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
Message msg = mHandler.obtainMessage();
SystemClock.sleep(200);
msg.what = FLAGS;
msg.arg1 = i;
mHandler.sendMessage(msg);
}
}
}).start();
}
}
示例很简单,就是通过Handler去sendMessage把要更新progressBar和TextView的操作发送给handleMessage来执行,当然有一点就是Message对象可以自己new出来也可以使用handler.obtainMessage去获取这个对象,因为使用.obtainMessage去获取一个对象的性能比new出来的要好,是因为.obtainMessage是从消息池中取出(如果有),而不是每次都直接new,省去了创建对象的内存消耗,所以就用了这个方法效果如图
前面说了Android消息机制主要指Handler及其附带的MessageQueue、Looper以及Message的工作流程,下面一个个分析
消息,里面可以包含消息处理对象和处理数据等等,由MessageQueue进行统一的排列,然后交由Handler进行处理.
MessageQueue主要有插入和读取两个操作,
插入:在MessageQueue中插入操作由enqueueMessage这个方法来执行,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;
}
从源码可以看出,这个方法主要就是对单链表进行插入操作,当然我们平常开发可能会碰到一个异常就是从这里报的,比如第一个例子
当我们把
这段代码放到for循环外面时,就会报
Java.lang.IllegalStateException: { when=-205ms what=1 arg1=1 target=com.sjr.handlerdemo.MainActivity$1 } This message is already in use.这个异常,这是因为当把代码放到循环体外面的时候sendMessage可能会和handleMessage并发同时操作一个对象,所以Android直接抛了这个异常出来,当然这是题外话。
读取:读取操作的执行方法是next方法,next的源码如下:
从源码可以看到,next方法里面有个死循环方法,当消息队列里没有消息时next方法就会一直阻塞,当有消息时就返回这条消息然后将消息从消息队列中移除。
Looper在这套消息机制里面可以称为一个轮询器,它会不断的从MessageQueue中轮询查看是否有新消息,如果有新消息这个轮询器就会处理,创建一个Looper的方法为Looper.prepare();源码中Looper创建的方法为:
然后它进行了一系列的逻辑操作来创建一个Looper,介于篇幅有限就不再详细点进去研究了,当然主线程有一个专门的创建方法prepareMainLooper,它最终也是通过prepare来实现,由于主线程Looper比较特殊,所以Android帮我们封装了一个方法,通过getMainLooper可以获取到主线程的Looper。如果不需要使用Looper时应该终止它,通过quit()或quitSafely()方法可以终止Looper,两个方法的区别是一个前者一旦调用就直接退出,后者是把消息队列中存在的消息处理完之后才会退出。
我们前面说了Looper会一直轮询MessageQueue,接下来的方法就是起轮询作用的方法,有了这个方法Looper才会去轮询,这个方法是loop();源码为:
我们发现他调用的是ThreadLocal的get方法,ThreadLocal是一个线程内部数据存储类,通过它可以在指定线程中存储数据然后只能在只能的线程中才能获取存储的数据。它通过get和set方法去获取和设置当前线程的localValues对象的table数组,由于它们对ThreadLocal的读/写操作仅限于各自线程内部,所以ThreadLocal可以在多个线程中互不干扰地存储和修改数据。这个类在我们平常开发中用得比较少,这里只是因为涉及到其中的get方法就提一下,get方法源码为:
从源码可以看出,这是一个取出当前线程的localValues对象,如果这个对象不为空就取出它的table数组并找出ThreadLocal的reference对象在数组中的额位置,table数组中的下一个位置所存储的数据就是ThreadLocal的值。
回到loop()的源码,可以看到如果
去处理这条消息,这里的msg.target是发送这条消息的Handler对象,所以通过这段代码我们就可以解开我们前面的疑惑,为什么同一个Handler对象可以在子线程中发送消息然后在主线程中通过自己的handleMessage去处理这条消息,然后这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper执行的,这样就可以将代码逻辑切换到指定的线程中执行了。
Handler是消息的处理者,它的工作主要是消息的发送和接收以及处理,下面是Handler对消息插入的处理,就是一系列senMessage方法的源码:
从源码可以看到,send方法最后返回的都是enqueueMessage这个方法,这个方法的源码为:
最后返回的是MessageQueued的enqueueMessage方法去往消息队列中插入一条消息,这就是Handler的sendMessage能够插入消息的原理了,然后MessageQueue的next方法发现有消息了就停止阻塞返回这条消息给Looper,Looper收到消息之后就开始进行处理,最后交由Handler的dispatchMessage方法去处理。dispatchMessage的源码为:
通过这个方法就可以对消息进行处理了,这个方法的逻辑是先检查Message的callback方法是否为null,不为null就调用handleCallback这个方法来处理,Callback是一个接口:
通过这个接口我们可以初始化一个Handler而且不需要实现Handler的子类,比如前面第一个例子,我们是new了一个Handler类然后重写了handleMessage方法,而Callback这个接口是另一种使用Handler的方式.
回到上面的dispatchMessage方法然后会判断当前Handler的Callback是不是为null,如果不为null就接着判断它的handleMessage方法是否为true,如果为true就直接return,最后调用handleMessage方法,这是空的方法,我们调用时一般是重写这个犯法实现具体的业务逻辑。
Android消息机制在日常开发中很常见,熟悉它的运行机制有助于我们开发出更高效的应用程序,最后上述的内容可以用一张图来简略总结(图画得有些难看,接受吐槽..)