fred-ye / summary

my blog
43 stars 9 forks source link

[Android]一个关于子线程中刷UI的问题 #34

Open fred-ye opened 10 years ago

fred-ye commented 10 years ago

有如下一段代码,定义了一个很普通的Activity,然后在其onCreate()方法中启了一个子线程去刷UI.

一段代码

Activity

public class TestUIThreadActivity extends Activity {
    private TextView tvTest;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_ui_thread);
        tvTest = (TextView)findViewById(R.id.tv_test);
        new ThreadOne().start();
    }
    private class ThreadOne extends Thread {
        @Override
        public void run() {
            tvTest.setText(“ABC");
        }
    }

}

很普通的布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:my="http://schemas.android.com/apk/res/com.example.test"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/tv_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=“AAA" />

</RelativeLayout>

程序运行的结果是ABC显示出来了,虽然是在子线程中刷的UI,但是android系统没有报任何异常信息。如果将子线程的启动放在onResume()方法中,结果也是一样。

但是如果我们把程序这样改一下,在程序中添加一个Button,点击Button的时候才会去刷新UI,更改文字的内容,结果又会是什么呢?

第二段代码

Activity中

public class TestUIThreadActivity extends Activity {
    private TextView tvTest;
    private Button btnTest;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_ui_thread);
        tvTest = (TextView)findViewById(R.id.tv_test);
        btnTest = (Button)findViewById(R.id.btn_test);
        btnTest.setOnClickListener(listener);

    }
    private View.OnClickListener listener = new View.OnClickListener() {

        @Override
        public void onClick(View view) {
            new ThreadOne().start();
        }
    };

    private class ThreadOne extends Thread {
        @Override
        public void run() {
            tvTest.setText("bbb");
        }
    }
}

布局文件中

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:my="http://schemas.android.com/apk/res/com.example.test"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/tv_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="AAA" />
    <Button
        android:id="@+id/btn_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_test"
        android:text="Click Me"/>
</RelativeLayout>

运行时发现有异常抛出:

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

我们期待的在子线程中不能刷UI的异常出现了。 其实如果在第一段代码中,让子线程延迟5秒这个样子再去刷UI,同样也会报这个异常。那么为什么第一段代码中直接刷UI不会出现异常呢?我们很容易想到,当我们直接在onCreate方法中调用子线程刷UI时,此时UI层都还没有展示出来。所有UI的更新,都会调用到相应组件的invalidate()方法,最后会调用到ViewRootImpl中的invalidateChildInParent方法,在这个方法中会调checkThread()。直接执行时,是因为ViewRootImpl还没有创建出来。ViewRootImpl的创建是在onResume方法完成。