参考
Handler和他的小伙伴们(上)
Android:异步处理之Handler+Thread的应用(一)
Android:异步处理之Handler、Looper、MessageQueue之间的恩怨(三)
android的消息处理机制(图+源码分析)——Looper,Handler,Message
Android中为什么主线程不会因为Looper.loop()里的死循环卡死
主线程是指进程所拥有的线程,默认情况下一个进程只有一个线程就是主线程,主线程主要处理界面交互,又叫UI线程;除了主线程之外都是子线程,又叫工作线程。
一、为什么不能直接更新UI
不要在UI主线程中进行耗时操作,你可能会疑问什么是UI主线程,UI主线程主要运行的就是Activity、Service等里面的生命周期方法,所以不要在生命周期方法如onCreate()中进行下载这些大事件。对于耗时操作,我们应该新建一个子线程并交给他处理。但是还需要注意一点,不要在子线程中更新UI界面。
为什么不允许子线程访问UI呢?这是因为UI控件是线程不安全的,如果多线程并发访问会导致UI控件处于不可预期状态。那为什么不加锁机制来解决呢?缺点有两个:
- 锁机制会让UI访问逻辑变复杂
- 锁机制会阻塞某些线程的执行,降低UI访问效率
所以最简单高效方式就是单线程处理UI操作。
二、Handler
现在我们需要进行耗时操作(例如下载文件)时不能在主线程执行,我们又需要在UI界面通知用户我们活干完了不能在子线程中执行。这似乎是一个棘手的热山芋呀,幸好谷歌给我们提供了一个救我们于危难之中的Handler,一个能让主线程监听子线程发送来消息的东东。
<pre>
private Button btn;
private TextView text;
private Handler handler = new Handler(){
private int process = 0;
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0://更细下载进度
process += 1;
text.setText("下载" + process + "%");//在主线程中更新UI界面
break;
case 1://提示下载完成
text.setText("下载完成");//在主线程中更新UI界面
break;
default:
break;
}
}
};
//onCreate之类的生命周期的方法就是允许在UI主线程中
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
text = (TextView) findViewById(R.id.text);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
//为了不阻塞主线程,将下载任务通过子线程来执行
for(int i = 0; i < 100; i++){
try {
Thread.sleep(200);//休眠0.2秒,模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);//发送消息到handler,通知下载进度
}
handler.sendEmptyMessage(1);//发送消失到handler,通知主线程下载完成
}
}.start();
}
});
}
</pre>
但是如果你觉得每次都要重写handlerMessage()比较麻烦,我们完全可以用更加简略的方法来解决我们的需求,就是用handler中的post方法。代码如下
<pre>
new Thread(){
@Override
public void run() {
//在子线程中进行下载操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
@Override
public void run() {
text.setText("下载完成");
}
});//发送消失到handler,通知主线程下载完成
}
}.start();
</pre>
这样处理的话我们就可以不用重写handlerMessage()方法了,适合子线程与主线程进行较为单一的交流。但在这里我们要强调的一点的是,post里面的Runnable还是在UI主线程中运行的,而不会另外开启线程运行,千万不要在Runnable的run()里面进行耗时任务
如果你有时候连handler都不想搞,还可以这样写代码滴。我们只需要把handler换成View组件进行post,更新任务自然会加载到UI主线程中进行处理。
<pre>
text.post(new Runnable() {
@Override
public void run() {
text.setText("下载完成");
}
});//发送消失到handler,通知主线程下载完成
</pre>
关于sendMessage和post详细区别,可参考承香墨影Android--多线程之Handler
三、Handler looper messengerQueue
这里给大家写了一个向子线程发送消息并显示输出的例子,强调一下下哦,是向子线程哟。
<pre>
public class MainActivity extends ActionBarActivity {
private Handler handler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.sendmsg);
new HandlerThread().start();//启动子线程
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handler.sendEmptyMessage(0);//向子线程发送消息
}
});
}
class HandlerThread extends Thread{
@Override
public void run() {
//开始建立消息循环
Looper.prepare();//初始化Looper
handler = new Handler(){//默认绑定本线程的Looper
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0:
Toast.makeText(MainActivity.this, "子线程收到消息", Toast.LENGTH_SHORT).show();
}
}
};
Looper.loop();//启动消息循环
}
}
}
</pre>
说一下消息发送的过程:
1、启动一个子线程,并在子线程初始化一个Looper。
<pre>
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();
}
</pre>
在Looper()中,实例化了一个消息队列(MessageQueue)!并且如我们所愿的绑定到了mQueue这个局部变量上,在这里我们可以得出这么一个结论:调用Looper. prepare()的线程就建立起一个消息循环的对象。
2、在HandlerThread中实例化Handler,Handler自动绑定上当前线程的Looper。
<pre>
public Handler() {
this(null, false);
}
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;
}
</pre>
在代码中我们通过handler = new Handler() 调用到了Handler(Callback callback, boolean async)这个方法;我们发现mLooper = Looper.myLooper()把线程中的Looper绑定到了Handler上,通过mQueue = mLooper.mQueue获取了线程的消息队列(单链表,方便插入删除),我当然也可以换句话说:Handler已经绑定到了创建此Handler对象的线程的消息队列上了,所以咱们可以开始干坏事了。。。。
3、重写Handler里面的消息处理方法。
<pre>
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0:
Toast.makeText(MainActivity.this, "子线程收到消息", Toast.LENGTH_SHORT).show();
}
}
</pre>
4、执行Looper.loop()启动消息循环,子线程进入等待消息状态。
<pre>
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;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
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.recycle();
}
}
</pre>
在loop()的这个静态方法中,我们可以注意到for (;;)这个方法,这是死胡同死循环,所以我们将其称作为“消息循环”,说起来挺形象滴。在消息循环中会调用queue.next()来获取消息队列中排队等待处理的消息,并将其赋值到msg这个变量上;接下来就判断如果msg != null 就开始分发消息,也就是执行msg.target.dispatchMessage(msg)。在分发消息结束后,将会回收掉这个消息,体现在msg.recycle()这个函数上。
msg.target是一个handler对象,表示需要处理这个消息的handler对象,所以我们回到Handler看看dispatchMessage()这个方法了:
<pre>
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);//post方法传递的Runnable参数
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
</pre>
不知道大家有没有一眼发现handleMessage()这个方法,这可不是我们在第三步重写Handler中的方法么。真相大白,当 msg.callback != null 并且 mCallback != null 时将会调用 handleMessage(msg) 来处理其他线程发送来的消息,我们通过覆盖这个方法来实现我们具体的消息处理过程;这也就是Handler消息处理机制的全部内容。
通读全文,我们可以知道消息循环机制的核心就是Looper,因为Looper持有了MessageQueue的对象,并且可以被一个线程设为该线程的一个局部变量,我们可以这么认为这个线程通过Looper拥有了一个消息队列。而Handler的用处就是封装了消息发送和消息处理的方法,在线程通信中,线程可以通过Handler发送消息给创建Handler的线程,通过Looper将消息放入进入消息接收线程的消息队列,等待Looper取出消息并在最后交给Handler处理具体消息。
我们会发现在Activity中实例化一个Handler并不需要Looper.prepare()来初始化一个Looper和Looper.loop()来启动消息循环,因为Activity在构造过程中已经对Looper进行了初始化并且建立了消息循环,参见ActivityThread.java中的代码:
<pre>
public final class ActivityThread {
public static final void main(String[] args) {
......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false);
......
Looper.loop();
......
thread.detach();
......
}
}
</pre>
Android应用程序进程在启动的时候,会在进程中加载ActivityThread类,并且执行这个类的main函数,应用程序的消息循环过程就是在这个main函数里面实现的。
总结:一个线程只有一个Looper, 而一个Looper持有一个MessageQueue, 当调用Looper.prepare()时,Looper就与当前线程关联起来了(在Activity里没有显示调用Looper.prepare()是因为系统自动在主线程里帮我们调用了),而Handler是与Looper的线程是绑定的,查看Handler类的源码可以发现它几个构造函数,其中有接收一个Looper参数的,也有不接收Looper参数的,从上面的代码上看,我们没有为Handler指定Looper,那么Handler就默认更当前线程(即主线程)的Looper关联起来了,之所以啰嗦那么多就是因为这决定了Handler.handlerMessage(msg)方法体里的代码到底在哪个线程里执行,我们再梳理一下,Looper.prepare调用决定了Looper与哪个线程关联,间接决定了与这个Looper相关联的Handler.handlerMessage(msg)方法体里的代码执行的线程。(太啰嗦了)
现在回到上面的代码,我们的Handler是在主线程里的定义的,所以也默认跟主线程的Looper相关联,即handlerMessage方法的代码会在UI线程执行。