天天看點

HandlerThread見解之它不是更新ui的

前兩天 ,看一個demo 發現了裡面有使用HandlerThread。心生好奇,自己到網上看了 看資料,又翻了一下開發文檔。寫出來 抛磚引玉。

HandlerThread不能更新ui

我第一次看見handlerThread時候,因為,他帶着Handler還以為是更新UI的,其實不是的。在驗證的過程中,我還發現了 我以前的一個知識點錯誤(受一篇博文的引導。。。)。以後,任何東西 還是自己驗證一下比較好。

首先,我先來說HandlerThread是啥。通俗的來說就是一個子線程具有了 looper和handler這樣的機制 ,當這個子線程建立了handler的時候,别的線程可以通過handler來發送資訊,并且可以在這個handler裡面執行耗時的操作,除了不能更新ui,這樣的機制,我們自己也可以通過

Looper.prepare();
Handler inner = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        System.out.println("内部handler 裡面" + Thread.currentThread().getName() + "     ");
        Toast.makeText(MainActivity.this,"haahh",Toast.LENGTH_LONG).show();
        textView.setText(">>>>>>>>");
    }
};
inner.sendEmptyMessage(1);
Looper.loop();
           

不過,這樣多費勁是吧。是以 谷歌給我們造了HandlerThread這個類 就是完成這樣作用的。

如果,你運作以上代碼會抛出異常:

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

這個就是隻有主線程才能更新view。

看上面代碼,我們在handleMessage(Message msg)這個方法裡面 想更新 textView.setText(">>>>>>>>")。是以報錯 了。這個首先說明了子線程聯系的handler是不能更新線程的。

不過,如果 我們把textView.setText()注釋的話。

Looper.prepare();
Handler inner = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        System.out.println("内部handler 裡面" + Thread.currentThread().getName() + "     ");
        Toast.makeText(MainActivity.this,"haahh",Toast.LENGTH_LONG).show();
        //textView.setText(">>>>>>>>");
    }
};
inner.sendEmptyMessage(1);
Looper.loop();
           

運作 你會發現toast彈出了!!!!

這是怎麼回事了?首先,我們看toast的show方法

HandlerThread見解之它不是更新ui的

看我劃紅線的地方。service服務。我們看一下 getService()方法

HandlerThread見解之它不是更新ui的

我們可以看見 傳回了 INotificationManager對象的靜态執行個體。那麼這個INotificationManager是什麼呢。看上面的英文注釋。翻譯一下的意思是:

下面的所有内容都是與通知服務的互動,通知服務處理這些系統範圍内的正确排序。
           

這個呢 我們可以發現通知服務,其實到這裡呢,應該有人就明白了。Android 有一個專門的背景服務Service 來進行Toast 等一些通知的顯示 彈出。 而我們知道在Service裡面 我們是可以彈出Toast的。是以 ,這個說明了 一些通知資訊的顯示,都是由背景的 Service來承擔的。我們在前台 隻是一個通知作用

上面略微跑題

接下來 我們來說HandlerThread

HandlerThread handlerThread = new HandlerThread("first");
handlerThread.start();

final Handler handler = new Handler(handlerThread.getLooper(),new HandlerCallback()) {
    int count = 0;
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Toast.makeText(MainActivity.this,"haahh",Toast.LENGTH_LONG).show();
        textView.setText(">>>>>>");
    }
};


           

如果,我們這麼用代碼的會和上面抛出一樣的錯誤。因為這段代碼和上面那段代碼的作用是相同的。是以HandlerThread是不能更新UI的

正确使用是 我們仍然需要主線程的handler來更新ui,HandlerThread來接受其他子線程發送過來的消息,這些消息的處理不需要更新ui

//這個是主線程Handler 執行個體 還有一個知識點就是如果new Handler時候沒有給她傳遞looper的話,他會預設的去目前運作線程的looper進行聯系
       MainHandler mainHandler = new MainHandler(this);


       HandlerThread handlerThread = new HandlerThread("first");
       handlerThread.start();
        //這是子線程聯系的handler 傳入了handlerThread的looper
        final Handler childHandler = new Handler(handlerThread.getLooper()) {
            int count = 0;
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                System.out.println("子線程 的handler 接受到資料  現在 将資料發送給 主線程 執行 "+Thread.currentThread().getName());
                //發送消息給 主線程的handler 讓她更新ui
                Message.obtain(mainHandler, 1, ++count).sendToTarget();
            }
        };



    // 開啟一個子線程 調用childHandler 發送資訊   
    button.setOnClickListener(new View.OnClickListener() {
         @Override
        public void onClick(View v) {

           new Thread(new Runnable() {
              @Override
               public void run() {
                System.out.println("子線程發送資料 " +  Thread.currentThread().getName());
                //發送給handlerThread的handler 
                 childHandler.sendEmptyMessage(1);
              }
         }).start();
     }
  });


    //這個是主線程的Handler 定義成這種形式,可以防止記憶體洩露
   static class MainHandler extends Handler{
        private WeakReference<MainActivity> mainActivityWeakReference;
        
        MainHandler(MainActivity mainActivity) {
            this.mainActivityWeakReference =new WeakReference<>(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mainActivityWeakReference.get().textView.setText(msg.obj+"");
        }
    }
           

在上面我們可以看見,我們開啟了一個子線程,在子線程裡面調用了 HandlerThread的childHandler發送資訊,childHandler接受到消息,再調用主線程的mainHandler重新整理ui。

通過上面的例子來看 ,大家肯定感覺 這不是多此一舉麼。一開始 我也是這麼想的,後來 我仔細思考,這個HandlerThread 其實可以應用于這些情況下面:

  1. 比如,你同時有好幾個子線程,你需要得到他們運算時候發出的資訊,以前使用接口回調,還得實作的 ,如果 好幾個子線程 豈不是要好幾個接口。
  2. 比如 還有 一個子線程運算完 你想知道結果,也可以通過這個來實作。以前你使用callable,接口的話 也還是 比較複雜一點的。

小提示

這個handler的構造方法中的callback 官方是這麼說的

在執行個體化處理程式時可以使用回調接口,以避免必須實作自己的處理程式子類。
           

簡而言之呢,就是 你可以實作這個回調接口,這樣你在執行個體化handler時候,就不要重寫handleMessag()這個方法了。

//在執行個體化處理程式時可以使用回調接口,以避免必須實作自己的處理程式子類。
    private class HandlerCallback implements Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) {
            System.out.println("我是handler Callback " + Thread.currentThread().getName());
            //傳回true意味着即使子類重寫了handlerMessage方法 也不會被調用。
            //傳回false  如果有子類的話,會調用handlerMessager方法
            return true;
        }
    }
           

callback 已經實作了 handler方法

還有 調用HandlerThread的handler.post(Runnable) 也是在 子線程 運作的。

貼上全部代碼

package com.example.handlerthread;

import android.os.Handler;
import android.os.HandlerThread;
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;
import android.widget.Toast;

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private Button   button;

    MainHandler mainHandler = new MainHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text);
        button = findViewById(R.id.button);
        HandlerThread handlerThread = new HandlerThread("first");
        handlerThread.start();

        final Handler childHandler = new Handler(handlerThread.getLooper(), new HandlerCallback()) {
            int count = 0;

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                System.out.println("子線程 的handler 接受到資料  現在 将資料發送給 主線程 執行 " + Thread.currentThread().getName());
                Message.obtain(mainHandler, 1, ++count).sendToTarget();
            }
        };
        
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("子線程發送資料 " + Thread.currentThread().getName());
                        childHandler.sendEmptyMessage(1);
                    }
                }).start();
            }
        });
    }
    //這個是主線程的Handler 定義成這種形式,可以防止記憶體洩露
    static class MainHandler extends Handler {
        private WeakReference<MainActivity> mainActivityWeakReference;


        MainHandler(MainActivity mainActivity) {
            this.mainActivityWeakReference = new WeakReference<>(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mainActivityWeakReference.get().textView.setText(msg.obj + "");
        }
    }

    //在執行個體化處理程式時可以使用回調接口,以避免必須實作自己的處理程式子類。
    private class HandlerCallback implements Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) {
            System.out.println("我是handler Callback " + Thread.currentThread().getName());
            return true;
        }
    }
}