从后台线程异常在TextView上设置文本

Sta*_*ski 4 java multithreading android thread-safety

我刚刚开始使用Android Concurrency / Loopers / Handers玩游戏,而且刚遇到奇怪的异常情况。下面的代码不会阻止我从其他线程在TextView上设置文本。

TextView tv;
Handler backgroundHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    tv = (TextView) findViewById(R.id.sample_text);
    Runnable run = new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            backgroundHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    String text = (String) msg.obj;
                    tv.setText(Thread.currentThread().getName() + " " + text);
                }
            };
            Looper.loop();
        }
    };

    Thread thread = new Thread(run);
    thread.setName("Background thread");
    thread.start();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Message message = backgroundHandler.obtainMessage();
    message.obj = "message from UI";
    backgroundHandler.sendMessage(message);
}
Run Code Online (Sandbox Code Playgroud)

猜猜会发生什么

在此处输入图片说明

但是,当我休眠后台线程一段时间

backgroundHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String text = (String) msg.obj;
                tv.setText(Thread.currentThread().getName() + " " + text);
            }
        };
Run Code Online (Sandbox Code Playgroud)

确实引发了异常

07-03 18:54:40.506 5996-6025/com.stasbar.tests E/AndroidRuntime: FATAL EXCEPTION: Background thread
                                                             Process: com.stasbar.tests, PID: 5996
                                                             android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
                                                                 at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7275)
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下发生了什么吗?为什么我可以从其他线程设置文本?

hqt*_*hqt 5

从最初的尝试开始,您的代码是错误的,因为您是Handler在不同的线程上创建的。这导致looper / handler在不同的线程上运行。根据您的评论,我想您知道此问题,并且您想了解为什么异常不会在第一次尝试后就抛出,而是第二次抛出。

您应该当心:在其他线程上访问UI元素UI Thread会导致未定义的行为(除外)。这意味着:

  • 有时您会看到它起作用。
  • 有时候你不知道 如您所见,您将遇到例外。

这意味着在不同线程上访问UI元素并不总是100%可重复的。

为什么只应在UI线程上访问所有UI元素?因为处理UI元素(更改内部状态,绘制到屏幕...)是一个复杂的过程,并且需要在相关方之间进行同步。例如,您调用TextView#setText(String)2个在屏幕上都可见的片段。Android不会并发执行此操作,而是将所有作业推送到UI消息队列中并按顺序执行。不仅从您的应用程序角度来看,而且从整个Android系统角度来看,也是如此。从系统调用的状态栏更新,从应用程序调用的应用程序更新始终在处理之前将操作推送到相同的 UI消息队列。

当您访问和修改不同线程上的UI元素时,您中断了该过程。这意味着也许两个线程可以在同一状态和同一时间访问和修改元素。结果,您将在某些时候满足比赛条件。当发生错误时。

很难解释您的情况,因为没有足够的数据进行分析。但是有一些原因:

  • 首次尝试时,TextView尚未显示在屏幕上。因此,不同的线程能够对TextView进行更改。但是在第二次尝试时,您会睡一秒钟。此时,所有视图均已成功渲染并显示在屏幕上,因此引发了异常。您可以尝试Thread.sleep(0)并希望您的代码不会崩溃。
  • 在某些情况下会发生这种情况,但很难猜测原因。碰巧的是,您的线程和ui线程都访问相同的锁对象,引发了异常。

你可以阅读更多关于多线程的问题在这里的Android主题

明确引用

非主线程上的许多任务的最终目标是更新UI对象。但是,如果这些线程之一访问视图层次结构中的对象,则可能导致应用程序不稳定:如果辅助线程在任何其他线程引用该对象的同时更改了该对象的属性,则结果是不确定的。

希望这对您有所帮助。