Android多線程——Handler

Android多線程——Handler

參考:

如下的例子,button的點擊事件中,在子線程中修改UI:

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        try {
                            Log.w("MainActivity", Thread.currentThread().getName());
                            Thread.sleep(6000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        textView.setText("Changed From Thread");

                    }
                }).start();

            }
        });

此時Logcat會提示如下的錯誤:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

在Android多線程中有如下的原則:

  • 不要阻塞UI線程
  • 不要在UI線程之外訪問UI組件

1.Handler.sendXXXMessage()等方法

在上面的Activity中定義一個Handler,重寫handleMessage

Handler mHandler = new Handler(){  
        @Override  
        public void handleMessage(Message msg){  
            if(msg.what == 0x123){  
                text.setText("Task Done!!");  
            }  
        }  
    };  

然後將工作線程的代碼改爲下面的樣子

mRunnable = new Runnable() {  
            @Override  
            public void run() {  
                try {  
                    Thread.sleep(5000);//模擬耗時任務  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                mHandler.sendEmptyMessage(0x123);//關於發消息的方法有很多,比如sendMessage(Message msg),sendMessageDelayed(Message msg, long delayMills)等等,可按具體需求選擇,這裏不作擴展  

            }  
        };  

一個線程只有一個Looper, 而一個Looper持有一個MessageQueue, 當調用Looper.prepare()時,Looper就與當前線程關聯起來了(在Activity裏沒有顯示調用Looper.prepare()是因爲系統自動在主線程裏幫我們調用了),而Handler是與Looper的線程是綁定的,查看Handler類的源碼可以發現它幾個構造函數,其中有接收一個Looper參數的,也有不接收Looper參數的,從上面的代碼上看,我們沒有爲Handler指定Looper,那麼Handler就默認更當前線程(即主線程)的Looper關聯起來了,之所以囉嗦那麼多就是因爲這決定了Handler.handlerMessage(msg)方法體裏的代碼到底在哪個線程裏執行,我們再梳理一下,Looper.prepare調用決定了Looper與哪個線程關聯,間接決定了與這個Looper相關聯的Handler.handlerMessage(msg)方法體裏的代碼執行的線程。(太囉嗦了)
現在回到上面的代碼,我們的Handler是在主線程裏的定義的,所以也默認跟主線程的Looper相關聯,即handlerMessage方法的代碼會在UI線程執行,因此更新TextView就不會報錯了。下面這張圖是弄清handlerMessage(msg)方法體裏的代碼的執行線程的思路
looper

2.Handler.post(Runnable)

只要將上面代碼中的

 mHandler.sendEmptyMessage(0x123);  

改成

mHandler.post(new Runnable() {  
    @Override  
    public void run() {  
        text.setText("Task Done!!");                 
        }  
 });  

Handler由以下部分組成:

  • Handler
  • Message
  • Message Queue
  • Looper

Android 異步通信:手把手教你使用Handler消息傳遞機制(含實例Demo)有如下的一張圖:
handler關係

Handler

在Activity中定義一個Handler

Handler允許你發送和處理與線程的MessageQueue關聯的MessageRunnable對象。每個Handler實例都與一個線程和該線程的消息隊列(message queue)相關聯。當創建一個新的Handler時,它被綁定到創建它的線程/消息隊列,它將messages和runnables傳遞給該消息隊列並執行

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.
Handler有兩個主要用途:(1)安排messages和runnables在將來的某個時刻執行; (2)在不同於自己的線程上執行的操作。

Handler可以把一個Message對象或者Runnable對象壓入到消息隊列中,進而在UI線程中獲取Message或者執行Runnable對象,所以Handler把壓入消息隊列有兩大體系,PostsendMessage

  • PostPost允許把一個Runnable對象入隊到消息隊列中。它的方法有:post(Runnable)postAtTime(Runnable,long)postDelayed(Runnable,long)
  • sendMessagesendMessage允許把一個包含消息數據的Message對象壓入到消息隊列中。它的方法有:sendEmptyMessage(int)sendMessage(Message)sendMessageAtTime(Message,long)sendMessageDelayed(Message,long)

Post

對於HandlerPost方式來說,它會傳遞一個Runnable對象到消息隊列中,在這個Runnable對象中,重寫run()方法。一般在這個run()方法中寫入需要在UI線程上的操作

        btnMes1.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // 新啓動一個子線程
                new Thread(new Runnable() {                    
                    @Override
                    public void run() {
                        // tvMessage.setText("...");
                        // 以上操作會報錯,無法再子線程中訪問UI組件,UI組件的屬性必須在UI線程中訪問
                        // 使用post方式修改UI組件tvMessage的Text屬性
                        handler.post(new Runnable() {                    
                            @Override
                            public void run() {
                                tvMessage.setText("使用Handler.post在工作線程中發送一段執行到消息隊列中,在主線程中執行。");                        
                            }
                        });                                
                    }
                }).start();
            }
        });

Message

Message是容納任意數據的容器。生產線程發送消息給 Handler,Handler 將消息加入到消息隊列中。

如果對於一般的數據,Message提供了getData()setData()方法來獲取與設置數據,其中操作的數據是一個Bundle對象

還有另外一種方式在Message中傳遞對象,那就是使用Message自帶的obj屬性傳值,它是一個Object類型,所以可以傳遞任意類型的對象,Message自帶的有如下幾個屬性:

  • int arg1:參數一,用於傳遞不復雜的數據,複雜數據使用setData()傳遞。
  • int arg2:參數二,用於傳遞不復雜的數據,複雜數據使用setData()傳遞。
  • Object obj:傳遞一個任意的對象。
  • int what:定義的消息碼,一般用於設定消息的標誌。

對於Message對象,一般並不推薦直接使用它的構造方法得到,而是建議通過使用Message.obtain()這個靜態的方法或者Handler.obtainMessage()獲取

package com.bgxt.datatimepickerdemo;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class HandlerMessageActivity1 extends Activity {
    private Button btnDown;
    private ImageView ivImage;
    private static String image_path = "http://ww4.sinaimg.cn/bmiddle/786013a5jw1e7akotp4bcj20c80i3aao.jpg";
    private ProgressDialog dialog;
    private static int IS_FINISH = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.asynctask_activity);

        btnDown = (Button) findViewById(R.id.btnDown);
        ivImage = (ImageView) findViewById(R.id.ivSinaImage);

        dialog = new ProgressDialog(this);
        dialog.setTitle("提示信息");
        dialog.setMessage("正在下載,請稍後...");
        dialog.setCancelable(false);

        btnDown.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                    new Thread(new MyThread()).start();
                    dialog.show();
            }
        });
    }

    private  Handler handler = new Handler() {
        // 在Handler中獲取消息,重寫handleMessage()方法
        @Override
        public void handleMessage(Message msg) {            
            // 判斷消息碼是否爲1
            if(msg.what==IS_FINISH){
                byte[] data=(byte[])msg.obj;
                Bitmap bmp=BitmapFactory.decodeByteArray(data, 0, data.length);
                ivImage.setImageBitmap(bmp);
                dialog.dismiss();
            }
        }
    };

    public class MyThread implements Runnable {

        @Override
        public void run() {
            HttpClient httpClient = new DefaultHttpClient();
            HttpGet httpGet = new HttpGet(image_path);
            HttpResponse httpResponse = null;
            try {
                httpResponse = httpClient.execute(httpGet);
                if (httpResponse.getStatusLine().getStatusCode() == 200) {
                    byte[] data = EntityUtils.toByteArray(httpResponse
                            .getEntity());
                    // 獲取一個Message對象,設置what爲1
                    Message msg = Message.obtain();
                    msg.obj = data;
                    msg.what = IS_FINISH;
                    // 發送這個消息到消息隊列中
                    handler.sendMessage(msg);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章