1 引出问题
在启动一个Android应用的同时,便会开启一个主线程——Main Thread(也叫UI线程),主线程负责处理与UI相关的事件。
但是在开发中经常会做一些耗时的任务,这些耗时的任务会阻塞主线程,若长时间地阻塞主线程则会导致应用发生ANR(应用程序无响应)。
因此我们需要将这些耗时任务放在子线程中去处理,并且在处理耗时任务的过程中,我们需要更新UI以便告知用户耗时任务的进度、状态等信息。
那么如何在子线程中更新主线程中的UI控件呢?
对此,我们可以借助Handler来完成。Handler提供了三种方式来解决上述问题:
- 调用Handler的sendMessage方法;
- 调用Handler的post方法;
- 调用Handler的obtainMessage方法;
2 Handler的简单使用
2.1 调用sendMessage方法
为了更好地理解Handler的使用,我们创建一个Demo来做一个简单的演示示例。点击屏幕上的按钮开始执行任务,同时文本框显示“开始执行任务”,用休眠5秒钟模拟执行耗时任务,当任务执行完(休眠结束后),文本框显示“任务执行完毕”。
布局文件代码如下:
MainActivity的代码编写如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MA-cxs";
//工具提示文本框
private TextView tooltipTv;
//开始执行任务按钮
private Button startExecute;
//是否开始执行
private boolean isExecute = false;
public final int MSG_EXECUTE_START = 1000;
public final int MSG_EXECUTE_COMPLETE = 1001;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
/**
* 初始化控件
*/
private void initViews() {
tooltipTv = findViewById(R.id.tooltip_tv);
startExecute = findViewById(R.id.start_execute_btn);
startExecute.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.start_execute_btn) {
if (!isExecute) {
new MyThread().start();
}
}
}
/**
* 创建一个执行耗时任务的子线程,并发送消息
*/
class MyThread extends Thread {
@Override
public void run() {
isExecute = true;
Log.d(TAG, "子线程开始执行");
//发送消息给Handler
mExecuteTaskHandler.sendEmptyMessage(MSG_EXECUTE_START);
//借助休眠模拟执行任务的过程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行完任务后再次发送一个执行成功的消息
Message message = new Message();
//此处也可设置message.arg1、message.arg2、message.obj、message.setData(Bundle对象)方法
message.what = MSG_EXECUTE_COMPLETE;
message.setData(new Bundle());
mExecuteTaskHandler.sendMessage(message);
isExecute = false;
Log.d(TAG, "子线程执行完毕");
}
}
//接收消息并进行处理
private Handler mExecuteTaskHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_EXECUTE_START:
Log.d(TAG, "收到开始执行任务的消息");
tooltipTv.setText("开始执行任务");
break;
case MSG_EXECUTE_COMPLETE:
Log.d(TAG, "收到任务执行完毕的消息");
tooltipTv.setText("任务执行完毕");
break;
}
}
};
}
Handler的使用步骤总结:
1.发送消息:在执行耗时任务时发送消息给Handler;
2.接收消息并进行处理:在主线程(UI线程)中创建一个Handler对象,并实现其handleMessage()方法,并且根据message参数(what、arg1、arg2或obj)的不同进行相应的处理——更新UI。
设置Message除了可以设置其what、arg1及arg2之外,还可以借助Message的setData方法,传入一个Bundle对象。
发送消息的方法除了有sendMessage外还有其他的方法:
修饰符和返回值类型 | 方法及其描述 |
---|---|
public final boolean | sendEmptyMessage(int what) 发送仅包含what值的消息 |
public final boolean | sendEmptyMessageAtTime(int what, long uptimeMillis) 发送仅包含what值并且在指定的绝对时间传递的消息 |
public final boolean | sendEmptyMessageDelayed(int what, long delayMillis ) 仅包含what值的消息,并且该消息将在延迟指定的时间后发送 |
public final boolean | sendMessage(Message msg) 将消息放在当前待处理的消息队列的末尾 |
public final boolean | sendMessageAtFrontOfQueue(Message msg) 将消息放入消息队列的最前面,以在消息循环的下一次迭代中进行处理 |
public boolean | sendMessageAtTime(Message msg, long uptimeMillis) 在指定的绝对运行时间发送消息 |
public final boolean | sendMessageDelayed(Message msg, long delayMillis) 在延迟指定的时间后发送消息 |
对于延时、定时消息,有时候需要取消,则可以通过以下方法将指定消息移除:
修饰符和返回值类型 | 方法及其描述 |
---|---|
public final void | removeCallbacksAndMessages(Object token) 移除obj为token的任何待处理的回调及已发送的消息 |
public final void | removeMessages(int what) 删除消息队列中为what参数为what值的待处理的消息 |
public final void | removeMessages(int what, Object object) 删除消息队列中为what参数what值并且obj参数为object的待处理的消息 |
2.2 调用post方法
则改写上述代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//其余代码不变
@Override
public void onClick(View view) {
if (view.getId() == R.id.start_execute_btn) {
if (!isExecute) {
//new MyThread().start();
new PostThread().start();
}
}
}
class PostThread extends Thread {
@Override
public void run() {
isExecute = true;
Log.d(TAG, "PostThread run(): ThreadId=" + Thread.currentThread().getId() +
", ThreadName=" + Thread.currentThread().getName());
//发送消息给Handler
mExecuteTaskHandler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Runnable run(): ThreadId=" + Thread.currentThread().getId() +
", ThreadName=" + Thread.currentThread().getName());
tooltipTv.setText("开始执行任务");
}
});
//借助休眠模拟执行任务的过程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行完任务后再次发送一个执行成功的消息
Message message = new Message();
//此处也可设置message.arg1、message.arg2、message.obj、message.setData(Bundle对象)方法
message.what = MSG_EXECUTE_COMPLETE;
message.setData(new Bundle());
mExecuteTaskHandler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Runnable run(): ThreadId=" + Thread.currentThread().getId() +
", ThreadName=" + Thread.currentThread().getName());
tooltipTv.setText("任务执行完毕");
}
});
isExecute = false;
}
}
}
运行应用程序后点击按钮,打印日志如下:
PostThread run(): ThreadId=154, ThreadName=Thread-2
Runnable run(): ThreadId=2, ThreadName=main
Runnable run(): ThreadId=2, ThreadName=main
总结:
从上面代码并结合日志可以看出:Handler的post方法参数为一个Runnable对象,由于Handler是在主线程中创建的,因此,Runnable也是在主线程中运行,则Runnable与创建它的线程无关,与调用post方法的线程无关。并且Runnable的run方法是在主线程中更新UI的。与sendMessage方法类似,post方法也有多个相似的方法:
修饰符和返回值类型 | 方法及其描述 |
---|---|
public final boolean | post(Runnable r) 将Runnable对象添加到消息队列中 |
public final boolean | postAtFrontOfQueue(Runnable r) 将Runnable对象添加到消息队列的最前面 |
public final boolean | postAtTime(Runnable r, long uptimeMillis) 将Runnable对象添加到消息队列中,并且在指定的绝对时间执行 |
public final boolean | postAtTime(Runnable r, Object token, long uptimeMillis) 同上 |
public final boolean | postDelayed(Runnable r, long delayMillis) 将Runnable对象添加到消息队列中,并在经过指定的时间后运行 |
public final boolean | postDelayed(Runnable r, Object token, long delayMillis) 同上 |
同sendMessage方法,可以通过removeCallbacks(Runnable r)
、removeCallbacks(Runnable r, Object token)
及removeCallbacksAndMessages(Object token)
方法取消post定时、延时处理的Runnable。
2.3 调用obtainMessage方法
obtainMessage方法与sendMessage方法类似,也可以看成是一种。通过下面的代码就能看出这一点:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//其余代码不变
@Override
public void onClick(View view) {
if (view.getId() == R.id.start_execute_btn) {
if (!isExecute) {
// new MyThread().start();
// new PostThread().start();
new ObtainThread().start();
}
}
}
class ObtainThread extends Thread {
@Override
public void run() {
isExecute = true;
Log.d(TAG, "PostThread run(): ThreadId=" + Thread.currentThread().getId() +
", ThreadName=" + Thread.currentThread().getName());
//发送消息给Handler
mExecuteTaskHandler.obtainMessage(MSG_EXECUTE_START).sendToTarget();
//借助休眠模拟执行任务的过程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行完任务后再次发送一个执行成功的消息
mExecuteTaskHandler.obtainMessage(MSG_EXECUTE_COMPLETE).sendToTarget();
isExecute = false;
}
}
}
总结:
通过调用obtainMessage方法即可生成Message对象,此对象携带其target对象,通过调用sendToTarget方法即可将消息发送到Handler的消息队列中,然后再由Handler的handleMessage方法进行处理。
3 Handler的官方定义
说了这么多,那么到底什么是Handler呢?
Android API是这样给Handler定义的:Handler可以用来发送和处理与线程的MessageQueue关联的Message和Runnable对象。每个Handler实例都与一个线程和该线程的消息队列相关联。Handler在创建时便被绑定到正在创建它的线程或MessageQueue上,然后Handler会把Message和Runnable对象传递到MessageQueue中,并在它们从MessageQueue中出来时执行它们。
Handler主要有两个用途(按自己理解进行翻译的):
原文:There are two main uses for a Handler:
(1) to schedule messages and runnables to be executed at some point in the future;
(2) to enqueue an action to be performed on a different thread than your own.
- 在将来的某个时间点执行计划好的消息和Runnable对象;
- 创建自己的线程(执行耗时任务)并通过Handler与应用的主线程进行通讯。
4 Handler具体实现解析
首先来看一下Handler的通信机制。具体如下图所示:
- 创建Handler,并采用当前线程的Looper创建消息循环系统;
- 调用Handler的sendMessage(Message)或post(Runnable)发送消息,调用enqueueMessage方法将消息插入到消息链表中;
- Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage方法里执行。
4.1 Handler、Looper与MessageQueue的构建
首先来看Handler构造方法:
public Handler() {
this(null, false);
}
发现其调用的带参构造方法,如下所示:
public Handler(@Nullable 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());
}
}
//1. 得到主线程的Looper对象
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//2. 得到主线程的Looper中的MessageQueue,因为构建Looper的同时会创建一个MessageQueue,这里的mQueue和4.2.2的步骤3中的mQueue是对应的。
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
下面补充一下主线程Looper对象时何时初始化的:
这个Looper对象是何时创建的?其实在程序启动时,借助ActivityThread的main方法初始化了一个Looper对象,即主线程的Looper,如下所示的prepareMainLooper方法
public static void main(String[] args) {
···
//创建主线程的Looper对象
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
···
}
再来看Looper的prepareMainLooper方法及prepare方法,将创建的Looper对象存储到ThreadLocal当中:
ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象。
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal sThreadLocal = new ThreadLocal();
public static void prepareMainLooper() {
prepare(false);
···
}
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时,首先会创建一个消息队列MessageQueue,并获取当前的线程。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
通过Looper.myLooper();
获得主线程的Looper对象,从ThreadLocal中获取存储的Looper对象。
@Nullable
public static Looper myLooper() {
return sThreadLocal.get();
}
关于ThreadLocal如何保存和获取Looepr请参考博客: (转)Android Handler 使用详解
4.2 Handler发送消息
首先来看一下Handler的sendMessage方法,代码如下:
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
然后进入sendMessageDelayed方法。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
再进入sendMessageAtTime方法,如下所示,sendMessageAtTime方法将穿过来的Message与Handler的mQueue(MessageQueue)通过enqueueMessage方法进入队列。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
//3.发送消息,这里和4.2.1中步骤2对应
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);
}
在enqueueMessage方法中,首先为message的target赋值为当前的Handler对象,然后通过MessageQueueenqueueMessage入队。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//4.为message的target赋值为当前的Handler对象并入队
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
对于post(Runnable)而言,首先通过post方法传递该Runnable对象,然后调用getPostMessage静态方法构造一个Message对象,在通过sendMessageDelayed方法传递消息,后面和sendMessage流程就一样了。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
4.3 消息入队列
然后进入MesssageQueue类中的方法中,代码如下(水平有限,这里不做具体分析,就是消息入队列的过程
):
//5.消息入队列
boolean enqueueMessage(Message msg, long when) {
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) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
4.4 Looper处理消息
接下来看Looper在loop方法中处理消息,若消息为空,则返回,否则取出消息,并通过msg.target.dispatchMessage方法回调到Handler中去。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
// 6.从Looper中的MessageQueue中取出Message
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
//7.若消息不为空,则通过调用mag.target获取Handler对象并调用其dispatchMessage回调到Handler中去
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
4.5 Handler处理消息
最后再回到Handler类当中,看一下dispatchMessage方法:若该消息有callback,即通过post(Runnable)方式发送的消息,因为在发送Runnable对象时,把Runnable对象赋值给了message的callback,则交由handleCallback方法处理;否则交由handleMessage方法处理,在使用Handler时重写handleMessage方法即可。
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//8.调用handleMessage来处理消息
handleMessage(msg);
}
}
总结:
- 通过上一部分的源码分析,可以发现,当启动一个Android应用时,会自动创建一个给应用的主线程使用的Looper实例,像Android中的一些事件(如按钮点击等)、生命周期方法的调用等都会被放入到消息中,Looper的主要工作就是挨个处理消息队列中的消息对象。
- 在通过Handler发送消息并将消息放入消息队列的同时会将Message的target赋值为当前的Handler对象,这样Looper在处理该消息时,才可以调用Handler的handleMessage方法完成消息的处理。
5 Handler存在的问题及改进方法
5.1 存在的问题
存在的问题1
在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,例如当Activity finish时,Handler可能并未执行完,这样可能会造成Activity的内存泄漏。
存在的问题2
当Activity finish时,在onDestroy方法中释放了一些资源,若此时Handler在执行handleMessage方法时,会由于相关资源被释放而引起空指针异常。
5.2 改进方法
那么如何避免上述问题呢?
- 针对内存泄漏,我们可以使用静态内部类,这是因为静态内部类不会持有外部类的引用,因此不会导致外部类实例的内存泄漏。而当我们需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理,如下代码所示:
ExecuteTaskHandler mExecuteTaskHandler = new ExecuteTaskHandler(MainActivity.this);
/**
* 为避免handler造成的内存泄漏
* 1、使用静态的handler,对外部类不保持对象的引用
* 2、但Handler需要与Activity通信,所以需要增加一个对Activity的弱引用
*/
private static class ExecuteTaskHandler extends Handler {
private final WeakReference mActivityReference;
ExecuteTaskHandler(Activity activity) {
this.mActivityReference = new WeakReference(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
MainActivity mainActivity = (MainActivity) mActivityReference.get();
switch (msg.what) {
case MSG_EXECUTE_START:
Log.d(TAG, "收到开始执行任务的消息");
mainActivity.tooltipTv.setText("开始执行任务");
break;
case MSG_EXECUTE_COMPLETE:
Log.d(TAG, "收到任务执行完毕的消息");
mainActivity.tooltipTv.setText("任务执行完毕");
break;
}
}
}
- 针对可能造成的空指针异常,我们可以通过添加try catch来解决
- 若是使用handleMessage,则在该方法中添加try catch;
- 若是调用的post方法则在Runnable方法中添加try catch;
当然,最好不加try catch,而是在onDestroy中把消息队列的消息remove掉。
@Override
protected void onDestroy() {
super.onDestroy();
//避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列
handler.removeCallbacks(postRunnable); //清除runnable对应的message
//handler.removeMessage(what) 清除what对应的message
}
参考资料:
- Android Handler的基本使用
- Handler | Android Developers
- Android Handler详解
- (转)Android Handler 使用详解