3

View.getWidth()希望这是一个关于和View.getHeight()方法的线程安全的简单问题。

我正在编写一个后台任务,该任务执行给定 View 对象的操作。视图中唯一真正需要的部分是它的维度。但是,当我的任务启动时,视图可能没有经过布局过程,因此它的尺寸设置为 0。任务在 UI 上启动,但在返回到发布结果的 UI 线程(很像AsyncTask)。

我想知道从后台线程调用这些 getter 方法会产生什么影响。我知道 UI 工具包被设计为单线程的,所以我担心我可能会冒着发布不安全出版物的风险。

Kcoppock 有一个很好的解决方案,当尺寸已知时,通过在 UI 线程上接收回调来避免这个问题

我写了一个小测试来查看这些维度何时可以从后台线程(附在下面)中看到,并且似乎它们与回调同时可用。

当然,我知道这并不能证明任何事情,所以如果有人对 UI 框架有更深入的了解,我将不胜感激。

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final ImageView view = new ImageView(this);
    view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            Log.d("TAG", Thread.currentThread().getName() + ": onGlobalLayout()");
            Log.d("TAG", Thread.currentThread().getName() + ": view dimensions: " + view.getWidth() + "x" + view.getHeight());
        }
    });

    // Bad code follows
    new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                Log.d("TAG", Thread.currentThread().getName() + ": view dimensions: " + view.getWidth() + "x" + view.getHeight());
                try {
                    Thread.sleep(1);
                } catch (InterruptedException ignored) {}
            }
        }
    }).start();

    setContentView(view);
    Log.v("TAG", "Set view as content");
}
4

1 回答 1

2

由于 Android UI 框架设计为在单线程环境中工作,因此它没有任何内部同步,并且所有 View 类都不是线程安全的。这意味着在从 UI 线程设置它们之后,不能保证从后台线程中可以看到width和值。height您可能会在后台线程中收到陈旧的值或根本没有收到任何正值。有一个Java 内存模型,它描述了 Java 中的线程如何通过内存进行交互,并说明了您需要做什么才能使变量通过不同的线程可见。

我想您的代码可以在单处理器系统上运行,但在 SMP 上可能会失败。我不能说这是一个严重的问题,但无论如何这段代码在 Java 内存模型方面是不正确的。要修复它,您需要使用修饰符或width块或. 您的代码将如下所示:heightvolatilesynchronizedAtomicInteger

...
private volatile int width;
private volatile int height;
...
@Override
public void onGlobalLayout() {
    // here values are published safely
    width = view.getWidth();
    height = view.getHeight();
}
...
new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
            // and here they are read
            Log.d("TAG", Thread.currentThread().getName() + ": view dimensions: " + width + "x" + height);
            try {
                Thread.sleep(1);
            } catch (InterruptedException ignored) {}
        }
    }
}).start();

volatile修饰符保证 UI 线程中的任何更改widthheight变量将立即在后台线程中看到,并且此后台线程将接收最新值。

于 2012-07-15T17:40:36.540 回答