一、Handler訊息傳遞機制和深入認識(在一個執行緒中,可以有多個Handler嗎?):
(一)、引入:
子執行緒沒有辦法對UI介面上的內容進行操作,如果操作,將丟擲異常:CalledFromWrongThreadException
為了實現子執行緒中操作UI介面,Android中引入了Handler訊息傳遞機制,目的是打破對主執行緒的依賴性。
什麼是Handler?
handler通俗一點講就是用來在各個執行緒之間傳送資料的處理物件,再簡單點講就是一個用來處理不同執行緒間通訊的類。
在任何執行緒中,只要獲得了另一個執行緒的handler,則可以通過 handler.sendMessage(message)方法向那個執行緒傳送資料。基於這個機制,我們在處理多執行緒的時候可以新建一個thread,這個thread擁有UI執行緒中的一個handler。當thread處理完一些耗時的操作後通過傳遞過來的handler向UI執行緒傳送資料,由UI執行緒去更新介面。 主執行緒:執行所有UI元件,它通過一個訊息佇列來完成此任務。裝置會將使用者的每項操作轉換為訊息,並將它們放入正在執行的訊息佇列中。主執行緒位於一個迴圈中,並處理每條訊息。如果任何一個訊息用時超過5秒,Android將丟擲ANR。所以一個任務用時超過5秒,應該在一個獨立執行緒中完成它,或者延遲處理它,當主執行緒空閒下來再返回來處理它。
(二)、常用類:(Handler、Looper、Message、MessageQueue)
- Message:訊息,被傳遞和處理的資料。其中包含了訊息ID,訊息處理物件以及處理的資料等,由MessageQueue統一列隊,終由Handler處理。
- Handler:處理者,負責Message的傳送及處理。使用Handler時,需要實現handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。Handler類的主要作用:(有兩個主要作用)1)、在工作執行緒中傳送訊息;2)、在主執行緒中獲取、並處理訊息。
- MessageQueue:訊息佇列,本質是一個資料結構,用來存放Handler傳送過來的訊息,並按照FIFO規則執行。當然,存放Message並非實際意義的儲存,而是將Message串聯起來,等待Looper的抽取。
- Looper:訊息泵或迴圈器,不斷從MessageQueue中抽取Message。因此,一個MessageQueue需要一個Looper。
- Thread:執行緒,負責排程整個訊息迴圈,即訊息迴圈的執行場所。
(三)、Handler、Looper、Message、MessageQueue之間的關係:
- Looper和MessageQueue一一對應,建立一個Looper的同時,會建立一個MessageQueue;
- 而Handler與它們的關係,只是簡單的聚集關係,即Handler裡會引用當前執行緒裡的特定Looper和MessageQueue;
在一個執行緒中,只能有一個Looper和MessageQueue,但是可以有多個Handler,而且這些Handler可以共享一個Looper和MessageQueue;
- Message被存放在MessageQueue中,一個MessageQueue中可以包含多個Message物件。
【備註:】
Looper物件用來為一個執行緒開啟一個訊息迴圈,從而操作MessageQueue; 預設情況下,Android建立的執行緒沒有開啟訊息迴圈Looper,但是主執行緒例外。 系統自動為主執行緒建立Looper物件,開啟訊息迴圈; 所以主執行緒中使用new來建立Handler物件。而子執行緒中不能直接new來建立Handler物件就會異常。
子執行緒中建立Handler物件,步驟如下:
Looper.prepare();
Handler handler = new Handler() {
//handlemessage(){}
}
Looper.loop();
複製程式碼
(四)、Handler類中常用方法:
- handleMessage() 用在主執行緒中,構造Handler物件時,重寫handleMessage()方法。該方法根據工作執行緒返回的訊息標識,來分別執行不同的操作。
- sendEmptyMessage() 用在工作執行緒中,傳送空訊息。
- sendMessage() 用在工作執行緒中,立即傳送訊息。
(五)、Message訊息類中常用屬性:
- arg1 用來存放整型資料
- arg2 用來存放整型資料
- obj 用來存放Object資料
- what 用於指定使用者自定義的訊息程式碼,這樣便於主執行緒接收後,根據訊息程式碼不同而執行不同的相應操作。
【重點】:使用Message需要注意4點: 1、Message雖然也可以通過new來獲取,但是通常使用Message.obtain()或Handler.obtainMessage()方法來從訊息池中獲得空訊息物件,以節省資源; 2、如果一個Message只需要攜帶簡單的int型資料,應優先使用arg1和arg2屬性來傳遞資料,這樣比其他方式節省記憶體; 3、儘可能使用Message.what來標識資訊,以便用不同的方式處理Message; 4、如果需要從工作執行緒返回很多資料資訊,可以藉助Bundle物件將這些資料集中到一起,然後存放到obj屬性中,再返回到主執行緒。
(六)、示例程式碼一:
private Handler handler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text_main_info = (TextView) findViewById(R.id.text_main_info);
pDialog = new ProgressDialog(MainActivity.this);
pDialog.setMessage("Loading...");
image_main = (ImageView) findViewById(R.id.image_main);
// 主執行緒中的handler物件會處理工作執行緒中傳送的Message。根據Message的不同編號進行相應的操作。
handler = new Handler() {
public void handleMessage(android.os.Message msg) {
// 工作執行緒中要傳送的資訊全都被放到了Message物件中,也就是上面的引數msg中。要進行操作就要先取出msg中傳遞的資料。
switch (msg.what) {
case 0:
// 工作執行緒傳送what為0的資訊代表執行緒開啟了。主執行緒中相應的顯示一個進度對話方塊
pDialog.show();
break;
case 1:
// 工作執行緒傳送what為1的資訊代表要執行緒已經將需要的資料載入完畢。本案例中就需要將該資料獲取到,顯示到指定ImageView控制元件中即可。
image_main.setImageBitmap((Bitmap) msg.obj);
break;
case 2:
// 工作執行緒傳送what為2的資訊代表工作執行緒結束。本案例中,主執行緒只需要將進度對話方塊取消即可。
pDialog.dismiss();
break;
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 當工作執行緒剛開始啟動時,希望顯示進度對話方塊,此時讓handler傳送一個空資訊即可。
// 當傳送這個資訊後,主執行緒會回撥handler物件中的handleMessage()方法。handleMessage()方法中
// 會根據message的what種類來執行不同的操作。
handler.sendEmptyMessage(0);
// 工作執行緒執行訪問網路,載入網路圖片的任務。
byte[] data = HttpClientHelper.loadByteFromURL(urlString);
// 工作執行緒將網路訪問獲取的位元組陣列生成Bitmap點陣圖。
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
data.length);
// 工作執行緒將要傳送給主執行緒的資訊都放到一個Message資訊物件中。
// 而Message物件的構建建議使用obtain()方法生成,而不建議用new來生成。
Message msgMessage = Message.obtain();
// 將需要傳遞到主執行緒的資料放到Message物件的obj屬性中,以便於傳遞到主執行緒。
msgMessage.obj = bitmap;
// Message物件的what屬性是為了區別資訊種類,而方便主執行緒中根據這些類別做相應的操作。
msgMessage.what = 1;
// handler物件攜帶著Message中的資料返回到主執行緒
handler.sendMessage(msgMessage);
// handler再發出一個空資訊,目的是告訴主執行緒工作執行緒的任務執行完畢。一般主執行緒會接收到這個訊息後,
// 將進度對話方塊關閉
handler.sendEmptyMessage(2);
}
}).start();
}
複製程式碼
(七)、示例程式碼二:圖片定時切換
1、思路:利用多執行緒,子執行緒每隔2秒傳送一個訊息給主執行緒,主執行緒中Handler接收訊息,並更新ImageView中的圖片。這樣就實現了迴圈切換的動態效果。
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
image_main_pic.setImageResource(imageId[position++]);
if (position >= imageId.length) {
position = 0;
}
break;
default:
break;
}
}
};
// 第一種解決辦法:利用Thread和Thread的sleep
// new Thread(new Runnable() {
// @Override
// public void run() {
// while (flag) {
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// handler.sendEmptyMessage(0);
// }
// }
// }).start();
// 第二種解決辦法:利用Timer定時器和定時器的schedule()方法。
//schedule()方法中有三個引數:
/*第一個:表示定時任務TimerTask。 TimerTask 類實現了Runnable介面,所以要new TimerTask(),一定要實現run()方法。
第二個:表示第一次執行前的等待延遲時間;
第三個:表示兩次定時任務執行之間的間隔時間。*/
new Timer().schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(0);
//sendEmptyMessage()方法等同於以下幾句話。所以。如果只傳送一個what,就可以使用sendEmptyMessage()。這樣更簡單。
//Message message = Message.obtain();
// Message message2 = handler.obtainMessage();
//message.what = 0;
//handler.sendMessage(message);
}
}, 1, 1500);
複製程式碼
二、Handler、Looper原始碼分析:
(一)、Handler的概念:
- Handler是用於傳送和處理訊息和一個執行緒的MessageQueue相關聯的Runable物件。
- 每個Handler例項關聯到一個單一執行緒和執行緒的messagequeue。
- 當您建立一個Handler,從你建立它的時候開始,它就繫結到建立它的執行緒以及對應的訊息佇列,handler將傳送訊息到訊息佇列,並處理從訊息佇列中取出的訊息。
Handler的主要用途有兩個:
(1)、在將來的某個時刻執行訊息或一個runnable;
(2)、為執行在不同執行緒中的多個任務排隊。
主要依靠以下方法來完成訊息排程:
● post(Runnable)、
● postAtTime(Runnable, long)、
● postDelayed(Runnable, long)、
● sendEmptyMessage(int)、
● sendMessage(Message)、
● sendMessageAtTime(Message)、
● sendMessageDelayed(Message, long)
【備註:】
● post方法是當到Runable物件到達就被插入到訊息佇列;
● sendMessage方法允許你把一個包含有資訊的Message插入訊息佇列,它會在Handler的handlerMessage(Message)方法中執行(該方法要求在Handler的子類中實現)。
● 當Handler post或者send訊息的時候,可以在訊息佇列準備好的時候立刻執行,或者指定一個延遲處理或絕對時間對它進行處理,後兩個是實現了timeout、ticks或者其他timing-based的行為。
● 當你的應用建立一個程式時,其主執行緒(UI執行緒)會執行一個訊息佇列,負責管理優先順序最高的應用程式物件(Activity、廣播接收器等)和任何他們建立的windows。你也可以建立自己的執行緒,通過handler與主執行緒進行通訊,在新建立的執行緒中handler通過呼叫post或sendMessage方法,將傳入的Runnable或者Message插入到訊息佇列中,並且在適當的時候得到處理。
複製程式碼
(二)、Handler的用法:
當你例項化一個Handler的時候可以使用Callback介面來避免寫自定義的Handler子類。這裡的機制類似與Thread與runable介面的關係。 在Handler裡面,子類要處理訊息的話必須重寫handleMessage()這個方法,因為在handler裡面它是個空方法:
(三)、Handler、Looper、Message、MessageQueue訊息傳遞機制原始碼分析:
A、Handler.java:(3個屬性,10個方法)
3個屬性:
● final MessageQueue mQueue; 封裝好的Message被handler傳送出去,其實就是放到了MessageQueue訊息佇列中。
● final Looper mLooper; 每個handler都有一個looper為其不斷接收訊息佇列中的訊息,並返回給handler。
● final Callback mCallback; 被handler接收到的訊息要通過Callback介面中的handleMessage()方法來進行處理。
10個方法:
● public boolean handleMessage(Message msg); Callback介面中的handleMessage()方法,用來處理返回給handler的訊息的。
● public final Message obtainMessage() 獲取Message物件,其本質還是呼叫Message類的obtain()方法來獲取訊息物件。
● public final boolean sendMessage(Message msg) 傳送訊息
● public final boolean sendEmptyMessage(int what) 傳送只有what屬性的訊息
● public final boolean post(Runnable r) 傳送訊息
● public final boolean postAtTime(Runnable r, long uptimeMillis) 定時傳送訊息
● public final boolean postDelayed(Runnable r, long delayMillis) 延遲傳送訊息
● public void dispatchMessage(Message msg) 分發訊息。當Looper迴圈接收訊息佇列中的訊息時,就在不斷呼叫handler的分發訊息方法,從而觸發Callback介面中的訊息處理方法handleMessage()來對訊息進行處理。
● public final boolean sendMessageDelayed(Message msg, long delayMillis) sendMessage()就是在呼叫延時傳送訊息的方法。
● public boolean sendMessageAtTime(Message msg, long uptimeMillis) 延時傳送訊息的方法就是在呼叫定時傳送訊息的方法。
● private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 將訊息壓入訊息佇列中
B、MessageQueue.JAVA:(1個方法)
● final boolean enqueueMessage(Message msg, long when) handler傳送的訊息正是通過該方法被加進了訊息佇列中。因為訊息佇列中有訊息,從而觸發了Looper通過loop()方法迴圈接收所有的訊息。
C、Looper.JAVA:(3個屬性,5個方法)
3個屬性:
● static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 正是由於該執行緒本地變數,保證了一個執行緒中只能儲存一個Looper物件,也就是說一個Thread中只能有一個Looper物件。
● final MessageQueue mQueue; 正是由於MessagQueue中的訊息,才觸發了Looper的loop()方法。
● private static Looper mMainLooper = null; 該屬性是主執行緒中自動建立的Looper物件。
5個方法:
● public static void prepare() 每個handler所在的執行緒都必須有一個Looper提前準備好。
● public static void prepareMainLooper() 主執行緒中的的Looper已經被自動準備好。而該方法不需要手工呼叫。
● public static Looper getMainLooper() 獲取主執行緒中的Looper物件
● public static void loop() Looper的作用就是迴圈接收訊息佇列中的訊息。該方法中執行了一個無限迴圈。
● public static Looper myLooper() 該方法的作用是從執行緒本地變數中獲取到當前執行緒的Looper物件。
D、Message.java:(10個屬性,7個方法)
10個屬性:
● public int what; 該屬性一般用來標識訊息執行的狀態。
● public int arg1; 用來存放整型資料的屬性。
● public int arg2; 用來存放整型資料的屬性。
● public Object obj; 用來存放複雜訊息資料的屬性。
● Bundle data; 用來存放複雜訊息資料的屬性。
● Handler target; 標識該訊息要被髮送給哪個Handler物件。
● Message sPool; 訊息池物件。
● int sPoolSize; 記錄訊息池中剩餘訊息的數量。
● int MAX_POOL_SIZE=50; 訊息池中最大訊息數量。
● Messenger replyTo; 定義訊息的信使物件,將來可以用於跨APP的訊息傳遞。
7個方法:
● public static Message obtain() 從訊息池中獲取訊息物件。
● public void recycle() 往訊息池中歸還訊息物件。
● public void setTarget(Handler target) 給訊息設定接收該訊息的目標handler物件。
● public Handler getTarget() 獲取訊息的接收handler物件。
● public void sendToTarget() 將訊息傳送到目標handler物件。其本質是呼叫handler物件的sendMessage()方法來傳送當前訊息物件。
● public void setData(Bundle data) 將Bundle物件設定到message物件中Bundle屬性中。
● public Bundle getData() 從訊息物件中獲取Bundle屬性的資料。
複製程式碼