This is hopefully an easy question regarding the thread-safety of the View.getWidth() and View.getHeight() methods.
I’m writing a background task that performs an operation given a View object. The only part of the view that is actually necessary is its dimensions. However, there is a possibility that the view hasn’t gone through the layout process when my task is initiated and so its dimensions are set to 0. The task is initiated on the UI but runs a long operation in the background before returning to the UI thread to post the result (much like an AsyncTask).
I was wondering what the repercussions are of calling those getter methods from a background thread. I know that the UI toolkit is designed to be single threaded and so I’m afraid that I may be running the risk of an unsafe publication.
Kcoppock has a great solution here about avoiding this problem all together by receiving a callback on the UI thread when the dimensions are known.
I wrote a small test to see when those dimensions would be visible from the background thread (attached below) and it seems that they are available at the same time as the callback.
Of course I understand that this doesn’t prove anything so I would be grateful if someone with a greater knowledge of the UI framework could chime in.
@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");
}
Since Android UI framework is designed to work in single-threaded environment it doesn’t have any internal synchronization and all View classes are not thread-safe. It means that there is no guarantee that
widthandheightvalues will be visible from your background thread after they were set from UI thread. You may receive stale values or not receive any positive values at all in your background thread. There is a Java memory model which describes how threads in the Java interact through memory and it states what you need to do to make your variables visible through different threads.I suppose you code will work on uniprocessor systems but may possibly fail on SMP. I can’t say that this a severe issue but anyway this code is not correct in respect of Java memory model. To fix it you need to publish
widthandheightvalues safely usingvolatilemodifier orsynchronizedblock orAtomicInteger. Your code will looks something like that:volatilemodifier guarantees that any changes inwidthandheightvariables made from UI thread will be seen immediately in background thread and this background thread will receive most recent values.