Android Handler 非同步呼叫修改介面與主線程

來源:互聯網
上載者:User

標籤:

 

在Android編程的過程中,如果在Activity中某個操作會運行比較長的時間,比如:下載檔案。這個時候如果在主線程中直接下載檔案,會造成Activity卡死的現象;而且如果時間超過5秒,會有ANR報錯。

在這種情況下, 可以使用Handler來處理。

 

涉及到的類主要有:Handler、Thread、Message、MessageQueue、Looper、HandlerThread

  如果是針對上面的情況,可以只使用Handler、Message和Thread就可以解決。在Thread中處理下載檔案的過程,並在結束後,向Handler發送訊息(Message)。Handler在接收到訊息後對介面進行修改。

  如果只是不想在介面線程中進行下載檔案的操作,那隻需要建立一個新的線程來處理下載檔案的過程就可以,為什麼還要使用Handler呢?因為Android的介面線程是不安全的,所以如果直接在Thread中修改介面會報錯,所以要使用Handler來處理。當在介面線程中擷取Handler時,Handler將和介面線程在同一個線程中,所以通過Handler修改介面將不會報錯。

1、Handler要重寫handleMessage方法,用來處理修改介面的邏輯。

        handler = new Handler() {            @Override            public void handleMessage(Message msg) {                // TODO Auto-generated method stub                super.handleMessage(msg);                                switch (msg.what) {                case MessageType.ProgressType :                    int arg1 = msg.arg1;                    if (arg1 >= maxProgress) {                        Toast.makeText(MainActivity.this, "Progress is OK", Toast.LENGTH_SHORT);                        return;                    } else if (arg1 < 0) {                        arg1 = 0;                    }                    progressBar.setProgress(arg1);                    break;                                    default :                                        break;                }            }        };

說明:

  • 此處直接使用沒有參數的Handler建構函式,表示擷取當前線程的Looper,而主線程在建立的時候預設產生了一個Looper,所以在主線程的Activity中,可以直接使用Handler的無參建構函式。
  • 如果在主線程中想要擷取主線程的Looper,可以使用Looper.myLooper()方法擷取。
  • 在其他線程中要想擷取主線程的Looper,需要使用Looper.getMainLooper()方法擷取,在主線程中也可以使用這個方法擷取。
  • 在其他線程中,如果直接使用Looper.myLooper()方法,將擷取到null。

 

2、Thread中要在下載完檔案後(我使用了TimerTask和Timer來實現100毫秒進度加1),向Handler發送訊息。 

public class MyProgressTimerTask extends TimerTask {    private Handler handler;    private int count;        public MyProgressTimerTask(Handler handler) {        this.handler = handler;        count = 0;    }        @Override    public void run() {        // TODO Auto-generated method stub        count++;        Message message = handler.obtainMessage();        message.what = MessageType.ProgressType;        message.arg1 = count;        handler.sendMessage(message);                Looper looper = Looper.myLooper();        if (null == looper) {            System.out.println(">>>>>>>>>>>>>>>>Looper.myLooper is null<<<<<<<<<<<<<<");        }     }}

3、在點擊開始按鈕時,啟動Timer;點擊停止時,關閉Timer。也可以不使用Timer,而是使用Thread,在Thread中sleep一段時間,依然可以達到預期的效果。同時,兩種方式都可以對Activity進行操作,不會有死機的感覺。

     progressBar = (ProgressBar) findViewById(R.id.progressBar);        maxProgress = progressBar.getMax();                btnStartProgress = (Button) findViewById(R.id.btnStartProgress);        btnStartProgress.setOnClickListener(new View.OnClickListener() {                        @Override            public void onClick(View v) {                // TODO Auto-generated method stub                task = new MyProgressTimerTask(handler);                timer = new Timer();                timer.schedule(task, 1000, 100);            }        });                btnStopProgress = (Button) findViewById(R.id.btnStopProgres);        btnStopProgress.setOnClickListener(new View.OnClickListener() {                        @Override            public void onClick(View v) {                // TODO Auto-generated method stub                if (task != null) {                    task.cancel();                }                if (timer != null) {                    timer.cancel();                }                task = null;                timer = null;            }        });

注意:在產生Message的時候推薦使用Handler.obtainMessage()和Message.obtain()方法,因為這兩個方法(其實最終都是調用Message.obtain()方法)不是簡單的new一個對象。分析代碼可以看出,它會先判斷訊息池中是否有可用的訊息(由sPool指示開始Message的Message鏈表),如果沒有才會去建立新的Message。

 

主線程:

  Activity的繪製是在主線程中完成的,而主線程在建立的時候,就會建立Looper對象,所以介面線程使用Handler的時候,不需要考慮建立Looper的問題。那是不是Activity等組件的所有操作都在主線程中進行呢?通過實驗發現,基本可以這樣理解,當然如果在組件中建立新的線程,在新的線程的操作當然不可能在主線程中進行(比如new一個Thread,在Thread中計算1——100000的和)。除了這樣的操作外,可以理解為都在主線程中處理,即使這個組件是由new Thread調用startActivity建立的。

  我寫了這樣一段代碼。首先在啟動的Activity中,啟動一個新的線程,在這個新的線程中分別啟動一個Activity、Service、BroadcastReceiver。在Activity的onCreate、onStart、onResume、onPause、onStop、onDestroy、onRestart、onActivityResult方法中列印當前線程,結果列印出的都是主線程。在Service的onCreate中和BroadcastReceiver的onReceive中也是相同的效果。所以基本可以說組件的操作都是在主線程中進行的。

  因此,如果Handler主要用途是修改介面的話,那HandlerThread就不需要使用了。但是如果考慮到這樣的情境,在新啟動的線程A中,任務還是太多,又必須啟動其他線程,而線程A又需要獲得其他線程的返回結果。這時,使用HandlerThread可能就能很方便的解決問題了。Looper的使用應該很少了,HandlerThread的作用就是屏蔽掉Looper,直接使用Looper比較複雜,需要先使用Looper.prepare,然後在所有操作結束後調用Looper.loop使其迴圈讀取訊息佇列中的訊息。使用HandlerThread後,程式將極度簡化。

  

 

參考資料 :

深入理解Android訊息處理系統——Looper、Handler、Thread

深入剖析Android訊息機制

Android HandlerThread 的使用及其Demo

 

Android Handler 非同步呼叫修改介面與主線程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.