【Android 系統開發】_「核心技術」篇 -- Handler機制(用法)

開篇

引出問題

在 Android 開發中,咱們常常會遇到這樣一種狀況:在 UI 界面上進行某項操做後要執行一段很耗時的代碼,好比咱們在界面上點擊了一個 「下載」 按鈕,那麼咱們須要執行網絡請求,這是一個耗時操做。java

爲了保證不影響 UI 線程,因此咱們會建立一個新的線程去執行咱們的耗時的代碼。當咱們的耗時操做完成時,咱們須要更新 UI 界面以告知用戶操做完成了。android

代碼實例

好比,咱們看如下代碼:spring

package com.example.marco.handlerdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements Button.OnClickListener{

    private TextView textView = null;
    private Button btnDownload = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        btnDownload = findViewById(R.id.btnDownload);
        btnDownload.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        DownLoadThread downLoadThread = new DownLoadThread();
        downLoadThread.start();
    }

    class DownLoadThread extends Thread {
        @Override
        public void run() {
            try {
                System.out.println("開始下載文件");
                Thread.sleep(5000);
                System.out.println("下載完成");
                MainActivity.this.textView.setText("文件下載完成");  // 執行後,此處崩潰!!!
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

執行結果

E/AndroidRuntime: FATAL EXCEPTION: Thread-4
    Process: com.example.marco.handlerdemo, PID: 14415
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7579)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1200)
        at android.view.View.requestLayout(View.java:22156)
        at android.view.View.requestLayout(View.java:22156)
        at android.view.View.requestLayout(View.java:22156)
        at android.view.View.requestLayout(View.java:22156)
        at android.view.View.requestLayout(View.java:22156)
        at android.view.View.requestLayout(View.java:22156)
        at android.support.constraint.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
        at android.view.View.requestLayout(View.java:22156)
        at android.widget.TextView.checkForRelayout(TextView.java:8553)
        at android.widget.TextView.setText(TextView.java:5416)
        at android.widget.TextView.setText(TextView.java:5272)
        at android.widget.TextView.setText(TextView.java:5229)
        at com.example.marco.handlerdemo.MainActivity$DownLoaderThread.run(MainActivity.java:36)   // 錯誤開始

錯誤分析

執行以後,咱們發現程序崩潰,而且出現了以上的錯誤:只有建立 View 的原始線程才能更新 View。爲何會出現這個錯誤,這個錯誤是什麼意思?安全

出現這樣錯誤的緣由是 Android 中的 View 不是線程安全的,在 Android 應用啓動時,會自動建立一個線程,即程序的主線程,主線程負責 UI 的展現、UI 事件消息的派發處理等等,所以主線程也叫作 UI 線程,textView 是在 UI 線程中建立的,當咱們在 DownloadThread 線程中去更新 UI 線程中建立的 textView 時天然會報上面的錯誤。網絡

Android 的 UI 控件是非線程安全的,不一樣的平臺提供了不一樣的解決方案以實現跨線程更新 UI 控件,Android 爲了解決這種問題引入了 Handler機制多線程

Handler 引入

那麼 Handler 究竟是什麼呢?Handler 是 Android 中引入的一種讓開發者參與處理線程中消息循環的機制。app

每一個 Hanlder 都關聯了一個線程,每一個線程內部都維護了一個消息隊列 MessageQueue,這樣 Handler 實際上也就關聯了一個消息隊列。能夠經過 Handler 將 Message 和 Runnable 對象發送到該 Handler 所關聯線程的 MessageQueue(消息隊列)中,而後該消息隊列一直在循環拿出一個 Message,對其進行處理,處理完以後拿出下一個 Message,繼續進行處理,周而復始。當建立一個 Handler 的時候,該 Handler 就綁定了當前建立 Hanlder 的線程。從這時起,該 Hanlder 就能夠發送 Message 和 Runnable 對象到該 Handler 對應的消息隊列中,當從 MessageQueue 取出某個 Message 時,會讓 Handler 對其進行處理。ide

Handler 能夠用來在多線程間進行通訊,在另外一個線程中去更新 UI 線程中的 UI 控件只是 Handler 使用中的一種典型案例,除此以外,Handler 能夠作不少其餘的事情。每一個 Handler 都綁定了一個線程,假設存在兩個線程 ThreadA 和 ThreadB,而且 HandlerA 綁定了 ThreadA,在 ThreadB 中的代碼執行到某處時,出於某些緣由,咱們須要讓 ThreadA 執行某些代碼,此時咱們就可使用 Handler,咱們能夠在 ThreadB 中向 HandlerA 中加入某些信息以告知 ThreadA 中該作某些處理了。由此能夠看出,Handler 是 Thread 的代言人,是多線程之間通訊的橋樑,經過 Handler,咱們能夠在一個線程中控制另外一個線程去作某事。post

Handler 用法

Handler 提供了兩種方式解決前面遇到的問題(在一個新線程中更新主線程中的 UI 控件),一種是經過 post 方法,一種是調用 sendMessage 方法。ui

post

代碼實例

package com.example.marco.handlerdemo;

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements Button.OnClickListener{

    private TextView textView = null;
    private Button btnDownload = null;

    private Handler uiHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        btnDownload = findViewById(R.id.btnDownload);
        btnDownload.setOnClickListener(this);
        System.out.println("MainThread id " + Thread.currentThread().getId());
    }

    @Override
    public void onClick(View v) {
        DownLoadThread downLoadThread = new DownLoadThread();
        downLoadThread.start();
    }

    class DownLoadThread extends Thread {
        @Override
        public void run() {
            try {
                System.out.println("DownloadThread id " + Thread.currentThread().getId());
                System.out.println("開始下載文件");
                Thread.sleep(5000);
                System.out.println("下載完成");
                // MainActivity.this.textView.setText("文件下載完成");
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("RunnableThread id " + Thread.currentThread().getId());
                        MainActivity.this.textView.setText("文件下載完成");
                    }
                };
                uiHandler.post(runnable);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

執行結果

2018-09-28 15:18:24.474 15864-15864/com.example.marco.handlerdemo I/System.out: MainThread id 2
2018-09-28 15:18:26.901 15864-15938/com.example.marco.handlerdemo I/System.out: DownloadThread id 2441
2018-09-28 15:18:26.901 15864-15938/com.example.marco.handlerdemo I/System.out: 開始下載文件
2018-09-28 15:18:31.902 15864-15938/com.example.marco.handlerdemo I/System.out: 下載完成
2018-09-28 15:18:31.906 15864-15864/com.example.marco.handlerdemo I/System.out: RunnableThread id 2

經過輸出結果能夠看出,Runnable 中的代碼所執行的線程 ID 與 DownloadThread 的線程 ID 不一樣,而與主線程的線程 ID 相同,所以咱們也由此看出在執行了 Handler.post(Runnable) 這句代碼以後,運行 Runnable 代碼的線程與 Handler 所綁定的線程是一致的,而與執行 Handler.post(Runnable) 這句代碼的線程(DownloadThread)無關。

結果分析

咱們在 Activity 中建立了一個 Handler 成員變量 uiHandler,Handler 有個特色,在執行 new Handler() 的時候,默認狀況下 Handler 會綁定當前代碼執行的線程,咱們在主線程中實例化了 uiHandler,因此 uiHandle r就自動綁定了主線程,即 UI 線程。當咱們在 DownloadThread 中執行完耗時代碼後,咱們將一個 Runnable 對象經過 post 方法傳入到了 Handler 中,Handler 會在合適的時候讓主線程執行 Runnable 中的代碼,這樣 Runnable 就在主線程中執行了,從而正確更新了主線程中的 UI。

sendMessage

代碼實例

package com.example.marco.handlerdemo;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements Button.OnClickListener{

    private TextView textView = null;
    private Button btnDownload = null;

    private Handler uiHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    System.out.println("handleMessage thread id " + Thread.currentThread().getId());
                    System.out.println("msg.arg1:" + msg.arg1);
                    System.out.println("msg.arg2:" + msg.arg2);
                    MainActivity.this.textView.setText("文件下載完成");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        btnDownload = findViewById(R.id.btnDownload);
        btnDownload.setOnClickListener(this);
        System.out.println("MainThread id " + Thread.currentThread().getId());
    }

    @Override
    public void onClick(View v) {
        DownLoadThread downLoadThread = new DownLoadThread();
        downLoadThread.start();
    }

    class DownLoadThread extends Thread {
        @Override
        public void run() {
            try {
                System.out.println("DownloadThread id " + Thread.currentThread().getId());
                System.out.println("開始下載文件");
                Thread.sleep(5000);
                System.out.println("下載完成");
                // 文件下載完成後更新 UI
                Message msg = new Message();
                msg.what = 1;
                msg.arg1 = 123;
                msg.arg2 = 456;
                
                //咱們也能夠經過給 obj 賦值 Object 類型傳遞向 Message 傳入任意數據
                //msg.obj = null;
                
                // MainActivity.this.textView.setText("文件下載完成");
                /* Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("RunnableThread id " + Thread.currentThread().getId());
                        MainActivity.this.textView.setText("文件下載完成");
                    }
                };
                uiHandler.post(runnable);*/
                uiHandler.sendMessage(msg);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

執行結果

2018-09-28 16:16:16.613 19652-19652/? I/System.out: MainThread id 2
2018-09-28 16:16:19.431 19652-19692/com.example.marco.handlerdemo I/System.out: DownloadThread id 2493
2018-09-28 16:16:19.431 19652-19692/com.example.marco.handlerdemo I/System.out: 開始下載文件
2018-09-28 16:16:24.432 19652-19692/com.example.marco.handlerdemo I/System.out: 下載完成
2018-09-28 16:16:24.434 19652-19652/com.example.marco.handlerdemo I/System.out: handleMessage thread id 2
2018-09-28 16:16:24.434 19652-19652/com.example.marco.handlerdemo I/System.out: msg.arg1:123
2018-09-28 16:16:24.435 19652-19652/com.example.marco.handlerdemo I/System.out: msg.arg2:456

結果分析

經過 Message 與 Handler 進行通訊的步驟是:

  1. 重寫 Handler 的 handleMessage 方法,根據 Message 的 what 值進行不一樣的處理操做;
  2. 設置 Message 的 what 值:Message.what 是咱們自定義的一個 Message 的識別碼,以便於在 Handler 的 handleMessage 方法中根據 wha t識別出不一樣的 Message ,以便咱們作出不一樣的處理操做;
  3. 設置 Message 的所攜帶的數據,簡單數據能夠經過兩個 int 類型的 field :arg1 和 arg2 來賦值,並能夠在 handleMessage 中讀取;
  4. 若是 Message 須要攜帶複雜的數據,那麼能夠設置 Message 的 obj 字段,obj 是 Object 類型,能夠賦予任意類型的數據;
  5. 咱們經過 Handler.sendMessage(Message) 方法將 Message 傳入 Handler 中讓其在 handleMessage 中對其進行處理;
  6. 須要說明的是,若是在 handleMessage 中不須要判斷 Message 類型,那麼就無須設置 Message 的 what 值;並且讓 Message 攜帶數據也不是必須的,只有在須要的時候才須要讓其攜帶數據;若是確實須要讓 Message 攜帶數據,應該儘可能使用 arg1 或 arg2 或二者,能用 arg1 和 arg2 解決的話就不要用obj,由於用 arg1 和 arg2 更高效。
  7. 由上咱們能夠看出,執行 handleMessage 的線程與建立 Handler 的線程是同一線程,在本示例中都是主線程。執行 handleMessage 的線程與執行 uiHandler.sendMessage(msg) 的線程沒有關係。

參考Blog

  01. https://blog.csdn.net/iisprin...

相關文章
相關標籤/搜索