前言
在安卓当中提供了异步消息处理机制的两种方式来解决线程之间的通信,一种是是AsynchTask,另外一种就是现在我们主要分析的Handler。
Handler是Android类库提供的用于接受、传递和处理消息或Runnable对象的处理类,它结合Message、MessageQueue和Looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。
简单使用分析
总所周知,安卓中子线程是不能更新UI的,如果在子线程更新,那么程序就会崩溃,那么这时候我们就使用到了handler,子线程操作完成通知主线程更新UI。我们先看下handler机制的分析图,和架构图:
- Looper有一个MessageQueue消息队列;
- MessageQueue有一组待处理的Message;
- Message中有一个用于处理消息的Handler;
- Handler中有Looper和MessageQueue。
一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象,使用Handler生成Message,所生成的Message对象的Target属性,就是该Handler对象。而一个Handler可以生成多个Message,所以说,Handler和Message是一对多的关系。
Android中主线程向子线程发送消息
1. 创建Handler
在安卓的ui线程中创建一个Handler
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e("TAG", "当前线程: " + Thread.currentThread().getName() + " 收到消息:" + msg.obj);
}
};
2. 开启子线程向主线程的handler发送消息
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Message obtain = Message.obtain();
obtain.obj = Thread.currentThread().getName() + ": 发送消息";
handler.sendMessage(obtain);
}
}).start();
}
});
3. 结果分析
结果:
当前线程: main 消息:Thread-4: 发送消息
结果说明我们在主线程中创建handler,然后点击按钮子线程向主线程发送消息成功
==问题==:
在一个activity中如上使用handler,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用,通常handler都伴随着一个耗时线程出现,例如访问网络,访问成功,使用handler刷新界面。但是在访问网络的时候这时候activity关闭了,这时候handler依然持有activity的引用,导致内存泄露了,当过多的内存泄露,导致程序OOM了,是不是感觉handler还是有点坑?
==答案==
-
方法一:通过程序逻辑来进行保护。
- 在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
- 如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
-
方法二:将Handler声明为静态类。
PS:在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。代码如下:
static class MyHandler extends Handler
{
WeakReference<Activity> mWeakReference;
public MyHandler(Activity activity)
{
mWeakReference=new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg)
{
final Activity activity=mWeakReference.get();
if(activity!=null)
{
if (msg.what == 1)
{
//刷新UI
}
}
}
}
分析:
我们按照刚才的流程图来 分析一遍。
首先我们在主线程创建了一个Handler,那么在主线程中也会对应有一个Looper对象在轮询消息,一个Looper对象有一个MessageQueue,于是主线程中也有一个MessageQueue。我们的流程就是主线程中looper对象在一直轮询消息,如果消息队列中没有任何消息的话,那么当前线程暂时阻塞,直到子线程中获取handler对象发送消息,这时候,handler会对主线程中的MessageQueue中添加消息,当消息添加成功时,将阻塞的线程唤醒。于是looper轮询到有新消息,将新消息返回给handler对象,因为handler对象是在主线程中创建,所以消息将会在主线程中显示。
这个流程和生产者消费者模型有一点相似,一个线程生成消息,一个线程消费消息。所以在MessageQueue中的添加消息,和消费消息都会有一把锁。将这两个方法锁住;首先避免多个线程同时操作消息列队,和避免再写入消息的时候读取消息,导致消息错乱的问题。如下图,MessageQueue源码中锁住当前对象:
- 也许这里有些难懂,但是没关系,我们继续向下分析
子线程向主线程发送消息
上面我们操作了子线程向主线线程发送消息,接下来我们使用handler主线程向子线程发送消息。
1. 子线程中创建handler对象。
2. 为当前子线程创建一个looper对象。(这里我们使用ThreadLocal来保存Looper副本)
3. 开启子线程looper轮询消息
new Thread(new Runnable() {
@Override
public void run() {
//对当前线程创建一个looper副本
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e("TAG", "当前线程: " + Thread.currentThread().getName() + " 收到消息:" + msg.obj);
}
};
//开启轮询消息
Looper.loop();
}
}).start();
4. 主线程向子线程发送消息
Message obtain = Message.obtain();
obtain.obj = Thread.currentThread().getName() + "线程发送的消息";
handler.sendMessage(obtain);
5. 结果分析
结果如下:
当前线程: Thread-4 收到消息:main线程发送的消息
这时候我们就成功重主线程发送了一条消息给子线程
分析:
我们重上面代码注意到相比子线程发送消息给主线程我们主线程发送消息给子线程多了两行代码:
- Looper.prepare();
我们翻阅源码如下:
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来保存每一个接收线程中的Looper对象副本。由于子线程是我们手动开启的线程,所以我们要初始化一个looper副本。由于安卓主线程中,安卓系统自动维护了一个安卓主线程的looper对象副本并让looper轮询着消息。
- Looper.loop();
我们翻阅源码如下:
public static void loop() {
//获取looper对象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取messageQueue对象
final MessageQueue queue = me.mQueue;
...............
//轮询消息
for (;;) {
//轮询messageQueue中的消息,没有消息就再这里阻塞。
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is
return;
}
...........
try {
//发送消息
msg.target.dispatchMessage(msg);
............
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
............
}
}
这里安卓系统也是在主线程中轮询着消息。
手写一套handler。
我们经过上面的使用和简单分析了以后,也许还是有一些懵逼。所以下面我们自己通过生产者/消费者模型来模仿安卓Handler手写一套。代码如下:
Handler
public class Handler {
private Looper mLooper;
private MessageQueue mQueue;
public Handler() {
//获取当前线程的looper
mLooper = Looper.myLooper();
//获取当前线程的消息列队
mQueue = mLooper.messageQuene;
}
/**
* 发送消息
* @param message
*/
public void sendMessage(Message message) {
message.target = this;
mQueue.enqueueMessage(message);
}
/**
* 处理消息
* @param message
*/
public void handleMessage(Message message) {
}
/**
* 分发消息
* @param message
*/
public void dispatchMessage(Message message) {
handleMessage(message);
}
}
Looper
public class Looper {
final MessageQueue messageQuene;
private static ThreadLocal<Looper> threadLocal = new ThreadLocal<>();
public Looper() {
messageQuene = new MessageQueue();
}
/**
* 为当前线程初始化一个looper副本对象
*/
public static void prepare() {
if (threadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
threadLocal.set(new Looper());
System.out.println("looper初始化");
}
/**
* 获取当前线程的looper副本对象
*
* @return
*/
public static Looper myLooper() {
return threadLocal.get();
}
/**
* 轮询消息
*/
public static void loop() {
//获取当前线程的looper对象
Looper me = myLooper();
Message msg;
//开始轮询消息
for (; ; ) {
//轮询消息,没有消息就阻塞
msg = me.messageQuene.next();
if (msg == null || msg.target == null) {
System.out.println("Looper:" + "空消息");
continue;
}
System.out.println("Looper:" + "looper轮询到了消息,发送消息");
//轮询到了消息分发消息
msg.target.dispatchMessage(msg);
}
}
}
Message
public class Message {
//发送的消息
public Object obj;
//目标Handler
public Handler target;
@Override
public String toString() {
return obj.toString();
}
}
MessageQueue
要实现生产者/消费者模型,首先的有锁,这里使用ReentrantLock
主要考虑的重写入,它可以根据设定的变量来唤醒不同类型的锁,也就是说当我们队列有数据时,我们需要唤醒read锁;当队列有空间时,我们需要唤醒写锁。
public class MessageQueue {
Message[] mItems;
int mPutIndex;
//队列中消息数
private int mCount;
private int mTakeIndex;
//锁
Lock mLock;
//唤醒,沉睡某个线程操作
Condition getCondition;//可取
Condition addCondition;//可添加
public MessageQueue() {
mItems = new Message[50];
mLock = new ReentrantLock();
getCondition = mLock.newCondition();
addCondition = mLock.newCondition();
}
/**
* 消息队列取消息 出队
*
* @return
*/
Message next() {
Message msg = null;
try {
mLock.lock();
//检查队列是否空了
while (mCount <= 0) {
//阻塞
System.out.println("MessageQueue:" + "队列空了,读锁阻塞");
getCondition.await();
}
msg = mItems[mTakeIndex];//可能空
//消息被处理后,置空数组中该项
mItems[mTakeIndex] = null;
//处理越界,index大于数组容量时,取第一个item
mTakeIndex = (++mTakeIndex >= mItems.length) ? 0 : mTakeIndex;
mCount--;
//通知生产者生产
addCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
return msg;
}
/**
* 添加消息进队列
*
* @param message
*/
public void enqueueMessage(Message message) {
try {
mLock.lock();
//检查队列是否满了
while (mCount >= mItems.length) {
//阻塞
System.out.println("MessageQueue:" + "队列空了,写锁阻塞");
addCondition.await();
}
mItems[mPutIndex] = message;
//处理越界,index大于数组容量时,替换第一个item
mPutIndex = (++mPutIndex >= mItems.length) ? 0 : mPutIndex;
mCount++;
//通知消费者消费
getCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
}
}
测试:
public class Test {
public static Handler handler;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
System.out.println("Test:" + Thread.currentThread().getName() + "线程接收到:" + message.obj);
}
};
Looper.loop();
}
}).start();
//睡0.5s,保证上面的线程中looper初始化好了
Thread.sleep(500);
new Thread(() -> {
Message message = new Message();
message.obj = Thread.currentThread().getName() + "发送的消息 ";
handler.sendMessage(message);
}).start();
new Thread(() -> {
Message message = new Message();
message.obj = Thread.currentThread().getName() + "发送的消息 ";
handler.sendMessage(message);
}).start();
}
}
结果分析
结果:
looper初始化
MessageQueue:队列空了,读锁阻塞
Looper:looper轮询到了消息,发送消息
Test:Thread-0线程接收到:Thread-1发送的消息
Looper:looper轮询到了消息,发送消息
Test:Thread-0线程接收到:Thread-2发送的消息
MessageQueue:队列空了,读锁阻塞
分析:
到这里我们的手写的一套Handler就完成了。自己手写一次handler消息处理机制,再回过头来看看handler是不是很简单了,再也不怕面试中被问到。当然android源码中的handler处理机制移值到C层处理了.