在启动一个Android应用的同时,便会开启一个主线程——Main Thread(也叫UI线程),主线程负责处理与UI相关的事件。java
可是在开发中常常会作一些耗时的任务,这些耗时的任务会阻塞主线程,若长时间地阻塞主线程则会致使应用发生ANR(应用程序无响应)。android
所以咱们须要将这些耗时任务放在子线程中去处理,而且在处理耗时任务的过程当中,咱们须要更新UI以便告知用户耗时任务的进度、状态等信息。算法
那么如何在子线程中更新主线程中的UI控件呢?
对此,咱们能够借助Handler来完成。Handler提供了三种方式来解决上述问题:shell
为了更好地理解Handler的使用,咱们建立一个Demo来作一个简单的演示示例。点击屏幕上的按钮开始执行任务,同时文本框显示“开始执行任务”,用休眠5秒钟模拟执行耗时任务,当任务执行完(休眠结束后),文本框显示“任务执行完毕”。
布局文件代码以下:并发
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/tooltip_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/start_execute_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始执行" /> </LinearLayout>
MainActivity的代码编写以下:less
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.arg一、message.arg二、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、arg一、arg2或obj)的不一样进行相应的处理——更新UI。async
设置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的待处理的消息 |
则改写上述代码以下:ide
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.arg一、message.arg二、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。
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方法进行处理。
说了这么多,那么到底什么是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.
首先来看一下Handler的通讯机制。具体以下图所示:
首先来看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对象及MessageQUeue对象(详见该部分的补充分析) 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<Looper> sThreadLocal = new ThreadLocal<Looper>(); 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 使用详解
首先来看一下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); }
而后进入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) { //若已经调用了quit,这里便会抛出异常 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; }
接下来看Looper在loop方法中处理消息,若消息为空,则返回,不然取出消息,并经过msg.target.dispatchMessage方法回调到Handler中去。
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; // 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) { // 若消息不为空,则MessageQueue不会被quit 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(); } }
再来看MessageQueue的next()方法作了什么:
Message next() { ...// 仅贴出关键代码 for (;;) { synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null) { if (now < msg.when) { // 若是还没处处理消息的时候,就继续循环 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 把这个消息取出来返回给 looper 进行处理 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { nextPollTimeoutMillis = -1; } // 若是使用了 quit,这里就会返回 null,而后当前线程的 looper 循环也就结束了 if (mQuitting) { dispose(); return null; } } }
最后再回到Handler类当中,看一下dispatchMessage方法:若该消息有callback,即经过post(Runnable)方式发送的消息,由于在发送Runnable对象时,把Runnable对象赋值给了message的callback,则交由handleCallback方法处理;不然交由handleMessage方法处理,在使用Handler时重写handleMessage方法便可。
/** * Handle system messages here. */ public void dispatchMessage(@NonNull Message msg) { //若使用post方法发送的消息,则会在这里执行 if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { // 若使用 Handler(new Handler.Callback()) 建立的handler对象, // 则会在它的回调函数handleMessage内处理消息 if (mCallback.handleMessage(msg)) { return; } } //8.调用handleMessage来处理消息 //使用Handler()建立的Handler对象则会在此到处理消息 handleMessage(msg); } }
总结:
存在的问题1
在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,例如当Activity finish时,Handler可能并未执行完,这样可能会形成Activity的内存泄漏。
存在的问题2
当Activity finish时,在onDestroy方法中释放了一些资源,若此时Handler在执行handleMessage方法时,会因为相关资源被释放而引发空指针异常。
那么如何避免上述问题呢?
ExecuteTaskHandler mExecuteTaskHandler = new ExecuteTaskHandler(MainActivity.this); /** * 为避免handler形成的内存泄漏 * 一、使用静态的handler,对外部类不保持对象的引用 * 二、但Handler须要与Activity通讯,因此须要增长一个对Activity的弱引用 */ private static class ExecuteTaskHandler extends Handler { private final WeakReference<Activity> mActivityReference; ExecuteTaskHandler(Activity activity) { this.mActivityReference = new WeakReference<Activity>(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,而是在onDestroy中把消息队列的消息remove掉。
@Override protected void onDestroy() { super.onDestroy(); //避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列 handler.removeCallbacks(postRunnable); //清除runnable对应的message //handler.removeMessage(what) 清除what对应的message }
参考资料: