Handler是用來結合線程的消息隊列來發送、處理“Message對象”和“Runnable對象”的工具。每一個Handler實例之後會關聯一個線程和該線程的消息隊列。當你創建一個Handler的時候,從這時開始,它就會自動關聯到所在的線程/消息隊列,然後它就會陸續把Message/Runnalbe... ...
概述
對於剛入門的同學來說,往往都會對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 ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//設置監聽事件
mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public 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() {
@Override
public 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();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//設置監聽事件
mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 1; i <= 10; i++) {
final int fadedSecond = i;
//每延遲一秒,發送一個Runnable對象
mHandler.postDelayed(new Runnable() {
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
// 設置點擊事件
mBinding.clickBtn.setOnClickListener(this);
mBinding.resetBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
// 響應load按鈕
case R.id.clickBtn:
// 開啟一個線程
new Thread(new Runnable() {
@Override
public void run() {
// 在Runnable中進行網路讀取操作,返回bitmap
final Bitmap bitmap = loadPicFromInternet();
// 在子線程中實例化Handler同樣是可以的,只要在構造函數的參數中傳入主線程的Looper即可
Handler handler = new Handler(Looper.getMainLooper());
// 通過Handler的post Runnable到UI線程的MessageQueue中去即可
handler.post(new Runnable() {
@Override
public 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(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
...
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mHandler.postDelayed(new Runnable() {
@Override
public 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() {
@Override
public void run() {
// 操作
}
};
@Override
protected 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);
}
@Override
public 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(){
@Override
public 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;
...
@Override
public 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;
@Override
protected 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() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "下載A完成", Toast.LENGTH_SHORT).show();
mainHandler.post(new Runnable() {
@Override
public 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() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "下載B完成", Toast.LENGTH_SHORT).show();
mainHandler.post(new Runnable() {
@Override
public void run() {
tv_B.setText("B任務已經下載完成");
}
});
}
}, 1000 * 7);
}
}
總結
由於Android的UI更新只能在主線程,所以Handler是Android中一套非常重要的更新UI線程機制,雖然在很多框架的幫助下我們可以減少了很多Handler的代碼編寫,但實際上很多框架的底層實現都是通過Handler來更新UI的,所以可見掌握好Handler對我們來說是多麼重要,所以這也是很多面試官在面試中的高頻考點之一。雖然Handler對開發者來說是一個非常方便的存在,但是我們也不能否認它也是存在缺點的,如處理不當,Handler所造成的的記憶體泄漏對開發者來說也是一個非常頭疼的難題。