Android Handler詳解、使用(倒計時、驗證碼)

Android Handler詳解、使用(倒計時、驗證碼)



一、爲什麼要使用Handler
當出現耗時操作,並需要根據耗時操作返回結果時:
當Android的一個程序開啓的時候,他會開啓一個主線程,也就是常說的UI線程,但是大家都知道不能在主線程中進行耗時操作,就是各種下載、IO操作、等等,如果時間過長那麼會出現一個ANR無響應的對話框,提示等待或者關閉。所以我們把這些耗時的操作放入子線程中去執行。
我們將耗時操作放入子線程中執行的話又會出現一個問題,就是當我們執行耗時操作的時候又想對UI更新操作怎麼辦?
子線程沒有辦法對UI界面上的內容進行操作,如果操作,將拋出異常:CalledFromWrongThreadException
也就是說我們在Android中處理多線程要保證一下兩點:
1.不要阻塞UI線程
2.不要在UI線程之外訪問Android UI工具包(TextView.setText()這樣的操作)
所以這時就需要一種機制:主線程可以發送“命令/任務”給子線程執行,然後子線程反饋執行結果;
那麼爲了實現子線程中操作UI界面,Android中引入了Handler消息傳遞機制,目的是打破對主線程的依賴性。

二、那麼什麼是Handler呢?

一個Handler允許你發送和處理消息(Message)以及與一個線程的消息隊列相關的Runnable對象。每個Handler實例都和單個線程以及該線程的消息隊列有關。當你創建了一個新Handler,它就會和創建它的線程/消息隊列綁定,在那以後,它就會傳遞消息以及runnable對象給消息隊列,然後執行它們。

需要使用Handler有兩大主要的原因:
      (1)在將來的某個時間點調度處理消息和runnable對象;
      (2)將需要執行的操作放到其他線程之中,而不是自己的;

調度處理消息是通過調用post(Runnable), postAtTime(Runnable, long),postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message),sendMessageAtTime(Message, long)和sendMessageDelayed(Message,long)等方法完成的。其中的post版本的方法可以讓你將Runnable對象放進消息隊列;sendMessage版本的方法可以讓你將一個包含有bundle對象的消息對象放進消息隊列,然後交由handleMessage(Message)方法處理。(這個需要你複寫Handler的handleMessage方法)

你必需要知道的:
若在主線程中實例化一個Handler對象,例如:     Handler mHandler = newHandler();
此時它並沒有新派生一個線程來執行此Handler,而是將此Handler附加在主線程上,故此時若你在Handler中執行耗時操作的話,還是會彈出ANR對話框!


三、一個簡單的示例,驗證碼倒計時的實現

先上一張圖吧


1.創建一個Handler對象並實現其中的handleMessage方法
private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    break;
            }
        }
    };

2.在點擊方法中開啓線程循環發送消息
    //send方式發送驗證碼
    public void sendMessageClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
              for(int i = 59;i>=0;i--){
                  Message msg = handler.obtainMessage();
                  msg.arg1 = i;
                  handler.sendMessage(msg);
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
            }
        }).start();
    }
 
3.對handleMessage方法進行完善
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    if(msg.arg1==0){
                        button_send.setText("點擊發送驗證碼");
                        button_send.setClickable(true);
                    }else{
                        button_send.setText("請在("+msg.arg1+"秒)後再次點擊按鈕");
                        button_send.setClickable(false);
                    }

                    break;
            }
        }
    };
但是!上述代碼並不算是一個很好的代碼,因爲沒有考慮到Handler的內存泄露問題,那麼這個內存泄露是如何產生的呢?又應該如何去解決。
因爲Handler會持有一個外部類的引用,例如在Mainactivity中創建,那麼會獲取到Mainactivity的引用,因爲Handler是他的內部類,內部類的對象要依賴於外部類。當你使用的時候,如果退出了Mainactivity那麼Handler對象應該被銷燬,但是當你退出的時候Handler還在工作的話,那麼這個Mainactivity並不能被退出。也就是Handler執行後纔會退出,依然佔用內存,也就造成了內存泄露。
解決辦法:
1.定義一個內部類時會默認擁有外部類對象的引用,所以建議使用內部類時最好定義爲一個static靜態內部類。(因爲靜態內部類相當於外部類,不依賴外部類對象)
2.創建一個自定義的Handler類並集成Handler
3.定義一個弱引用 定義當前對象
private static class MyHandler extends Handler{
        private final WeakReference<MainActivity> weakReference;
        public MyHandler(MainActivity activity) {
            weakReference = new WeakReference<MainActivity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = weakReference.get();
            if(activity!=null){
                switch (msg.what) {
                    case 0:
                        if(msg.arg1==0){
                            button_send.setText("點擊發送驗證碼");
                            button_send.setClickable(true);
                        }else{
                            button_send.setText("請在("+msg.arg1+"秒)後再次點擊按鈕");
                            button_send.setClickable(false);
                        }
                        break;
                }
            }
        }
    }
4.創建一個自定義Handler對象
private MyHandler myHandler = new MyHandler(this);

四、上述是使用send,那麼如何使用Post呢?

1.最開始還是一樣的方式

2.將Runnable對象發送到消息隊列中,按照隊列的機制按順序執行不同的Runnable對象中的run方法。
    //post方式發送驗證碼
    public void sendMessagePostClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 59;i>=0;i--){
                    Message msg = myHandler.obtainMessage();
                    msg.arg1 = i;
                    final int finalI = i;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    myHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            if(finalI ==0){
                                button_post.setText("點擊發送驗證碼");
                                button_post.setClickable(true);
                            }else{
                                button_post.setText("請在("+ finalI +"秒)後再次點擊按鈕");
                                button_post.setClickable(false);
                            }
                        }
                    });
                }
            }
        }).start();
    }


五、如果在消息處理的時候有延時操作怎麼辦?
1.post
    //開啓子線程的線程名字
    private HandlerThread handlerThread = new HandlerThread("myHandlerThread");
    private Handler mHandler = new Handler();
    public void downloadPostClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //第一種方式,在handler綁定在主線程中,其中不能做耗時操作
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(60000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Log.e("ceshi", "post線程id"+Thread.currentThread().getId());
                    }
                }) ;
/*                //開啓了子線程,可以耗時操作了
                handlerThread.start();
                mHandler = new Handler(handlerThread.getLooper());
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("ceshi", "post線程id"+Thread.currentThread().getName());
                    }
                });*/
            }
        }).start();
    }
如果不開啓子線程的話,就是在主線程中執行耗時操作了,所以開啓一個HandlerThread在這個新建的線程中執行耗時操作。
2.send方式的話那麼在處理的時候使用新的線程就好


六、原理
1.首先是基本的概念:
a.Message:消息對象,其中包含了消息ID,消息處理對象以及處理的數據等,由MessageQueue統一列隊,終由Handler處理。用於重複利用,避免大量創建消息對象,造成內存浪費
b.Handler:消息處理者,負責Message的發送及處理。使用Handler時,需要實handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。Handler類的主要作用:(有兩個主要作用)1)、在工作線程中發送消息;2)、在主線程中獲取、並處理消息。
c.MessageQueue:消息隊列,用來存放Handler發送過來的消息,並按照FIFO規則執行。當然,存放Message並非實際意義的保存,而是將Message串聯起來的,等待Looper的抽取。
d.Looper:消息泵(消息對象的處理者),不斷地從MessageQueue中抽取Message執行。因此,一個MessageQueue需要一個Looper。
e.Thread:線程,負責調度整個消息循環,即消息循環的執行場所。
首先Handler創建一個Message對象,存放如消息隊列中,然後消息泵從隊列中取出Message對象(FIFO原則)
,當取出對象後交送給Handler的消息處理者handleMessage(Message msg)去處理消息。其中Message Pool是消息池,當消息創建後如果在執行那麼則創建新的,如果不在執行那麼從消息池中直接調用就好。

附上驗證碼倒計時的源碼:源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章