android java 线程通信方式_Android线程通信机制-Handler(Java层)

一、概述

Android的单线程UI模型,决定了在UI线程中不能进行耗时任务,在开发过程中,需要将网络、io等耗时任务放在工作线程中执行,工作线程中执行完成后需要在UI线程中进行刷新,因此就有了Handler进程内线程通信机制,当然Handler并不是只能用在UI线程与工作线程间的切换,Android中任何线程间通信都可以使用Handler机制。

Android的Handler机制应该说是有两套实现,Java层与native层分别实现了Handler机制,也就是说在Java层与native层各自维护了自己的消息队列,native层消息优先于Java层消息处理,在MessageQueue的源码中可以看到很多的native代码。这里只对Java层做个分析。

二、使用Handler实现线程间通信

1、在UI线程中使用Handler

UI线程中使用Handler非常简单,因为框架已经帮我们初始化好了Looper,只需要创建一个Handler对象即可,之后便可直接使用这个Handler实例向UI线程发送消息。

private Handler handler = new Handler() {

@Override

public void handleMessage(Message msg) {

// do something

}

};

注意:这种做法会导致内存泄漏。

我们通过Handler发送消息,在Message对象中会持有当前Handler对象的引用,在Java中非静态成员类、内部类、匿名类会持有外部对象的引用(这里在源码中有提到),而Looper是线程局部变量,其生命周期与UI线程相同,Looper持有MessageQueue的引用,MessageQueue持有Message的引用,当通过Handler发送一个延时消息未处理之前用户已经离开当前Activity,会导致Activity不能及时释放而内存泄漏。

解决思路:

既然知道是因为Handler持有Activity的引用而导致内存泄漏,那便让Activity在结束的时候不再有对象持有当前Activity的引用,或者不再有对象持有该Handler的引用,总之便是将这条引用链切断。

2、在非UI线程中使用Handler

在非UI线程中使用Handler一定要注意必须在创建Handler之前调用Looper.prepare()方法来初始化Looper,否则会报异常。如果不调用Looper.loop()方法,线程会在执行完毕后退出,也无法接收到消息。

private Handler handler;

Thread threadA = new Thread(new Runnable() {

@Override

public void run() {

// 这一步其实是创建threadA的线程本地变量Looper

Looper.prepare();

// 创建Handler

handler = new Handler() {

@Override

public void handleMessage(Message msg) {

Bundle bundle = msg.getData();

System.out.println(bundle.getString("msg"));

}

};

// 让threadA进入Looper循环中,不断的获取消息

Looper.loop();

}

});

Thread threadB = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("<<<<<<<<<<

// 创建消息

Message msg = Message.obtain();

Bundle bundle = new Bundle();

bundle.putString("msg", "Hello World!");

msg.setData(bundle);

// 发送消息

handler.sendMessage(msg);

System.out.println(">>>>>>>>>>>");

}

});

这里只是为了简单写的示例代码,在实际开发中当然不会这么做,异步任务都会管理起来,不然离开了当前界面还有任务在执行(比如请求数据),那便没有意义了。

三、Handler机制实现原理

1、UML类图

Handler机制主要由Handler、Looper、MessageQueue、Message四个类组成,从下面的UML中可以看到Handler持有一个Lopper实例,这个Looper实例与线程相关,而Looper中管理着一个MessageQueue消息队列,MessageQueue本质上是一个链表。从UML类图大致能看到Handler的一个整体结构。

94a8b50b7fcc

Handler、Looper、MessageQueue、Message类图

2、Handler工作流程

2.1、基本流程

Handler工作流程主要分为两条支线,工作线程(也就是要发送消息的线程,后同)中发送消息实际上是将消息插入到消息队列MessageQueue中,初始化Handler的线程(即接受消息的线程,后同)则通过Looper.loop()方法进入无限循坏,不断的从消息队列MessageQueue中取出消息,通过Message本身持有的Handler去分发消息。

2.2、线程切换的关键

1、在Looper初始化的时候,其实是在当前线程的本地变量(ThreadLocal)中存储了一个Looper,而Looper.loop()在进入循环时,便是通过当前线程拿到Looper对象,从而拿到当前线程维持的MessageQueue消息队列,不断的读取消息。至于在另一个线程中通过Handler发送消息简单的说,读消息一直都是在初始化Handler的线程中进行,之后的分发消息与处理消息当然也是了。

2、在工作线程中发送消息,Handler本身持有了一个初始化线程的引用,当发送消息时,其实都是将消息放入初始化线程的消息队列中。

94a8b50b7fcc

Handler工作流程图

3、源码解析

3.1 Handler源码

从创建Handler开始看,Handler的构造方法有多个重载,最终都会走Handler(Callback callback, boolean async)或者Handler(Looper looper, Callback callback, boolean async)这两个构造方法。

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());

}

}

// Looper.myLooper()是从线程本地存储中拿到与当前线程相关的Looper

mLooper = Looper.myLooper();

// 这里抛出的异常就是我们经常看见的,未再非UI线程中使用Handler却没有调用Looperprepare()初始化Looper

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;

}

Handler创建好了,接下来便是使用Handler,也就是通过Handler来发送消息。发送消息的方法主要有两大类,post****系列发送一个Runnable,作为处理消息的callback,或者send****系列方法发送一个Message,本质上都是构造一个Message对象,加上消息分发的时间点,最终都会调用sendMessageAtTime(Message msg, long uptimeMillis)这个方法。

有一个例外的方法,sendMessageAtFrontOfQueue(Message msg),默认将消息的执行时间点置为0,也就是立即分发,在入消息队列时会放置在队列头。

/**

* @param msg 要发送的消息

* @param uptimeMillis 消息的投递时间

* @return true 消息放入队列成功, false则是失败

*/

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

MessageQueue queue = mQueue;

// 这个方法很简单,在这里检查了下MessageQueue是否准备好了

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);

}

这个方法只有几行代码,真正入队列还是在MessageQueue中做的,却在入队列之前做了一件很重要的事情。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

// 重要的事情在这,设置了Message的target,这也是后面分发消息的关键

msg.target = this;

if (mAsynchronous) {

msg.setAsynchronous(true);

}

// 调用MessageQueue的成员方法将消息入队列

return queue.enqueueMessage(msg, uptimeMillis);

}

Handler的另一个任务是分发消息并交给合适的方法去处理消息,由Handler.dispatchMessage(Message msg)这个方法完成。

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

// 这里的callback就是个Runable对象,会执行其run()方法

handleCallback(msg);

} else {

if (mCallback != null) {

// mCallback是在创建Handler对象时设置的监听

if (mCallback.handleMessage(msg)) {

return;

}

}

// 这个方法很熟悉了,就是创建Handler时复写的,也是最常用的

handleMessage(msg);

}

}

3.2 Looper源码

Looper的代码非常少,先来看一下初始化方法,有两个重载Looper. prepare()与Looper. prepare(boolean quitAllowed)。

public static void prepare() {

// 调用的有参的那个构造方法

prepare(true);

}

/**

* @param quitAllowed 是否允许退出循环

*/

private static void prepare(boolean quitAllowed) {

// 检查是否已经初始化过了,从里可以看到`Looper.prepare()`在一个线程中只能调用一次

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

}

// 这里可以看到创建了一个Looper对象,并存入TLS

sThreadLocal.set(new Looper(quitAllowed));

}

接下来时Looper的构造方法,这个构造方法是私有的,也就是说我们在初始化Looper时只能通过Looper.prepare()这个方法。

private Looper(boolean quitAllowed) {

// 创建了消息队列,每个线程只有一个MessageQueue对象

mQueue = new MessageQueue(quitAllowed);

// 存储当前线程的引用

mThread = Thread.currentThread();

}

还有个prepareMainLooper()方法,也是用来初始化Looper的,这个方法只是为了UI线程使用,UI线程Looper的初始化是在ActivityThread的main()方法中进行的。我们可以通过Looper.getMainLooper().getThread()来获取UI线程。

接下来是Looper的消息循环方法Looper.loop(),这个方法去掉一些安全性验证与Log,核心代码也很短。

public static void loop() {

// 获取当前线程绑定的Looper

final Looper me = myLooper();

// 未初始化Looper

if (me == null) {

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

}

// 当前线程的消息队列

final MessageQueue queue = me.mQueue;

...

for (;;) {

// 从消息队列中取出下一个消息,没有则阻塞

Message msg = queue.next(); // might block

// 取到null,这是因为MessageQueue已经退出消息循坏

if (msg == null) {

return;

}

// 分发消息,这里的target就是Handler对象,在Handler中消息入队列时设置的

msg.target.dispatchMessage(msg);

...

// 将Message放入消息池

msg.recycleUnchecked();

}

}

Looper的任务就是创建一个循环器,不断的从消息队列取消息,交给Handler去分发。

3.3 MessageQueue源码

前面说Handler机制在native层与Java层都有实现,而Handler与Looper中都未出现native层的代码,其实是在MessageQueue中将Java层与native层联系了起来,这里只分析Java层实现,在做应用开发的时候也往往只使用Java层的Handler机制。

首先来看一下MessageQueue的构造方法,这个构造方法是包级权限,也就是说我们是无法在自定义的类中创建MessageQueue这个类的实例的。

MessageQueue(boolean quitAllowed) {

// 是否允许退出消息循环

mQuitAllowed = quitAllowed;

// mPtr是native层消息队列的头指针

mPtr = nativeInit();

}

在分析Handler发送消息的时候,可以看到最终都是将消息Message放入消息队列MessageQueue中,MessageQueue称为消息队列,其实现的数据结构却并不是真正的队列,而是一个单链表,在插入消息节点Message时按照时间点来确定位置。看下一下将消息入队列的MessageQueue.enqueueMessage(Message msg, long when)方法。

boolean enqueueMessage(Message msg, long when) {

// 如果没有target,消息无法被处理

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) {

msg.next = p;

mMessages = msg;

needWake = mBlocked;

} else { // 根据消息的时间将消息放入队列的合适位置,就是单链表的插入

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;

}

接下来看一下取出消息MessageQueue.next()方法,这个方法挺长的,而且与native层交互很多。

简单说下大概的逻辑:

1.消息循环已经退出,则返回null,在Looper.loop()方法结束循环

2.阻塞操作,等待nextPollTimeoutMillis到达,或者线程被唤醒未到下一次唤醒时间,则阻塞线程,等待唤醒,在MessageQueue.enqueueMessage(Message msg, long when)方法中我们看到了唤醒的操作。

3.将取到的消息的时间与当前时间做比较,若还未到处理时间,则设置下一次轮询的超时时间

4.取出一条消息返回

5.消息队列为空,设置下一次超时时间为-1,会使线程一直阻塞,等待唤醒

Message next() {

// step 1

final long ptr = mPtr;

if (ptr == 0) {

return null;

}

int pendingIdleHandlerCount = -1; // -1 only during first iteration

int nextPollTimeoutMillis = 0;

for (;;) {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

// step 2

nativePollOnce(ptr, nextPollTimeoutMillis);

// 查找下一条消息,找到则返回

synchronized (this) {

final long now = SystemClock.uptimeMillis();

Message prevMsg = null;

Message msg = mMessages;

if (msg != null && msg.target == null) {

// 忽略所有的同步消息

do {

prevMsg = msg;

msg = msg.next;

} while (msg != null && !msg.isAsynchronous());

}

if (msg != null) {

if (now < msg.when) { // step 3

// 下一条消息执行时间还未到,设置一个超时时间

nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

} else { // step 4

mBlocked = false;

if (prevMsg != null) {

prevMsg.next = msg.next;

} else {

mMessages = msg.next;

}

msg.next = null;

// 设置消息使用的标志位

msg.markInUse();

return msg;

}

} else {

// step 5

nextPollTimeoutMillis = -1;

}

// 执行退出消息,返回null

if (mQuitting) {

dispose();

return null;

}

// 消息队列为空或者消息未到执行时间,线程空闲,可以执行IdleHandlers

if (pendingIdleHandlerCount < 0

&& (mMessages == null || now < mMessages.when)) {

pendingIdleHandlerCount = mIdleHandlers.size();

}

if (pendingIdleHandlerCount <= 0) {

// 没有IdleHandlers执行,直接进入下一次循环继续等待消息

mBlocked = true;

continue;

}

if (mPendingIdleHandlers == null) {

mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];

}

mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

}

// Run the idle handlers.

// We only ever reach this code block during the first iteration.

for (int i = 0; i < pendingIdleHandlerCount; i++) {

final IdleHandler idler = mPendingIdleHandlers[i];

mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;

try {

keep = idler.queueIdle();

} catch (Throwable t) {

Log.wtf(TAG, "IdleHandler threw exception", t);

}

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

}

// 重置IdleHandler,不会被再次执行

pendingIdleHandlerCount = 0;

nextPollTimeoutMillis = 0;

}

}

3.4 Message源码

Message就是消息的实体类,也是消息队列MessageQueue的节点,除了具有一些消息的基本属性,还持有下一条消息的指针。

Message的构造方法是空的,其所有属性都是通过set方法来设置。在开发过程中,尽量使用obtain系列的方法来获取一个消息实例,内部通过消息池来实现,减少因为创建对象而造成的开销,以达到复用的效果。

obtain方法有许多重载,本质上都是调用无参的Message.obtain()方法从消息池中取出一个消息实例,设置不同的属性。

public static Message obtain() {

// 通过同步sPoolSync对象,将sPool加锁,保证线程安全

synchronized (sPoolSync) {

if (sPool != null) { // 消息池不为空,则从消息池中取出一个消息实例

Message m = sPool;

sPool = m.next;

m.next = null;

m.flags = 0; // 清楚使用标志位

sPoolSize--; // 消息池数量减1

return m;

}

}

// 如果消息池为空,则创建一个新的消息对象

return new Message();

}

既然有消息池,那么消息池中的消息是哪来的呢,Message中有个Message.recycle()方法,这个方法便是将消息放入消息池中。

public void recycle() {

if (isInUse()) { // 正在使用的消息无法回收

if (gCheckRecycle) {

throw new IllegalStateException("This message cannot be recycled because it "

+ "is still in use.");

}

return;

}

// 真正的实现回收消息

recycleUnchecked();

}

Message.recycleUnchecked()多了个uncheck,是真的不检查是否在使用,强行回收。

void recycleUnchecked() {

// 设置使用标志位

flags = FLAG_IN_USE;

// 清楚Message的所有属性

what = 0;

arg1 = 0;

arg2 = 0;

obj = null;

replyTo = null;

sendingUid = -1;

when = 0;

target = null;

callback = null;

data = null;

// 同步

synchronized (sPoolSync) {

// 如果消息池未满,将消息放入消息池中

if (sPoolSize < MAX_POOL_SIZE) {

next = sPool;

sPool = this;

sPoolSize++;

}

}

}

四、结束

大概记录了Handler机制在Java层的一个实现流程,从Handler发送消息,将消息加入到MessageQueue中,Looper不断的循环从MessageQueue中取出消息,将消息交给Handler处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值