希望这是一个关于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");
}
答案 0 :(得分:2)
由于Android UI框架设计为在单线程环境中工作,因此它没有任何内部同步,并且所有View类都不是线程安全的。这意味着无法保证在从UI线程设置后,后台线程中可以看到width
和height
值。您可能会在后台线程中收到过时的值或者根本没有收到任何正值。有一个Java memory model描述了Java中的线程如何通过内存进行交互,并说明了需要做什么才能使变量通过不同的线程可见。
我认为您的代码可以在单处理器系统上运行,但可能在SMP上失败。我不能说这是一个严重的问题,但无论如何这个代码在Java内存模型方面是不正确的。要解决此问题,您需要使用width
修饰符或height
阻止或volatile
安全地发布synchronized
和AtomicInteger
值。您的代码看起来像这样:
...
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线程发生的width
和height
变量的任何更改,并且此后台线程将接收最新值。