【Android】非UI线程到底能不能直接更新UI?

先看一个直接在子线程更新UI的代码:

//文件:MainActivity.java
package com.noonkey.app;

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

public class MainActivity extends AppCompatActivity {

    private TextView tv_hint;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_hint=(TextView)this.findViewById(R.id.tv_hint);
//创建一个子线程,并在子线程中直接更新UI
        new Thread(){
            @Override
            public void run() {
                super.run();
                tv_hint.setText("我在子线程中直接更新UI");
            }
        }.start();
    }
}

布局文件为:activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.noonkey.app.MainActivity">
    <TextView
        android:id="@+id/tv_hint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
</RelativeLayout>

这样子并不会抛出异常,TextView被正常更新,难道真的可以在非UI线程中更新UI?
但是如果我们把MainActivity.java的代码改成:

package com.noonkey.app;

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

public class MainActivity extends AppCompatActivity {

    private TextView tv_hint;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_hint=(TextView)this.findViewById(R.id.tv_hint);
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    //线程睡眠一秒
                    sleep(1000);
                    tv_hint.setText("我在子线程中直接更新UI");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                catch (Exception e){
                    e.printStackTrace();
                }

            }
        }.start();
    }
}

发现抛出了异常信息:
ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
为什么线程睡眠后再更新UI就会抛出异常?
在我们调用更新UI的方法时,例如setText(),最终会调用到ViewRootImpl类里面的checkThread()方法,该方法源码如下:

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

异常就是从这里抛出的。
那为什么线程没有睡眠时不会抛出异常?
Activity生命周期中调用onCreate()方法后才到onResume(),实际上ViewRootImpl的实例是在onResume()里创建的,所以在onCreate()启动线程直接更新UI的时候,ViewRootImpl还没有被实例化,因此没有异常抛出。线程睡眠一段时间后,onResume()方法已经被调用,此时再更新UI会触发异常。

综上:某些情况下是可以在子线程中直接更新UI,但会发生不可预测的后果(我们无法预料子线程是否会被阻塞),因此,我们要遵守Android的规范,使用google公司为我们提供的Handler机制更新UI.

发表评论

电子邮件地址不会被公开。 必填项已用*标注