从后台线程异常在TextView上设置文本 [英] Setting text on TextView from background thread anomaly

查看:105
本文介绍了从后台线程异常在TextView上设置文本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've just started playing with Android Concurrency/Loopers/Handers, and I have just faced with strange anomaly. The code below doesn't block me from setting text on TextView from different Thread.

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);
}

猜猜会发生什么

但是,当我休眠后台线程一会儿

But, when I sleep background thread for a while

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);
            }
        };

它确实引发了异常

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)

有人可以告诉我发生了什么事吗?为什么我可以从其他线程设置文本?

Can someone explain me what happened ? Why I was able to set text from different thread ?

推荐答案

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

From the very first try, your code is wrong because you created Handler on different thread. This leads to looper/handler is run on different thread. Based on your comment, I guess you know this issue and you want to understand why exception doesn't throw on first try but second.

您应该注意:从UI Thread以外的其他线程上访问UI元素会导致未定义的行为.这意味着:

You should beware this: Accessing UI Element on different thread except from UI Thread causes undefined behavior. This means:

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

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

That means Accessing UI Element on different thread isn't always 100% reproducible.

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

Why you should access all UI Elements only on UI Thread? Because processing UI Element (change internal state, draw to screen ...) is a complex process and need to synchronized between related parties. For example, you call TextView#setText(String) on 2 fragments that both visible on screen. Android doesn't do this concurrently but pushing all jobs into an UI message queue and do that sequentially. This is also true not only from your application viewpoint but also from whole Android system perspective. Updating from status bar that called by system, updating from your app that called by your application always push actions to same UI Message queue before processing.

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

When you access and modify UI elements on different thread, you broke that process. That means maybe two threads might access and modify an element at same state and same time. As the result, you will meet race condition at some time. That when error occurs.

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

Explaining for your situation is hard because not enough data for analyzing. But there are some few reasons:

  • 第一次尝试时,TextView尚未显示在屏幕上.因此,不同的线程能够对TextView进行更改.但是在第二次尝试时,您会睡一秒钟.此时,所有视图均已成功渲染并显示在屏幕上,因此引发了异常.您可以尝试Thread.sleep(0),希望您的代码不会崩溃.
  • 在某些情况下会发生这种情况,但很难猜测原因.碰巧,您的线程和ui线程都访问同一个锁对象,引发了异常.
  • At your first try, TextView haven't displayed on screen yet. So different thread was able to make change on TextView. But on your second try, you sleep for 1 second. At this time, all view has rendered and displayed successfully on screen, so exception threw. You can try Thread.sleep(0) and hopefully your code doesn't crash.
  • This is will happen in some situations but hard to guess why. That by some chance, both your thread and ui thread accesses same lock object, exception threw.

您可以在此处 Android线程

明确引用

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

Many tasks on non-main threads have the end goal of updating UI objects. However, if one of these threads accesses an object in the view hierarchy, application instability can result: If a worker thread changes the properties of that object at the same time that any other thread is referencing the object, the results are undefined.

希望这对您有所帮助.

这篇关于从后台线程异常在TextView上设置文本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆