处理程序更改UI会导致CalledFromWrongThreadException

时间:2013-05-13 01:59:25

标签: java android multithreading garbage-collection handler

我创建了一个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)被调用时),尽管我我不确定为什么会导致这个错误。

1 个答案:

答案 0 :(得分:0)

我在这里会改变一些事情:

使用处理程序

如果要触发UI更新,Handler非常有用,并且可以从非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