3

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");
}
Community
  • 1
  • 1
zienkikk
  • 2,404
  • 1
  • 21
  • 28

1 Answers1

2

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 width and height values 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 width and height values safely using volatile modifier or synchronized block or AtomicInteger. Your code will looks something like that:

...
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 modifier guarantees that any changes in width and height variables made from UI thread will be seen immediately in background thread and this background thread will receive most recent values.

Andrei Mankevich
  • 2,253
  • 16
  • 15
  • Thanks Andrei. I'm well aware of the memory model but I just hoped (foolishly) that the UI would establish the happens-before relationship for us. Oh well. BTW, in the current implementation the background thread could see a correct value for width but an old value for height. A better approach would use a thread safe point (or pair) class that can update width and height atomically (or like you suggested a simple synchronized block). – zienkikk Jul 22 '12 at 17:41