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<MainActivity>)
引用当前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呀?我们看一下下面这段代码
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<Looper> 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();
}
}
我们来看一下上面代码中几个关键的地方:
-
final Looper me = myLooper() ; final MessageQueue queue = me.mQueue;
获取到当前线程所绑定的Looper
对象,以及当前线程所关联的MessageQueue
; -
for (;;){}
开启死循环; -
Message msg = queue.next();
不断从MessageQueue
队列中取出消息,当next()
返回为空时,next()
中会休眠loop()
,至于为什么next()
方法能够休眠loop
,本文不会进行讲解,有兴趣的小伙伴可以看一下这一篇文章 链接
(注)如果有小伙伴想要了解为何Android中为什么主线程不会因为Looper.loop()里的死循环阻塞可以看一下这一篇文章 链接 -
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(r)
来将Runnable
对象来转为Message
。
3.最后都通过enqueueMessage
方法将Message
绑定到MessageQueue
中;并且将当前handler
保存到msg.target
中
4.这也解释了上文Looper类 注释1中为什么msg.target是handler的情况
。
接着我们再看一下MessageQueue是如何添加Message的
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
放入队列中,放入队列的过程是:
-
如果队列为空,或者当前处理的时间点为0(
when
的数值,when
表示Message
将要执行的时间点),或者当前Message
需要处理的时间点先于队列中的首节点,那么就将Message
放入队列首部,否则进行第2步。 -
遍历队列中
Message
,找到when
比当前Message
的when
大的Message
,将Message
插入到该Message
之前,如果没找到则将Message
插入到队列最后。 -
判断是否需要唤醒
loop()
,当loop()
处于休眠情况下,则调用nativeWake(mPtr)
方法,会调用native
层方法唤醒loop()
。 -
执行完后,会释放持有的
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
网友评论