概述
對于剛入門的同學來說,往往都會對Handler比較迷茫,到底Handler是個什么樣的東西。當然,可能對于一些有工作經驗的工程師來說,他們也不一定能很準確地描述,我們來看下API的介紹。
Handler是用來結合線程的消息隊列來發送、處理“Message對象”和“Runnable對象”的工具。每一個Handler實例之后會關聯一個線程和該線程的消息隊列。當你創建一個Handler的時候,從這時開始,它就會自動關聯到所在的線程/消息隊列,然后它就會陸續把Message/Runnalbe分發到消息隊列,并在它們出隊的時候處理掉。
從官方文檔中,我們不難找出其中的關鍵詞,就是“線程”。我們都知道,一個涉及到網絡操作,耗時操作等的Android應用,都離不開多線程操作,然而,如果這時我們允許并發更新UI,那么最終導致控件的狀態都是不可確定的。所以,我們可以通過對控件進行加鎖,在不需要用時解鎖,這是一個解決方案之一,但最后很容易造成線程阻塞,效率會非常差。所以,谷歌采用了只允許在主線程更新UI,所以作為線程通信橋梁的Handler也就應運而生了。
Looper、MessageQueue、Message、Handler的關系
講到Handler,肯定離不開Looper、MessageQueue、Message這三者和Handler之間的關系,下面簡略地帶過,詳細自己可以查閱相關資料,或者查看源碼,這樣更方便大家深入學習。
Looper
每一個線程只有一個Looper,每個線程在初始化Looper之后,然后Looper會維護好該線程的消息隊列,用來存放Handler發送的Message,并處理消息隊列出隊的Message。它的特點是它跟它的線程是綁定的,處理消息也是在Looper所在的線程去處理,所以當我們在主線程創建Handler時,它就會跟主線程唯一的Looper綁定,從而我們使用Handler在子線程發消息時,最終也是在主線程處理,達到了異步的效果。
那么就會有人問,為什么我們使用Handler的時候從來都不需要創建Looper呢?這是因為在主線程中,ActivityThread默認會把Looper初始化好,prepare以后,當前線程就會變成一個Looper線程。
MessageQueue
MessageQueue是一個消息隊列,用來存放Handler發送的消息。每個線程最多只有一個MessageQueue。MessageQueue通常都是由Looper來管理,而主線程創建時,會創建一個默認的Looper對象,而Looper對象的創建,將自動創建一個MessageQueue。其他非主線程,不會自動創建Looper。
Message
消息對象,就是MessageQueue里面存放的對象,一個MessageQueu可以包括多個Message。當我們需要發送一個Message時,我們一般不建議使用new Message()的形式來創建,更推薦使用Message.obtain()來獲取Message實例,因為在Message類里面定義了一個消息池,當消息池里存在未使用的消息時,便返回,如果沒有未使用的消息,則通過new的方式創建返回,所以使用Message.obtain()的方式來獲取實例可以大大減少當有大量Message對象而產生的垃圾回收問題。
四者關系總體如下(如有不對的地方,謝謝指出)
Handler的主要用途
- 推送未來某個時間點將要執行的Message或者Runnable到消息隊列。
- 在子線程把需要在另一個線程執行的操作加入到消息隊列中去。
廢話不多說,通過舉例來說明Handler的兩個主要用途。
1. 推送未來某個時間點將要執行的Message或者Runnable到消息隊列
實例:通過Handler配合Message或者Runnable實現倒計時
- 首先看一下效果圖
- 方法一,通過Handler + Message的方式實現倒計時。代碼如下:
public class MainActivity extends AppCompatActivity {private ActivityMainBinding mBinding;private Handler mHandler ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);//設置監聽事件mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//通過Handler + Message的方式實現倒計時for (int i = 1; i <= 10; i++) {Message message = Message.obtain(mHandler);message.what = 10 - i;mHandler.sendMessageDelayed(message, 1000 * i); //通過延遲發送消息,每隔一秒發送一條消息}}});mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);mBinding.time.setText(msg.what + ""); //在handleMessage中處理消息隊列中的消息}};}
}
其實代碼不用怎么解釋,都比較通俗易懂,但是這里用到了DataBiding,可能沒用過的同學看起來有點奇怪,但其實反而簡略了代碼,有一定基礎的同學看起來都不會有太大壓力,所以不做太多解釋。通過這個小程序,作者希望大家可以了解到Handler的一個作用就是,在主線程中,可以通過Handler來處理一些有順序的操作,讓它們在固定的時間點被執行。
- 方法二,通過Handler + Runnable的方式實現倒計時。代碼如下:
public class MainActivity extends AppCompatActivity {private ActivityMainBinding mBinding;private Handler mHandler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);//設置監聽事件mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {for (int i = 1; i <= 10; i++) {final int fadedSecond = i;//每延遲一秒,發送一個Runnable對象mHandler.postDelayed(new Runnable() {@Overridepublic void run() {mBinding.time.setText((10 - fadedSecond) + "");}}, 1000 * i);}}});}
}
方法二也是通過代碼讓大家加深Handler處理有序事件的用途,之所以分開Runnable和Message兩種方法來實現,是因為很多人都搞不清楚為什么Handler可以推送Runnable和Message兩種對象。其實,無論Handler將Runnable還是Message加入MessageQueue,最終都只是將Message加入到MessageQueue。只要大家看一下源碼就可以知道,Handler的post Runnable對象這個方法只是對post Message進行了一層封裝,所以最終我們都是通過Handler推送了一個Message罷了,至于為什么會分開兩種方法,下文會給大家詳說究竟。下面再來看看Handler的第二個主要用途。
2. 在子線程把需要在另一個線程執行的操作加入到消息隊列中去
實例:通過Handler + Message來實現子線程加載圖片,在UI線程顯示圖片
- 效果圖如下
- 代碼如下(布局代碼也不放出來了)
public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {private ActivityThreadBinding mBinding = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);// 設置點擊事件mBinding.clickBtn.setOnClickListener(this);mBinding.resetBtn.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {// 響應load按鈕case R.id.clickBtn:// 開啟一個線程new Thread(new Runnable() {@Overridepublic void run() {// 在Runnable中進行網絡讀取操作,返回bitmapfinal Bitmap bitmap = loadPicFromInternet();// 在子線程中實例化Handler同樣是可以的,只要在構造函數的參數中傳入主線程的Looper即可Handler handler = new Handler(Looper.getMainLooper());// 通過Handler的post Runnable到UI線程的MessageQueue中去即可handler.post(new Runnable() {@Overridepublic void run() {// 在MessageQueue出隊該Runnable時進行的操作mBinding.photo.setImageBitmap(bitmap);}});}}).start();break;case R.id.resetBtn:mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));break;}}/**** HttpUrlConnection加載圖片,不多說* @return*/public Bitmap loadPicFromInternet() {Bitmap bitmap = null;int respondCode = 0;InputStream is = null;try {URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(10 * 1000);connection.setReadTimeout(5 * 1000);connection.connect();respondCode = connection.getResponseCode();if (respondCode == 200) {is = connection.getInputStream();bitmap = BitmapFactory.decodeStream(is);}} catch (MalformedURLException e) {e.printStackTrace();Toast.makeText(getApplicationContext(), "訪問失敗", Toast.LENGTH_SHORT).show();} catch (IOException e) {e.printStackTrace();} finally {if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}}return bitmap;}
}
Handler推送Message和Runnable的區別
在上文我們通過用Handler推送Message和Runnable實現相同的倒計時效果,這里我們就說一下Post(Runnable)和SendMessage(Message)的區別。
首先我們看看post方法和sendMessage方法的源碼:
public final boolean post(Runnable r){return sendMessageDelayed(getPostMessage(r), 0);}
public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}
可見,兩個方法都是通過調用sendMessageDelayed方法實現的,所以可以知道它們的底層邏輯是一致的。
但是,post方法的底層調用sendMessageDelayed的時候,卻是通過getPostMessage(r)來將Runnable對象來轉為Message,我們點進方getPostMessage()法可以看到:
private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}
其實,最終runnable最終也是轉化為一個Message,而這個Message只有一個被賦值的成員變量,就是Runnable的回調函數,也就是說,這個Message在進入MessageQueue之后,它只是一個“動作”,即我們Runnbale的run方法里面的操作。
要知道,我們的Message類可是有很多參數的,所以你可以理解為它是一個非常豐富的JavaBean,可以看看它的成員變量:
- public int what;
- public int arg1;
- public int arg2;
- public Object obj;
- ...
那么講到這里,大家也應該有所理解為什么Google工程師為什么會封裝這兩種方法,我總結如為:為了更方便開發者根據不同需要進行調用。當我們需要傳輸很多數據時,我們可以使用sendMessage來實現,因為通過給Message的不同成員變量賦值可以封裝成數據非常豐富的對象,從而進行傳輸;當我們只需要進行一個動作時,直接使用Runnable,在run方法中實現動作內容即可。當然我們也可以通過Message.obtain(Handler h, Runnable callback)來傳入callback接口,但這樣看起來就沒有post(Ruannable callback)那么直觀。
API
API是我們學習最好的文檔,所以我也簡要跟大家學習一下,其實大家認真看我上面的介紹加上自己親手實踐,Handler的API大家都可以隨便翻閱了。
構造函數
- Handler()
- Handler(Handler.Callback callback):傳入一個實現的Handler.Callback接口,接口只需要實現handleMessage方法。
- Handler(Looper looper):將Handler關聯到任意一個線程的Looper,在實現子線程之間通信可以用到。
- Handler(Looper looper, Handler.Callback callback)
主要方法
- void dispatchMessage (Message msg)
一般情況下不會使用,因為它的底層實現其實是作為處理系統消息的一個方法,如果真要用,效果和sendMessage(Message m)效果一樣。
public void dispatchMessage(Message msg) {if (msg.callback != null) {// 如果有Runnbale,則直接執行它的run方法handleCallback(msg);} else {//如果有實現自己的callback接口if (mCallback != null) {//執行callback的handleMessage方法if (mCallback.handleMessage(msg)) {return;}}//否則執行自身的handleMessage方法handleMessage(msg);}}private static void handleCallback(Message message) {message.callback.run();}
- void dump (Printer pw, String prefix)
主要Debug時使用的一個方法,dump函數只是使用了Printer對象進行了打印,打印出Handler以及Looper和Queue中的一些信息,源碼如下:
public final void dump(Printer pw, String prefix) {pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());// 如果Looper為空,輸出Looper沒有初始化if (mLooper == null) {pw.println(prefix + "looper uninitialized");} else {// 否則調用Looper的dump方法,Looper的dump方法也是mLooper.dump(pw, prefix + " ");}}
通過測試用例大家會了解得更清晰:
//測試代碼Printer pw = new LogPrinter(Log.ERROR, "MyTag");mHandler.dump(pw, "prefix");
結果:
- Looper getLooper ()
拿到Handler相關聯的Looper
- String getMessageName (Message message)
獲取Message的名字,默認名字為message.what的值。
- void handleMessage (Message msg)
處理消息。
- boolean hasMessages (int what)
判斷是否有Message的what值為參數what。
- boolean hasMessages (int what, Object object)
判斷是否有Message的what值為參數what,obj值為參數object。
- Message obtainMessage (int what, Object obj)
從消息池中拿到一個消息并賦值what和obj,其他重載函數同理。
- boolean post (Runnable r)
將Runnable對象加入MessageQueue。
- boolean post (Runnable r)
將Runnbale加入到消息隊列的隊首。但是官方不推薦這么做,因為很容易打亂隊列順序。
- boolean postAtTime (Runnable r, Object token, long uptimeMillis)
在某個時間點執行Runnable r。
- boolean postDelayed (Runnable r, long delayMillis)
當前時間延遲delayMillis個毫秒后執行Runnable r。
- void removeCallbacks (Runnable r, Object token)
移除MessageQueue中的所有Runnable對象。
- void removeCallbacksAndMessages (Object token)
移除MessageQueue中的所有Runnable和Message對象。
- void removeMessages (int what)
移除所有what值得Message對象。
- boolean sendEmptyMessage (int what)
直接拿到一個空的消息,并賦值what,然后發送到MessageQueue。
- boolean sendMessageDelayed (Message msg, long delayMillis)
在延遲delayMillis毫秒之后發送一個Message到MessageQueue。
Handler引發的內存泄漏
在上面的例子中,為了展示方便,我都沒有考慮內存泄漏的情況,但是在實際開發中,如果不考慮代碼的安全性的話,尤其當一個項目到達了一定的規模之后,那么對于代碼的維護和系統的調試都是非常困難的。而Handler的內存泄漏在Android中也是一個非常經典的案例。
詳細可以參考:How to Leak a Context: Handlers & Inner Classes
或參考翻譯文:Android中Handler引起的內存泄露
通常我們都會在一個Activity內部定義一個Handler的內部類:
public class MainActivity extends AppCompatActivity {private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what){...}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {...}}, 1000000);}
}
(1)外部類Activity中定義了一個非靜態內部類Handler,非靜態內部類默認持有對外部類的引用。如果外部Activity突然關閉了,但是MessageQueue中的消息還沒處理完,那么Handler就會一直持有對外部Activty的引用,垃圾回收器無法回收Activity,從而導致內存泄漏。
(2) 如上代碼,在postDelayed中,我們在參數中傳入一個非靜態內部類Runnable,這同樣會造成內存泄漏,假如此時關閉了Activity,那么垃圾回收器在接下來的1000000ms內都無法回收Activity,造成內存泄漏。
解決方案:
(1) 將非靜態內部類Handler和Runnable轉為靜態內部類,因為非靜態內部類(匿名內部類)都會默認持有對外部類的強引用。
(2) 改成靜態內部類后,對外部類的引用設為弱引用,因為在垃圾回收時,會自動將弱引用的對象回收。
避免內存泄漏的例子:
public class HandlerActivity extends AppCompatActivity {private final MyHandler mHandler = new MyHandler(this);private static final Runnable mRunnable = new Runnable() {@Overridepublic void run() {// 操作}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_fourth);mHandler.postDelayed(mRunnable, 1000*10);finish(); }private static class MyHandler extends Handler {WeakReference<HandlerActivity> mWeakActivity;public MyHandler(HandlerActivity activity) {this.mWeakActivity = new WeakReference<HandlerActivity>(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);final HandlerActivity mActivity = mWeakActivity.get();if (mActivity != null) {// 處理消息}}}}
HandlerThread
思考一下,假如我們需要同時下載A和B,下載A需要6s,下載B需要5s,在它們下載完成后Toast信息出來即可,此時HandlerThread便是一種解決方式之一。那么HandlerThread到底是什么?
- HandlerThread就是一種線程。
- HandlerThread和普通的Thread之間的區別就是HandlerThread在創建的時候會提供自己該線程的Looper對象。
所以,如果大家了解清楚了我前面所講的Looper、Message、Handler、MessageQueue的關系的話,這里就很清楚HandlerThread是什么東西了。大家都知道,我們在Actvity創建時系統會自動幫我們初始化好主線程的Looper,然后這個Looper就會管理主線程的消息隊列。但是在我們創建子線程時,系統并不會幫我們創建子線程的Looper,需要我們自己手動創建,如下:
new Thread(){@Overridepublic void run() {super.run();Looper.prepare();Handler mHandler = new Handler(Looper.myLooper());Looper.loop();}}.start();
所以HandlerThread就在內部幫我們封裝了Looper的創建過程,從源碼可以看到,HandlerThread集成于Thread,然后覆寫run方法,進行Looper的創建,從而通過getLooper方法暴露出該線程的Looper對象
public class HandlerThread extends Thread {int mPriority;int mTid = -1;Looper mLooper;...@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}public Looper getLooper() {if (!isAlive()) {return null;}// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {}}}return mLooper;}...
}
所以通過HandlerThread,我們可以輕松創建一個包含了Looper的子線程:
final HandlerThread mHandlerThread = new HandlerThread("HandlerThread");mHandlerThread.start();Handler mHandler = new Handler(mHandlerThread.getLooper());
使用HandlerThread同時下載A和B的Demo:
代碼:
public class HandlerThreadActivity extends AppCompatActivity {private TextView tv_A, tv_B;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler_thread);tv_A = (TextView) findViewById(R.id.txt_dlA);tv_B = (TextView) findViewById(R.id.txt_dlB);final Handler mainHandler = new Handler();final HandlerThread downloadAThread = new HandlerThread("downloadAThread");downloadAThread.start();Handler downloadAHandler = new Handler(downloadAThread.getLooper());// 通過postDelayed模擬耗時操作downloadAHandler.postDelayed(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "下載A完成", Toast.LENGTH_SHORT).show();mainHandler.post(new Runnable() {@Overridepublic void run() {tv_A.setText("A任務已經下載完成");}});}}, 1000 * 5);final HandlerThread downloadBThread = new HandlerThread("downloadBThread");downloadBThread.start();Handler downloadBHandler = new Handler(downloadBThread.getLooper());// 通過postDelayed模擬耗時操作downloadBHandler.postDelayed(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "下載B完成", Toast.LENGTH_SHORT).show();mainHandler.post(new Runnable() {@Overridepublic void run() {tv_B.setText("B任務已經下載完成");}});}}, 1000 * 7);}
}
總結
由于Android的UI更新只能在主線程,所以Handler是Android中一套非常重要的更新UI線程機制,雖然在很多框架的幫助下我們可以減少了很多Handler的代碼編寫,但實際上很多框架的底層實現都是通過Handler來更新UI的,所以可見掌握好Handler對我們來說是多么重要,所以這也是很多面試官在面試中的高頻考點之一。雖然Handler對開發者來說是一個非常方便的存在,但是我們也不能否認它也是存在缺點的,如處理不當,Handler所造成的的內存泄漏對開發者來說也是一個非常頭疼的難題。