Android:用Handler實現(xiàn)異步處理功能
一.一個問題
有這樣一個問題值得我們思考,若把一些類似于下載的功能(既耗時且不一定有結(jié)果)寫在Activity(主線程)里,會導(dǎo)致Activity阻塞,長時間無響應(yīng),直至頁面假死(如果5秒鐘還沒有完成的話,會收到Android系統(tǒng)的一個錯誤提示 "強(qiáng)制關(guān)閉")。因此,我們需要把這些耗時的操作放在單獨的子線程中操作。這就是Handler的使命。Handler提供異步處理的功能,發(fā)送和接收不是同時的(Activity的主線程和線程隊列里的線程是不同的線程,并行進(jìn)行,互不影響)。
二.Handler簡介
Handler 為Android操作系統(tǒng)中的線程通信工具,它主要由兩個作用:(1)安排消息或Runnable 在某個主線程中某個地方執(zhí)行(2)安排一個動作在另外的線程中執(zhí)行。每個Handler對象維護(hù)兩個隊列(FIFO),消息隊列和Runnable隊列, 都是有Android操作系統(tǒng)提供的。Handler可以通過這兩個隊列來分別:
- 發(fā)送、接受、處理消息–消息隊列;
- 啟動、結(jié)束、休眠線程–Runnable隊列;
Handler的使用方法大體分為3個步驟:1.創(chuàng)建Handler對象。2.創(chuàng)建Runnable和消息。3.調(diào)用post以及sendMessage方法將Runnable和消息添加到隊列。
三.Runnable隊列
1.java中的線程
在java中,線程的創(chuàng)建有兩種方法:繼承Thread類和實現(xiàn)Runnable接口。而這最重要的都是要復(fù)寫run方法來實現(xiàn)線程的功能。當(dāng)線程的時間片到了,開始運行時,就執(zhí)行run()函數(shù),執(zhí)行完畢,就進(jìn)入死亡狀態(tài)。
舉個創(chuàng)建線程的例子:
- Runnable r=new Runnable(){
- @Override
- public void run() {
- // TODO Auto-generated method stub
- System.out.println("thread");
- handler.postDelayed(thread, 3000);
- }
- };
2.關(guān)于Runnable隊列
(1)原理
Android的線程異步處理機(jī)制:Handler對象維護(hù)一個線程隊列,有新的Runnable送來(post())的時候,把它放在隊尾,而處理 Runnable的時候,從隊頭取出Runnable執(zhí)行。當(dāng)向隊列發(fā)送一個Runnable后,立即就返回,并不理會Runnable是否被執(zhí)行,執(zhí)行 是否成功等。而具體的執(zhí)行則是當(dāng)排隊排到該Runnable后系統(tǒng)拿來執(zhí)行的。這就好比郵局的例子。寄信者將信寫好后放入郵筒就回家了,他并不知道郵件何 時被郵局分發(fā),何時寄到,對方怎樣讀取這些事。這樣,就實現(xiàn)了Android的異步處理機(jī)制。
(2)具體操作
向隊列添加線程:
handler.post(Runnable );將Runnable直接添加入隊列
handler.postDelayed(Runnable, long)延遲一定時間后,將Runnable添加入隊列
handler.postAtTime(Runnable,long)定時將Runnable添加入隊列
終止線程:
handler.removeCallbacks(thread);將Runnable從Runnable隊列中取出
四.消息隊列
1.消息對象
(1)Message對象
Message對象攜帶數(shù)據(jù),通常它用arg1,arg2來傳遞消息,當(dāng)然它還可以有obj參數(shù),可以攜帶Bundle數(shù)據(jù)。它的特點是系統(tǒng)性能消耗非常少。
初始化: Message msg=handler.obtainMessage();
(2)Bundle對象
Bundle是Android提供的類,可以把它看做是特殊的Map,即鍵值對的包。而它特殊在鍵和值都必須要是基本數(shù)據(jù)類型或是基本數(shù)據(jù)類型的數(shù)組(Map的鍵值要求都是對象),特別的,鍵要求都是String類型。用Message來攜帶Bundle數(shù)據(jù):
放入:msg.setData(Bundle bundle);
取出:msg.getData();
2.關(guān)于消息隊列
(1)原理
Android的消息異步處理機(jī)制:Handler對象維護(hù)一個消息隊列,有新的消息送來(sendMessage())的時候,把它放在隊尾,之后排隊 到處理該消息的時候,由主線程的Handler對象處理(handleMessage())。整個過程也是異步的,和Runnable隊列的原理相同。
(2)具體操作:
向隊列添加Runnable:handler.sendMessage(Message);
將消息發(fā)送到消息隊列msg.sendToTarget();
延遲一定時間后,將消息發(fā)送到消息隊列 handler.sendMessageDelayed(Message,long);
定時將消息發(fā)送到消息隊列 handler.sendMessageAtTime(Message,long)
處理消息:
消息的具體處理過程,需要在new Handler對象時使用匿名內(nèi)部類重寫Handler的handleMessage(Message msg)方法,如下:
- Handler handler=new Handler(){
- @Override
- public void handleMessage(Message msg) {
- // TODO Auto-generated method stub
- 。。。。。。
- 。。。。。。
- }
- };
五.Handler的兩個作用
1.安排消息或Runnable 在某個主線程中某個地方執(zhí)行
代碼示例:
- public class HandlerTestActivity extends Activity {
- private Button start;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.handlertest);
- start=(Button) findViewById(R.id.start);
- start.setOnClickListener(new startListener());
- System.out.println("Activity Thread:"+Thread.currentThread().getId());
- }
- Handler handler=new Handler();
- Runnable thread=new Runnable(){
- @Override
- public void run() {
- // TODO Auto-generated method stub
- System.out.println("HandlerThread:"+Thread.currentThread().getId());
- }
- };
- class startListener implements OnClickListener{
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- handler.post(thread);
- }
- }
- }
這個小程序中,首先程序啟動,進(jìn)入onCreate(),打印出當(dāng)前線程(即主線程)的ID,之后點擊按鈕start,會將線程thread添加到線程隊 列,執(zhí)行線程thread,thread的作用就是打印出當(dāng)前線程的ID。在這個程序中,我們可以看到通過Handler我們可以實現(xiàn)安排 Runnable 在某個主線程中某個地方執(zhí)行,即作用(1)。
不過這里有個小小的陷阱,你發(fā)現(xiàn)了嗎?這個程序看上去似乎實現(xiàn)了Handler的異步機(jī)制, handler.post(thread)似乎實現(xiàn)了新啟線程的作用,不過通過執(zhí)行我們發(fā)現(xiàn),兩個線程的ID相同!也就是說,實際上thread還是原來 的主線程,由此可見,handler.post()方法并未真正新建線程,只是在原線程上執(zhí)行而已,我們并未實現(xiàn)異步機(jī)制。
2.安排一個動作在另外的線程中執(zhí)行。
(1)java中標(biāo)準(zhǔn)的創(chuàng)建線程的方法
第一步:
- Runnable r=new Runnable(){
- @Override
- public void run() {
- // TODO Auto-generated method stub
- System.out.println("thread");
- handler.postDelayed(thread, 3000);
- }
- };
第二步:
- Thread t=new Thread (r);
第三步:
- t.start();
若把上面示例程序中的handler.post(thread);語句改成以上形式,通過打印我們可以看到,兩個ID是不同的,新的線程啟動了!
(2)關(guān)于Looper
Looper類用來為線程開啟一個消息循環(huán),作用是可以循環(huán)的從消息隊列讀取消息,所以Looper實際上就是消息隊列+消息循環(huán)的封裝。每個線程只能對應(yīng)一個Looper,除主線程外,Android中的線程默認(rèn)是沒有開啟Looper的。
通過Handler與Looper交互,Handler可以看做是Looper的接口,用來向指定的Looper發(fā)送消息以及定義處理方法。默認(rèn)情況下Handler會與其所在線程的Looper綁定,即:
Handler handler=new Handler();等價于Handler handler=new Handler(Looper.myLooper());
Looper有兩個主要方法:
Looper.prepare();啟用Looper
Looper.loop(); 讓Looper開始工作,從消息隊列里取消息,處理消息。
注意:寫在Looper.loop()之后的代碼不會被執(zhí)行,這個函數(shù)內(nèi)部應(yīng)該是一個循環(huán),當(dāng)調(diào)用mHandler.getLooper().quit()后,loop才會中止,其后的代碼才能得以運行。
(3)Handler異步機(jī)制的實現(xiàn)
Handler是通過HandlerThread 使得子線程與主線程分屬不同線程的。實際上,HandlerThread 是一個特殊的線程,它是一個封裝好Looper的線程,
代碼示例:
- //創(chuàng)建一個名叫handler_hread的HandlerThread 對象
- HandlerThread handlerThread=new HandlerThread("handler_hread");
- //開啟handlerThread,在使用handlerThread.getLooper()之前必須先調(diào)用start方法,否則取出的是空
- handlerThread.start();
- //將handler綁定在handlerThread的Looper上,即這個handler是運行在handlerThread線程中的
- myHandler handler=new myHandler(handlerThread.getLooper());
- class myHandler extends Handler{
- public myHandler(){}
- public myHandler(Looper looper){
- super(looper);
- }
- @Override
- public void handleMessage(Message msg) {
- // TODO Auto-generated method stub
- System.out.println("Activity Thread:"+Thread.currentThread().getId());
- }
- }
這樣,就實現(xiàn)了handler的異步處理機(jī)制,在調(diào)用handler.post()方法,通過打印線程ID可以得知,子線程與主線程是分屬不同線程的。