【问题标题】:Handler changing UI causes CalledFromWrongThreadException处理程序更改 UI 导致 CalledFromWrongThreadException
【发布时间】:2013-05-14 06:05:19
【问题描述】:

我创建了一个Handler,可以从活动中的任何地方访问它,并且还编写了一个方法来更容易调用处理程序:

private Handler textFromBGThread = new Handler() {
    @Override
    public void handleMessage (Message msg) {
        // Get the string from the msg
        String outputString = msg.getData().getString("Output");
        // Find the TextView
        TextView Output = (TextView)findViewById(R.id.ConsoleOutputView);
        // Display the output
        Log.i("TextOutput","About to display message: " + outputString);
        Output.setText(Output.getText() + outputString);
        Log.i("TextOutput","Message displayed");
    }
};

private void TextOutputWrapper (String outputText) {
    Message msg = new Message();
    Bundle bndle = new Bundle();
    bndle.putString("Output", "\n" + outputText);
    msg.setData(bndle);
    textFromBGThread.handleMessage(msg);
}

那么这可以从后台线程简单地调用:

TextOutputWrapper("Attemping to connect...");

这将工作 1 次以上,但是,实际的视觉变化将导致 CalledFromWrongThreadException 被抛出。作为 Java 和 Android 的新手,我不知道为什么会发生这种情况。

我注意到,当调用之间的时间间隔稍长时,崩溃往往会发生,并且如果对TextOutputWrapper(String) 的调用一个接一个地发生得非常快,那么它就可以工作。例如,这个:

int i = 0;
while (i < 200) {
    TextOutputWrapper(String.valueOf(i));
    i++;
}

工作正常。

查看过 LogCat,似乎垃圾收集器释放了一些资源,然后在下次调用 TextOutputWrapper(String) 时,它崩溃了(准确地说,是在调用 Output.SetText(String) 时),虽然我不完全是确定为什么会导致此错误。

【问题讨论】:

    标签: java android multithreading garbage-collection handler


    【解决方案1】:

    这里有几处我要改变:

    使用处理程序

    Handler 如果您想触发 UI 更新,并且从 非 UI 线程又名“后台”线程)执行此操作,则很有用。

    在你的情况下,它没有达到这个目的。您正在直接调用

    textFromBGThread.handleMessage(msg);
    

    它不是为您设计的。您应该使用Handler 的方式是在handleMessage(Message) 方法中实现您想要对UI 执行的操作。你做到了。但是,您不应该直接致电handleMessage()。如果你这样做了,那么handleMessage() 将会被调用TextOutputWrapper() 的任何线程调用。 如果那是后台线程,那就错了。

    您要做的是调用处理程序的sendMessage(Message) 方法(或其他可用变体之一)。 sendMessage() 会将您的消息放入线程安全队列中,然后在主线程上进行处理。然后主线程将调用您的处理程序的handleMessage(),将其传回排队的消息,并允许它安全地更改 UI。因此,更改 TextOutputWrapper() 以使用它:

    private void TextOutputWrapper (String outputText) {
        Message msg = new Message();
        Bundle bndle = new Bundle();
        bndle.putString("Output", "\n" + outputText);
        msg.setData(bndle);
        textFromBGThread.sendMessage(msg);
    }
    

    Java 约定

    对于有经验的 Java 开发人员来说,这段代码有点难以阅读。在 Java 中,typical coding standards 为类之类的东西保留大写名称,而方法以小写字母开头。所以,请将方法重命名为:

    private void textOutputWrapper (String outputText);
    

    或者,更好的是,因为这实际上是一个方法,而不是一个包装,本身,重命名为类似的东西

    private void outputText(String text);
    

    安全线程替代方案

    最后,我可能会建议,如果您只是想要一种允许您从任何线程安全地修改 UI 的方法,请使用另一种技术。我不认为Handler 对初学者来说那么容易使用。

    private void outputText(final String outputString) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Find the TextView
                TextView output = (TextView)findViewById(R.id.ConsoleOutputView);
                // Display the output
                Log.i("TextOutput","About to display message: " + outputString);
                output.setText(Output.getText() + outputString);
                Log.i("TextOutput","Message displayed");
            }
        });
    }
    

    runOnUiThread() 是每个Activity 中可用的方法。

    我还会向您指出一些关于理解 Android 中的线程的一般文档:

    http://www.vogella.com/articles/AndroidBackgroundProcessing/article.html

    http://android-developers.blogspot.com/2009/05/painless-threading.html

    【讨论】:

      猜你喜欢
      • 2013-09-28
      • 1970-01-01
      • 2011-02-18
      • 1970-01-01
      • 1970-01-01
      • 2014-03-19
      • 2018-07-30
      • 2013-08-02
      • 2017-04-18
      相关资源
      最近更新 更多