概述
Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue的中文翻译是消息队列,顾名思义,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。Looper的中文翻译为循环,在这里可以理解为消息循环。由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。通过Looper.prepare()即可为当前线程创建一个Looper,Looper除了prepare方法外,还提供了prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。由于主线程的Looper比较特殊,所以Looper提供了一个getMainLooper方法,通过它可以在任何地方获取到主线程的Looper。接着通过Looper.loop()来开启消息循环。loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。也就是说,Looper必须退出,否则loop方法就会无限循环下去。loop方法会调用MessageQueue的next方法来获取新消息,而next是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。
Looper中还有一个特殊的概念,那就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。我们知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
1.ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。比如对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。
虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们通过ThreadLocal获取到的值却是不一样的,这就是ThreadLocal的奇妙之处。ThreadLocal之所以有这么奇妙的效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰。
2.消息队列的工作原理
消息队列在Android中指的是MessageQueue,MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。尽管MessageQueue叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
3.Looper的工作原理
Looper在Android的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。首先看一下它的构造方法,在构造方法中它会创建一个MessageQueue即消息队列,然后将当前线程的对象保存起来,Handler的工作需要Looper,没有Looper的线程就会报错,那么如何为一个线程创建Looper呢?其实很简单,通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。
Looper除了prepare方法外,还提供了prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。由于主线程的Looper比较特殊,所以Looper提供了一个getMainLooper方法,通过它可以在任何地方获取到主线程的Looper。Looper也是可以退出的,Looper提供了quit和quitSafely来退出一个Looper,二者的区别是:quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false。在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。
Looper的loop方法的工作过程也比较好理解,loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。也就是说,Looper必须退出,否则loop方法就会无限循环下去。loop方法会调用MessageQueue的next方法来获取新消息,而next是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。
4.Handler的工作原理
Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过post的一系列方法以及send的一系列方法来实现,post的一系列方法最终是通过send的一系列方法来实现的。可以发现,Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。
5.主线程的消息循环
Android的主线程就是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环
=============
Andoird消息机制
创建的时候,系统会同时创建消息队列对象(MessageQueue)和消息轮询器对象(Looper)
轮询器的作用,就是不停的检测消息队列中是否有消息(Message)
消息队列一旦有消息,轮询器会把消息对象传递给消息处理器(Handler),处理器会调用handlerMessage方法处理这一条消息,handleMessage方法运行在主线程中,所以可以刷新UI
总结:只要消息队列有消息,handleMessage方法就会被调用
子线程如果要刷新UI,只需要向消息队列中发一条消息,出发handleMessage方法即可
子线程使用处理器对象的sendMessage方法发送消息。
使用Handler(消息处理器)来发送消息,消息会被发送到主线程的消息队列(message queue),只要消息队列有消息,Handler的handleMessage()会处理消息队列,于是主线程被刷新率。消息队列message queue和消息轮询器Looper是在主线程创建的时候创建
===================================
0,概念
1)消息机制
Handler是Android消息机制的上层接口。
2)Handler、MessaegQueue和Loop
一个线程有一个Looper,一个MessageQueue。可以有很多个Handler,发送各自的Message到这个MessageQueue中。
3)场景
①更新UI
只有主线程可以访问UI。
②处理消息
③将任务(耗时任务)切换到某个指定的线程中执行
2,角色
1)Message(消息)
分为硬件产生的消息(例如:按钮、触摸)和软件产生的消息。
2)MessageQueue(消息队列)
①概念
Android启动程序时会建立一个MessageQueue。主要用来向消息池添加消息和取走消息。
②数据结构
单链表
③功能
消息的存储单元,不能处理消息。
④工作原理
MessageQueue有两个操作:插入和读取。
enqueueMessage(插入)
向队列中插入一条消息。
next(读取)
从消息队列读取数据并移除。
3)Looper(消息循环器)
①概念
一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。主要用来把消息分发给相应的处理者。
线程默认没有Looper,使用时需要创建Looper。
ActivityThread(主线程)被创建时会初始化Looper,故主线程中默认可以使用Looper。
以无限循环的形式查找是否有新消息,如果有就处理,否则一直等待。
注意:Looper运行在创建Handler所在的线程中。
②ThreadLocal
不是线程,是一个线程内部的数据存储类,作用是可以在每个线程中存储数据。
ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,Handler通过ThreadLocal可以轻松获取每个线程的Looper。
即时是用同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中去取各自的数组,数据之前是互不干扰的。
使用场景:
一、当某些数据是以线程为作用域并且不同线程具有不同的数据副本。
二、复杂逻辑下的对象的传递,如监听器的传递。
采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。
③实现
i>创建Looper
一、Looper.prepare(),通过Looper.loop()开启消息循环。
loop方法是一个死循环,唯一跳出方式:MessageQueue的next方法返回null。
二、prepareMainLooper方法,这个方法是给主线程创建Looper使用。
通过getMainLooper方法,可以在任何地方获取到主线程的Looper。
ii>退出Looper
建议不需要的时候,终止Looper。
一、quit
直接退出Looper。
二、quitSafely
设定退出标记,然后吧消息队列中的已有消息处理完才安全退出。
④Can’t create handler inside thread that has not called Looper.prepare()
Looper类用来为一个子线程开启一个消息循环。默认情况下Android中新诞生的非主线程没有开启消息循环(主线程诞生时系统会自动为其创建开启消息循环的Looper对象),对于非主线程需要先调用Looper.prepare()启用Looper,然后通过调用。
问题来自于在AsyncTask的onPostExecute(Integer result)方法中,即非主线程中直接new Handler。
除了启用Looper,另一种解决方式:
i>定义一个接口
public interface UIListener {
/**
* 获得字符串,更新UI
* @param type UI更新类型
* @param msg UI更新信息
*/
public void onEvent(int type, String msg);
}
ii>I更新层,实现接口。
在onEvent方法具体实现,发送消息给handle,由handle执行toast。
@Override
public void onEvent(int type, String msg) {
switch (type) {
case UIType.showCustomToast:
dismissLoadingDialog();
Message msg_toast = new Message();
msg_toast.what = 8;
msg_toast.obj = msg;
handler.sendMessage(msg_toast);
break;
default:
break;
}
}
iii>在AsyncTask中,通过调用接口的方式,将异步线程的执行结果传到UI更新层。
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
if (Result.NETWORK_ERROR == result) {
((MainActivity) mContext).onEvent(UIType.showCustomToast, "网络异常,未进行正常更新");
} else if (Result.SUCCESS == result) {
RefreshData();
}
if (Result.ERROR == result) {
((MainActivity) mContext).onEvent(UIType.showCustomToast, "新任务获取失败");
}
}
4)Handler(消息处理器)
Handler是Android消息机制的上层接口。
Handler的运行需要底层的MessageQueue和Looper的支撑。
主要向消息队列发送各种消息以及处理各种消息。
5)工作原理
①Handler创建时
采用当前线程的Looper来构建内部的消息循环系统(如果当前线程没有Looper会报错,需创建Looper)。
②Handler创建完毕后
通过Handler的post方法将一个Runnable投递到Handler内部的Looper中处理,也可以通过send方法来完成。
使用handler发送消息时有两种方式,都是将指定Runnable(包装成PostMessage)加入到MessageQueue中,然后Looper不断从MessageQueue中读取Message进行处理。
post(Runnable r)
postDelayed(Runnable r, long delayMillis):可以精确传递时间但又不阻塞队列。
postDelayed一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;
直到阻塞时间到或者下一次有Message进队;
这样,基本上就能保证Handler.postDelayed()发布的消息能在相对精确的时间被传递给Looper进行处理而又不会阻塞队列了。
③send方法
post方法最终也是通过send方法来完成的。
当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就好被调用。
6)消息流程
①Handler通过sendMessage()发送消息Message到消息队列MessageQueue。
②Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。
③target handler调用自身的handleMessage()方法来处理Message。
在整个消息循环的流程中,并不只有Java层参与,很多重要的工作都是在C++层来完成的。
注:虚线表示关联关系(它们发生关联的桥梁是MessageQueue),实线表示调用关系。
package com.luo.activity;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;
public class MainActivity extends Activity {
private Handler handler;// 获取数据变更
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
sendMsg(0, 1);
}
private void init() {
handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Toast.makeText(MainActivity.this, "this is handler" + msg.arg1, Toast.LENGTH_LONG).show();
break;
default:
break;
}
}
};
}
public void sendMsg(int flag, int value) {
Message message = new Message();
message.what = flag;
message.arg1 = value;
handler.sendMessage(message);
}
}
3,内存泄漏
1)原因
使用内部类、匿名类来创建Handler,这样会造成内存泄露!
点击查看什么是内存泄漏
原因:当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。
而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。
如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。
2)解决方案
①通过程序逻辑来进行保护(推荐)
i>在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
ii>如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
②将Handler声明为静态类
静态类不持有外部类的对象,所以Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference),具体实现如下:
static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}