在啓動一個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 }
參考資料: