【问题标题】:Android TextView.setText() invoked & returned before Thread.sleep() blocks until sleep() returns. Why?Android TextView.setText() 在 Thread.sleep() 阻塞之前调用并返回,直到 sleep() 返回。为什么?
【发布时间】:2013-05-16 19:07:10
【问题描述】:

在Android框架中,如果调用了TextViewsetText()方法,并且在它返回后调用了Thead.sleep(),那么设备的屏幕直到sleep()之后才会显示给定的文本回来。为什么?

public class SleeperActivity extends Activity {
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.main);
    }

    public void doDelay(View view) {
        try {
            TextView textView = (TextView) view;
            textView.setText("Sleeping");
            Log.d("Sleeper", "This log entry is invoked before sleeping");
            Thread.sleep(5000L);
            textView.setText("Ready");
        } catch (InterruptedException e) { finish(); }
    }
}

未显示的布局有一个按钮,其onClick 属性设置为doDelay()

当上面的代码编译并运行,并且点击按钮时,在setText()的第一次调用中传递的文本直到线程休眠五秒后才会出现在屏幕上,即使日志条目在这五秒开始之前出现在日志中。

为什么会发生这种情况,我应该怎么做才能使第一次调用 setText() 时传递的文本在线程开始休眠之前出现在屏幕上?

【问题讨论】:

    标签: java android multithreading


    【解决方案1】:

    1) 永远不要在 UI 线程上睡那么久。事实上,永远不要睡在上面。这会导致它被阻止并使手机无响应

    2) setText 在视图上调用无效。这将导致框架尽快再次绘制视图 - 但这意味着需要完成当前正在做的事情并返回主 Looper(基本上,它需要从它首先调用的代码中的任何方法返回)。在此之前,屏幕上不会显示更改。

    编辑:换句话说,在我们拥有的框架内

    do{
        Message msg = nextMessage();
        if(msg.what == ON_CREATE){
           currentActivity.onCreate();
        }
        else if(msg.what == DRAW){
            rootView.onDraw();
            //draw entire screen
        }
        else 
          ...  //Thousands of more events like touches, handlers, etc
    

    在处理完当前消息之前,它无法获取绘图消息。它是单线程的。

    【讨论】:

    • 关于#1:我知道,谢谢。这只是我用来理解这种现象的一个教学示例。这不是生产代码。关于#2:我不明白你。我想你漏掉了一些话。 “当前正在做的事情”是什么意思? “它首先调用的代码是什么意思?”显然setText() 是在调用sleep() 之前“我的代码中它首先调用的方法”,并且该方法在调用sleep() 之前返回。显然我误解了你。您能否向我展示我给出的示例代码的最小必要更改,以使文本按需要显示?
    • 在框架中,基本上有一个巨大的消息循环。该循环等待处理程序中的消息、IO 事件、Android 框架生成的事件(如 onCreate)等事件。当您调用 setText() 时,它会向该循环发送一条 DRAW 消息。在循环可以处理该 DRAW 命令之前,不会实际绘制视图。直到 onCreate 函数完成后才会发生这种情况。没有比这更快的方法了。
    • 是什么onCreate() 阻止循环处理 DRAW 命令直到它完成?我知道如果sleep() 不在那里,显示文本不需要五秒钟,所以必须有一种方法来(1)显示文本,(2)等待循环处理 DRAW,并且(3) 按此顺序调用sleep()。循环对 DRAW 命令的处理取决于 (a) 时间的流逝,还是 (b) onCreate() 方法的完成?听起来你说的是 (b),但你也使用了“更快”这个词,这听起来像是一个时间问题。解决方法是什么?
    • 循环在与 onCreate 相同的线程上执行。 onCreate 实际上是由循环调用的。所以消息循环不会要求下一条消息,直到它回到顶部的巨大 while 语句。在完成 onCreate 之前它无法做到这一点。也许我的编辑会让它更清晰。
    • 好的,我很清楚什么不起作用,但我仍然想知道什么起作用。如果有人聘请你编写一个 Android 应用程序,它在启动时:首先在屏幕上显示一些文本,然后一旦该文本可见就会阻塞主线程五秒钟,然后在五秒钟后在屏幕上显示一些其他文本,是这可能与否?如果可能,那么您提供的代码与我发布的代码有何不同?
    【解决方案2】:

    观察到行为的原因是Views 仅在通过事件循环的每个循环完成后才重绘。 android onCreate() 方法以及诸如单击按钮之类的触摸事件由框架从事件循环中调用。因此,诸如onCreate()onClick() 之类的方法将在重绘任何Views 之前执行完成,并且在这些方法返回之前不会对显示产生可见影响。在这些方法中对Views 所做的更改只有在框架调用所述方法的事件循环周期完成后才会变得可见(onCreate()onClick())。

    要实现问题中要求的行为,必须在您使用要显示的文本调用 setText() 的事件循环完成之后调用 sleep() 方法在线程阻塞期间。一种方法是简单地将对sleep() 的调用替换为对View.post() 的调用。 View.post() 采用 Runnablerun() 方法将被调用当前事件循环的循环已经完成并且框架已经显示了你想在线程运行期间看到的文本阻塞。更改问题中的doDelay() 方法,方法是将调用Thread.sleep() 放在Runnablerun() 方法中,如下所示:

        public void doDelay(View view) {
            final TextView textView = (TextView) view;
            textView.setText("Sleeping");
            textView.post(new Runnable() {
                public void run() {
                    try {
                        Log.d("Sleeper", "Thread " + Thread.currentThread() + " going to sleep...");
                        Thread.sleep(5000L);
                        Log.d("Sleeper", "Thread " + Thread.currentThread() + " now awake");
                        textView.setText("Ready");
                    } catch (InterruptedException e) { finish(); }
                }
            });
        }
    

    感谢Gabe Sechan,他的指导让我无法回答自己的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 2020-10-09
      • 1970-01-01
      • 1970-01-01
      • 2020-08-10
      • 1970-01-01
      相关资源
      最近更新 更多