天天看點

Android Handler之更新ui使用分析

在Android中Handler相信大家都很熟悉了,主要用在:将工作線程中需要操作UI的消息傳遞到主線程,主線程收到消息後根據需求更新UI。

這裡舉個例子看下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);
    mBtn = findViewById(R.id.text);
    new Thread(){
        @Override
        public void run() {
            super.run();
            Log.e(TAG,"My Thread running");
            mBtn.setText("hello");
        }
    }.start();
}
           

這裡我們直接在onCreate方法中建立一個子線程,并更新UI, 這時候跑下程式會發現,程式正常運作,并且ui也更新了,這是為什麼呢?不是說不能在子線程更新ui嗎?

其實這是一個比較特殊的情況,通過源碼知道子線程更新ui報的異常是在android.view.ViewRootImpl中抛出的:

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }

}
           

而ViewRootImpl的建立是在onResume方法之後的,是以直接在onCreate建立子線程更新ui并不會抛出異常。

接下來我們改下例子:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);
    mBtn = findViewById(R.id.text);
    mBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startThread();
        }
    });
}

private void startThread(){
    new Thread(){
        @Override
        public void run() {
            super.run();
            Log.e(TAG,"My Thread running");
            mBtn.setText("hello");
        }
    }.start();

           

 這裡我們把子線程放到點選事件中開啟,運作程式,點選button,這是發現抛出異常了:

Android Handler之更新ui使用分析

這就是ViewRootImpl中要求不能再子線程中更新ui抛出的異常資訊。

那麼我們如果要在子線程中更新Ui要怎麼做呢?這就是我們要介紹的Handler,這裡我們分兩種情況來使用

1.直接在子線程中new Handler代碼如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);
    mBtn = findViewById(R.id.text);
    mBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startThread();
        }
    });
}

private void startThread(){
    new Thread(){
        @Override
        public void run() {
            super.run();
            Log.e(TAG,"My Thread running");
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    mBtn.setText("hello");
                }
            });
        }
    }.start();

           

跑下程式,發現崩了:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

        at android.os.Handler.<init>(Handler.java:200)

        at android.os.Handler.<init>(Handler.java:114)

這是因為Android系統預設情況下非主線程中沒有開啟Looper,而Handler對象必須綁定Looper對象。

我們修改下代碼:

new Thread(){
    @Override
    public void run() {
        super.run();
        Log.e(TAG,"My Thread running");
        new Handler(getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                mBtn.setText("hello");
            }
        });
    }

}.start();
           

在Handler中加入getMainLooper()方法,把它綁定到主線程的looper這樣跑下程式,發現可以了。

2.在主線程中new Handler:

mHandler = new Handler();
    mBtn = findViewById(R.id.text);
    mBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startThread();
        }
    });
}

private void startThread(){
    new Thread(){
        @Override
        public void run() {
            super.run();
            Log.e(TAG,"My Thread running");
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mBtn.setText("hello");
                }
            });
        }
    }.start();

}
           

這樣跑起來也是沒問題的。

Android Handler的簡單使用就介紹到這邊,接下來一章,會結合源碼來詳細分析下Handler的機制。

繼續閱讀