【Android】Handler机制用法及源码解析

1.Android 中 Handler 的作用

在Android开发中,我们都知道不能在主线程中执行耗时的任务,避免产生 ANR 。那在子线程中处理完耗时操作要更新 在UI线程 中的界面该怎么办呢?
这里就引入了 Handler 的概念, Handler 的主要作用就是 线程间异步任务的回调通知 , 经常用在子线程中处理耗时操作后通知UI线程进行界面更新操作。

比如我们有一个需求: 界面上有一个按钮,点击后去下载一个很大的文件,下载完成后界面上的按钮需要消失。 首先下载文件是一个耗时的操作,我们必须开启一个子线程来处理这个未知完成时间的耗时操作,而让按钮消失必须要在UI线程中处理。这就产生了一个问题,子线程与主线程之间如何进行通信。而Handler就是用于解决这个问题的方法之一。

2.Handler 的使用:

Handler 的使用方式可以分为两种

1.通过 Handler + Message 的方式实现
2.通过 Handler + Runnable 的方式实现

下面我们就用demo来了解一下如何用Handler来解决上面描述的问题

2.1通过Handler + Message的解决上面描述的需求

package test.shop.cn.test;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {
private static final int NET_SUCCESS = 0;
private TextView mDownload;
private UIHandler mHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	mHandler = new UIHandler(this);
	mDownload = (TextView) findViewById(R.id.download);
	mDownload.setOnClickListener(new View.OnClickListener() {
		@Override
		public void onClick(View v) {
			ToDownloadFile();
		}
	});
}

/
* 模拟异步下载文件
*/
private void ToDownloadFile(){
//开启一个子线程
	Runnable mDownloadTask = new Runnable() {
		@Override
		public void run() {
			try{
				Thread.sleep(5000);//线程休眠5秒模拟下载操作
				Message msg = mHandler.obtainMessage(NET_SUCCESS);
				msg.obj = "从下载中获取的数据";
				mHandler.sendMessage(msg);
			}catch (InterruptedException e) {
			e.printStackTrace();
			}
		}
	};
	mDownloadTask.run();
}

private static class UIHandler extends Handler {
	WeakReference<MainActivity> mWeakRef;

	public UIHandler(MainActivity ref) {
		mWeakRef = new WeakReference<MainActivity>(ref);
	}

@Override
public void handleMessage(Message msg) {
	super.handleMessage(msg);
	MainActivity activity = mWeakRef.get();
	if (activity == null || activity.isFinishing()) {
		return;
	}
	switch (msg.what) {
		case NET_SUCCESS:
		String tip = (String)msg.obj;
		Toast.makeText(activity, tip, Toast.LENGTH_SHORT).show();
		activity.mDownload.setVisibility(View.GONE);
	break;
		default:
	break;
	}
}
}

@Override
protected void onDestroy() {
	super.onDestroy();
	if(mHandler != null){
		mHandler.removeCallbacksAndMessages(null);
		mHandler = null;
	}
}
}

可能会有很多小伙伴会有疑问,为什么上面的代码里要自定义一个 静态的UIHandler类继承Handler ,而不用系统 默认的Handler 呢? 并且为什么要将这个界面的 Activity 在初始化的时候传入,并用 弱引用( WeakReference) 引用当前Activity呢? 这是因为可能会存在 内存泄露 的问题,下面会详细说明

这个就是 系统默认使用handler的方式 ,直接在主线程中初始化:

private Handler mHandler = new Handler() {
	@Override
	public void handleMessage(Message msg) {
		super.handleMessage(msg);
		switch (msg.what) {
			case NET_SUCCESS:
			String tip = (String)msg.obj;
			Toast.makeText(MainActivity.this, tip, Toast.LENGTH_SHORT).show();
			mDownload.setVisibility(View.GONE);
			break;
		default:
			break;
		}
	}
};

这里回答上面的疑问 :我们知道在Java中, 非静态内部类会隐性地持有外部类的引用,静态内部类则不会。 在上面的代码中,我们在子线程中有5秒的耗时操作,如果用户在耗时操作还没完成的时候就 退出这个Activity ,就会产生 内存泄露 。 这是因为子线程持有Handler对象,Handler对象又隐性地持有了Activity的对象,当退出Activity发生GC内存回收时,因为Handler 持有Acitivity 的引用链导致Activity无法被回收,所以发生了内存泄露的问题。所以我们要在这里使用了静态内部类+弱引用持有当前Activity的方式,防止内存泄露问题,并且在onDestroy中及时清除消息。

2.2 通过Handler + Runnable的方式实现

public class MainActivity extends Activity{
private TextView mDownload;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDownload = (TextView) findViewById(R.id.download);
mDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ToDownloadFile();
}
});
}

private void ToDownloadFile(){
new Handler().postDelayed(new Runnable(){
@Override
public void run() {
mDownload.setVisibility(View.GONE);
}
}, 2000);
}
}

Handler + Runnable 的方式在实际需求开发中用到的比较少,之所以分开 Runnable和Message 两种方式来实现,是因为很多人都搞不清楚为什么Handler可以推送 Runnable和Message 两种对象。

其实,无论Handler将Runnable还是Message加入MessageQueue,最终都只是将Message加入到MessageQueue。Handler的post Runnable对象这个方法只是对post Message进行了一层封装,所以最终我们都是通过Handler推送了一个Message罢了。在下文源码中会对原理进行解析

3.Handler 的源码解析

文中贴出的源码删除了部分无关的代码,保留了重点部分,也会给出源码地址自行观看
在了解源码之前首先要对以下的关键对象有个初步的了解:

MessageQueue :消息队列,先进先出管理 Message ,当有消息的时候如果 loop() 处于休眠,则调用 nativeWake(mPtr) 方法唤醒 loop() 。在 Looper 对象初始化的时候会创建(new)一个与之关联的 MessageQueue ;

Looper :通过 TheadLocal 保证每个线程只能够有一个 Looper ,通过轮询管理 MessageQueue ,不断地从队列中取出 Message 分发给对应的 Handler 处理;(原理是用了 死循环 不断执行循环,没消息时阻塞休眠等待消息)

Handler :作用就是 发送 与 处理 信息,如果希望 Handler 正常工作,在当前线程中要有个 Looper 对象

Message : Handler 接收与处理的消息对象,可用来传递的 参数 与不同消息的 标识

TheadLocal : ThreadLocal 的作用是不同的线程拥有该线程独立的 变量 , 同名对象 不会被受到不同线程间相互使用出现异常的情况。
即:你的程序拥有多个线程,线程中要用到相同的对象,但又不允许线程之间操作同一份对象。那么就可以使用 ThreadLocal 来解决。每一个 ThreadLocal 能够放一个线程级别的变量,可是它本身能够被 多个线程共享使用 ,并且又能够达到 线程安全 的目的,且绝对线程安全。

初步了解完上述的关键对象后,我们开始从源码的角度来看是Handler 是如何实现线程间异步任务的回调通知

首先我们从Handler这个类看起 :Handler类的源码地址

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();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

从 Handler 源码中我们可以看到,在我们代码中初始化 Handler 对象的时候源码中就会初始化 Looper和MessageQueue对象 ,通过调用 Looper.myLooper() 获取当前线程绑定的 Looper对象 ( 由于我们是在onCreate方法中初始化的,所以此线程是UI线程 ),然后将该 Looper 对象保存到名为 mLooper 的成员字段中。

接着我们看一下Looper.myLooper()这个方法返回looper对象的原理 :Looper类的源码地址

Looper类中通过ThreadLocal保存了所有线程的Looper引用,并提供get和set方法 :

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

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

这里可能很多人会疑问,为什么Looper.myLooper()能够直接获取到looper对象而不是空值,我们并没有传looper呀?我们看一下下面这段代码

ActivityThread 类的源码地址

public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
//为当前线程(主线程)创建一个Looper对象
Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

if (sMainThreadHandler == null) {
//为当前线程设置Handler
sMainThreadHandler = thread.getHandler();
}

if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// 执行从消息队列中获取Message,并调用Handler进行处理的无限循环;
//所有和主线程相关的消息处理都在该方法中执行
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

这一段代码是 ActivityThread 类中的main方法,是Android应用的入口,每个 apk 中都包含一个且仅有一个 ActivityThread 类,并作为应用的主线程类。 ActivityThread 类管理主线程(UI线程)的执行,负责调度和运行应用中的 activity,广播,以及其他操作 。

上述代码涉及到了 Android进程的启动原理 ,这里不过多叙述,不是本章的重点,大家有个概念就行,在 apk启动 的时候就会执行 main 方法, main 方法中会初始化主线程中的 Looper 对象,并执行主线程 Looper对象的loop()方法 , 无限循环 处理主线程中消息队列中的 Message信息 ,这也解释了上面的问题,为什么 Looper.myLooper()方法 能够直接获取到主线程的 looper对象 ,是因为apk启动的时候就已经初始化好了 looper 对象并保存在 Looper 类中的 静态变量ThreadLocal sThreadLocal中

紧接着我们看一下Looper.loop();方法是如何处理MessageQueue中的消息的

public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;

for (;;) {
//取出下一条消息,当next()返回为空时,next()中会休眠loop()
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

try {
msg.target.dispatchMessage(msg);
} finally {
}
msg.recycleUnchecked();
}
}

我们来看一下上面代码中几个关键的地方:

  1. final Looper me = myLooper() ; final MessageQueue queue = me.mQueue;
    获取到当前线程所绑定的 Looper 对象,以及当前线程所关联的 MessageQueue ;

  2. for (;;){}
    开启死循环;

  3. Message msg = queue.next();
    不断从 MessageQueue 队列中取出消息,当 next() 返回为空时, next() 中会休眠 loop() ,至于为什么 next() 方法能够休眠 loop ,本文不会进行讲解,有兴趣的小伙伴可以看一下这一篇文章 链接
    (注)如果有小伙伴想要了解为何Android中为什么主线程不会因为Looper.loop()里的死循环阻塞可以看一下这一篇文章 链接

  4. msg.target.dispatchMessage(msg);
    msg 的 target 属性是Handler对象,从而就是调用Handler的 dispatchMessage 方法将当前要处理的 msg 分发出去。 (注1:下文中会说明为什么msg的target属性是Handler)

到此Looper的所有工作就已经结束了,然后我们看一下dispatchMessage()是如何分发消息的

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

/
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}

private static void handleCallback(Message message) {
message.callback.run();
}
  1. dispatchMessage 方法中首先会判断 msg.callback 存不存在, msg.callback 是 Runnable 类型,只有通过 上文demo中Handler + Runnable的方式实现 , msg.callback 才会存在,并回调 run() 方法

2.如果是以 上文demo中通过Handler + Message的方式实现 ,则在dispatchMessage(Message msg) 方法中会调用handleMessage(msg)方法; 源码中 handleMessage 方法是一个空方法,而在我们最开始的 demo 中我们重载了这方法

private static class UIHandler extends Handler {
WeakReference<MainActivity> mWeakRef;

public UIHandler(MainActivity ref) {
mWeakRef = new WeakReference<MainActivity>(ref);
}

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = mWeakRef.get();
if (activity == null || activity.isFinishing()) {
return;
}
switch (msg.what) {
case NET_SUCCESS:
String tip = (String)msg.obj;
Toast.makeText(activity, tip, Toast.LENGTH_SHORT).show();
activity.mDownload.setVisibility(View.GONE);
break;
default:
break;
}
}
}

到此 handler 的处理 message 的过程就结束了:
以 Handler + Message的方式 实现成功回调通知到我们重载的 handleMessage 方法中;
以 Handler + Runnable的方式 实现则成功回调 Runnable 的 run 方法;

Handler除了分发的过程之外,还有一个发送message到MessageQueue中的过程,下面是我们之前demo中的一段代码

/
* 模拟异步下载文件
*/
private void ToDownloadFile(){
//开启一个子线程
Runnable mDownloadTask = new Runnable() {
@Override
public void run() {
try{
Thread.sleep(5000);//线程休眠5秒模拟下载操作
Message msg = mHandler.obtainMessage(NET_SUCCESS);
msg.obj = "从下载中获取的数据";
mHandler.sendMessage(msg);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
};
mDownloadTask.run();
}

private void ToDownloadFile(){
new Handler().postDelayed(new Runnable(){
@Override
public void run() {
mDownload.setVisibility(View.GONE);
}
}, 5000);
}

从代码中我们看到,
1.我们创建了一个 message 对象,通过 mHandler 对象的 sendMessage 方法将 message 传递到 Handler 中;
2.我们创建了一个 Handler 对象,调用了 postDelayed 方法,传入了创建的 Runnable 对象
下面看一下源码中 sendMessage() 和 postDelayed() 是如何发送消息的

//以Message方式实现调用sendMessage方法
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//以Runnable 方式实现调用postDelayed方法
public final boolean postDelayed(Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
//通过getPostMessage(r)来将Runnable对象来转为Message
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//最后其实都是调用sendMessageAtTime方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;//当前线程的消息队列
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);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//将当前handler保存到msg.target中
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//将Message绑定到MessageQueue中
return queue.enqueueMessage(msg, uptimeMillis);
}

1.共同点是:从 Handler 源码中我们可以了解到,无论是上面说的 Handler+Message方式 还是 Handler+Runnable方式 发送消息,最终都会调到 sendMessageAtTime 方法。

2.区别不过是: post 方法的底层调用 sendMessageDelayed 的时候,却是通过 getPostMessage® 来将 Runnable 对象来转为 Message 。

3.最后都通过 enqueueMessage 方法将 Message 绑定到 MessageQueue 中;并且将当前 handler 保存到 msg.target 中

4.这也解释了 上文Looper类 注释1中为什么msg.target是handler的情况 。

接着我们再看一下MessageQueue是如何添加Message的
MessageQueue源码链接

boolean enqueueMessage(Message msg, long when) {
synchronized (this) { //同步锁
// 消息队列正在退出,回收 Message
if (mQuitting) {
msg.recycle();
return false;
}
//将消息添加到MessageQueue的具体操作
//每来一个新的消息,就会按照延迟时间的先后重新进行排序
msg.markInUse(); // 标记 Message 正在使用
msg.when = when; // 设置 Message 的触发时间
Message p = mMessages;// mMessages 记录着 MessageQueue 的队头的消息
boolean needWake;
// MessageQueue 没有消息、Message 触发时间为 0、Messgae 触发时间比队头 Message 早
// 总之这个 Message 在 MessageQueue 中需要最先被分发
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p; // 将以前的队头 Message 链接在这个 Message 后面
mMessages = msg; // 将这个 Message 赋值给 mMessages
needWake = mBlocked; // 队列是否阻塞
} else {
// 标记队列是否阻塞
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 按照时间顺序将 Message 插入消息队列
for (;;) {
prev = p; // prev 记录队头
p = p.next; // p 记录队头的后一个
// 队头后面没有消息或者其触发事件比要插入的 Message 晚,跳出循环
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 将 Message 插入队列
msg.next = p;
prev.next = msg;
}
//如果Looper.loop()是休眠状态
//则调用native方法唤醒loop()
//---重点---Looper的唤醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

整个 enqueueMessage 方法的过程就是先持有 MessageQueue.this 锁,然后将 Message 放入队列中,放入队列的过程是:

  1. 如果队列为空,或者当前处理的时间点为0( when 的数值, when 表示 Message 将要执行的时间点),或者当前 Message 需要处理的时间点先于队列中的首节点,那么就将 Message 放入队列首部,否则进行第2步。

  2. 遍历队列中 Message ,找到 when 比当前 Message 的 when 大的 Message ,将 Message 插入到该 Message 之前,如果没找到则将 Message 插入到队列最后。

  3. 判断是否需要唤醒 loop() ,当 loop() 处于休眠情况下,则调用 nativeWake(mPtr) 方法,会调用 native 层方法唤醒 loop() 。

  4. 执行完后,会释放持有的 MessageQueue.this 的锁。这样整个 enqueueMessage 方法算是完了。

4.到此整个Handler+Message+MessageQueue+Looper的源码已经全部讲完了
总结一下原理,整个过程就好比是一个工厂的流水线,MessageQueue是一条传送带,Message是个货物,传送带的动力是Looper,Handler负责从子线程将Message放到传送带上,并且在传送带终点主线程中将货物取走。

*备注:在本文中并未对native中以下两点进行说明:

1. MessageQueue 中 nativeWake(mPtr) 方法是如何唤醒 Looper
2.以及上文提到的 Looper 类中 queue.next() 方法为空时是如何休眠 loop() 的
这两点设计到了 native层 的原理,以下两个链接会对这两点进行详细的说明,有兴趣的同学可以了解一下*

https://www.2cto.com/kf/201610/556770.html
https://www.jianshu.com/p/4fac27284475

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值