从后台线程访问视图尺寸

时间:2012-07-05 16:44:19

标签: android multithreading user-interface thread-safety

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

我正在编写一个后台任务,在给定View对象的情况下执行操作。实际需要的视图的唯一部分是其尺寸。但是,当我的任务启动时,视图可能没有经历布局过程,因此其尺寸设置为0.任务在UI上启动但在后台运行长操作,然后返回到UI线程发布结果(很像AsyncTask)。

我想知道从后台线程调用这些getter方法的后果是什么。我知道UI工具包是专门设计的,所以我担心我可能会冒不安全的出版风险。

Kcoppock有一个很好的解决方案here,通过在维度已知的情况下在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");
}

1 个答案:

答案 0 :(得分:2)

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

我认为您的代码可以在单处理器系统上运行,但可能在SMP上失败。我不能说这是一个严重的问题,但无论如何这个代码在Java内存模型方面是不正确的。要解决此问题,您需要使用width修饰符或height阻止或volatile安全地发布synchronizedAtomicInteger值。您的代码看起来像这样:

...
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变量的任何更改,并且此后台线程将接收最新值。