Android 基礎之 Handler

weixin_34253539發表於2019-02-19

Android 是單執行緒模型,但是僅僅靠主執行緒是不夠的,比如在主執行緒中進行網路請求、下載圖片、下載視訊等耗時操作時,就會造成阻塞,使用者用起來會很不舒服。所以,除了 UI 的更新外,一些耗時的操作需開啟子執行緒來處理。主執行緒和子執行緒之間需要資料交換等通訊,子執行緒和子執行緒之間同樣也需要通訊。

目前執行緒中的通訊是藉助 Handler 實現的,但 Handler 的作用不僅限於執行緒間通訊,還有延時啟動 Runnable,有一點需要說明:一個執行緒 (Thread)對應一個 Looper,一個 Looper 對應一個訊息佇列 (MessageQueue) ,一個訊息佇列(MessageQueue )中可以包含多條 Message 訊息,一個執行緒中可以有多個 Handler 。

Handler

Handler 是一個訊息分發物件。當建立一個 Handle r時,會與建立他的執行緒和執行緒的訊息佇列繫結,從這時候開始,Handler 會向訊息佇列傳送 Message ,並且在 Message 從訊息佇列出來時對Message 進行處理。
Handler 類包含如下方法用於傳送、處理訊息:

  • handleMessage(Message msg):處理訊息的方法。
  • obtainMessage():獲取訊息。
  • sendEmptyMessage(int what):傳送空訊息。
  • sendMessage(Message msg):立即傳送訊息。
  • sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒後傳送訊息。
  • sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒後傳送空訊息。

Message

Handler接收和處理的訊息物件。

  • 2個整型數值:輕量級儲存int型別的資料。
  • 1個Object:任意物件。
  • replyTo:執行緒通訊時使用。
  • what:使用者自定義的訊息碼,讓接收者識別訊息。

MessageQueue

Message的佇列

  • 採用先進先出的方式管理Message。
  • 每一個執行緒最多可以擁有一個。

Looper

訊息泵,是 MessageQueue 的管理者,會不斷從 MessageQueue 中取出訊息,並將訊息分給對應的 Handler 處理。

  • 每個執行緒只有一個 Looper。
  • Looper.prepare():為當前執行緒建立 Looper物件。
  • Looper.myLooper():可以獲得當前執行緒的 Looper物件。
  • Handler:能把訊息傳送給 MessageQueue,並負責處理 Looper 分給它的訊息。

Android 訊息機制的執行流程

在子執行緒執行完耗時操作,當Handler傳送訊息時,將會呼叫MessageQueue.enqueueMessage,向訊息佇列中新增訊息。當通過Looper.loop開啟迴圈後,會不斷地從執行緒池中讀取訊息,即呼叫MessageQueue.next,然後呼叫目標Handler(即傳送該訊息的Handler)的dispatchMessage方法傳遞訊息,然後返回到Handler所線上程,目標Handler收到訊息,呼叫handleMessage方法,接收訊息,處理訊息。

MessageQueue,Handler和Looper三者之間的關係

每個執行緒中只能存在一個Looper,Looper是儲存在ThreadLocal中的。主執行緒(UI執行緒)已經建立了一個Looper,所以在主執行緒中不需要再建立Looper,但是在其他執行緒中需要建立Looper。每個執行緒中可以有多個Handler,即一個Looper可以處理來自多個Handler的訊息。 Looper中維護一個MessageQueue,來維護訊息佇列,訊息佇列中的Message可以來自不同的Handler。

Handler 如何去實現傳送和處理訊息

1. 使用 Thread 傳送訊息

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1001) {
                mTextView.setText("Star");
            }
        }
    };

    private TextView mTextView;
    private Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = findViewById(R.id.tv_text);
        mButton = findViewById(R.id.btn_send);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        }catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        mHandler.sendEmptyMessage(1001);
                    }
                }).start();
            }
        });
    }
}

2. 使用Handler下載檔案並更新進度條

  1. 新增許可權
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  1. 實現程式碼
public class MainActivity extends AppCompatActivity {

    private static final int DOWNLOAD_FILE_CODE = 100001 ;
    private static final String DOWNLOAD_URL ="http://download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk" ;
    private static final int DOWNLOAD_FILE_FAILE_CODE = 100002;
    private Button StartDownload;
    private TextView percent;
    private ProgressBar progressBar;
    private Handler handler;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        StartDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //執行下載程式
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        download(DOWNLOAD_URL);
                    }
                }).start();

            }
        });
        handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case 100001:
                        //更新進度條
                        progressBar.setProgress((Integer) msg.obj);
                        percent.setText(String.valueOf(msg.arg1)+"%");
                        if (progressBar.getProgress()==100){
                            Toast.makeText(MainActivity.this, "下載完成", Toast.LENGTH_SHORT).show();
                        }
                        break;
                    case 100002:
                        Toast.makeText(MainActivity.this, "下載失敗", Toast.LENGTH_SHORT).show();
                        break;

                }
            }
        };
    }

    private void download(String AppUrl) {
        //例項化URL物件
        try {
            URL url = new URL(AppUrl);
            //例項化一個URLConnection物件
            URLConnection conn = url.openConnection();
            //獲取下載路徑
            String path = Environment.getExternalStorageDirectory()
                    + File.separator + "ZerGen" + File.separator;
            //建立目錄
            File PathName = new File(path);
            //如果PathName不存在的話 就建立這個目錄
            if(!PathName.exists()){
                PathName.mkdir();
            }
            //有了目錄之後,就需要一個檔名
            String ApkName = path+"ZerGen.apk";
            //判斷一下這個檔案是否已經存在,存在的話就刪除它
            File ApkFile = new File(ApkName);
            if(ApkFile.exists()){
                ApkFile.delete();
            }
            //獲取檔案的總長度
            int ContentLength = conn.getContentLength();
            //獲取輸入流
            InputStream in = conn.getInputStream();
            byte[] b = new byte[1024];
            int DownloadLength = 0; //用於儲存實時下載長度
            int len  =0;
            OutputStream out = new FileOutputStream(ApkName);
            while((len = in.read(b))>-1){
                 out.write(b,0,len);
                 DownloadLength += len;
                //將實時的下載長度傳給UI執行緒
                Message message=handler.obtainMessage();
                message.what =DOWNLOAD_FILE_CODE;
                message.obj = DownloadLength*100/ContentLength;
                message.arg1 = DownloadLength*100/ContentLength;
                handler.sendMessage(message);

            }

        } catch (java.io.IOException e) {
            downloadfail();
            e.printStackTrace();
        }
    }

    private void downloadfail() {
        Message message=handler.obtainMessage();
        message.what =DOWNLOAD_FILE_FAILE_CODE;
        handler.sendMessage(message);
    }

    private void initView() {
        StartDownload = (Button) findViewById(R.id.btn_start);
        percent = (TextView) findViewById(R.id.percent);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);

    }
}

3. 使用 handler實現倒數計時功能

public class SplashActivity extends AppCompatActivity {
    private TextView mTime;
    private static int TIME = 5;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    TIME--;
                    mTime.setText(TIME + "s");
                    if (TIME > 0) {
                        Message message = mHandler.obtainMessage(1);
                        mHandler.sendMessageDelayed(message, 1000);      // send message
                    } else {
                        //跳轉到主介面
                        goHome();
                    }
            }
            super.handleMessage(msg);
        }
    };

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

        mTime = findViewById(R.id.textView);
        Message message = mHandler.obtainMessage(1);
        mHandler.sendMessageDelayed(message,1000);


    }

    private void goHome() {
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
        finish();
    }
}

相關文章