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

Java 从后台线程异常设置TextView上的文本,java,android,multithreading,thread-safety,Java,Android,Multithreading,Thread Safety,我刚刚开始使用Android并发/循环器/Handers,我刚刚遇到了一个奇怪的异常。下面的代码不会阻止我从不同的线程在TextView上设置文本 TextView tv; Handler backgroundHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.acti

我刚刚开始使用Android并发/循环器/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);
}
猜猜会发生什么

但是,当我睡一会儿的时候

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)

有人能解释一下发生了什么事吗?为什么我可以从不同的线程设置文本?

只有UI线程可以编辑UI项目。换句话说,您不能从后台线程进行用户界面编辑

因此,代替
tv.setText(Thread.currentThread().getName()+“”+text)
,在
背景处理程序中使用以下代码:-

 runOnUiThread(new Runnable() {
    public void run() {
        tv.setText(Thread.currentThread().getName() + " " + text);
    }
});

从第一次尝试开始,您的代码就是错误的,因为您在不同的线程上创建了
Handler
。这导致循环器/处理程序在不同的线程上运行。根据你们的评论,我想你们知道这个问题,你们想知道为什么第一次尝试时,异常并没有出现,而是第二次出现

您应该注意这一点:除了从
UI线程
访问不同线程上的UI元素会导致未定义的行为。这意味着:

  • 有时你会发现它是有效的
  • 有时候你不会。正如您所看到的,您将遇到异常
这意味着在不同线程上访问UI元素并不总是100%可重复的

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

当您访问和修改不同线程上的UI元素时,您中断了该进程。这意味着可能有两个线程可以在相同的状态和时间访问和修改一个元素。因此,您将在某个时间满足比赛条件。当错误发生时

很难解释你的情况,因为没有足够的数据进行分析。但有几个原因:

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

明确引用

非主线程上的许多任务都有最终目标 更新UI对象的方法。但是,如果其中一个线程访问 对象,则可能导致应用程序不稳定:如果 工作线程同时更改该对象的属性 如果任何其他线程正在引用该对象,则结果为 未定义


希望这能对你有所帮助。

我知道,请再次阅读我的帖子。我想知道,当我从不同的线程访问TextView时,它为什么会起作用。