Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Runnable is posted successfully but not run

In an existing Android project I've encountered the following piece of code (where I inserted the debugging litter)

ImageView img = null;

public void onCreate(...) {

    img = (ImageView)findViewById(R.id.image);

    new Thread() {
        public void run() {
            final Bitmap bmp = BitmapFactory.decodeFile("/sdcard/someImage.jpg");
            System.out.println("bitmap: "+bmp.toString()+" img: "+img.toString());
            if ( !img.post(new Runnable() {
                public void run() {
                    System.out.println("setting bitmap...");
                    img.setImageBitmap(bmp);
                    System.out.println("bitmap set.");
                }
            }) ) System.out.println("Runnable won't run!");
            System.out.println("runnable posted");
        }
    }.start();

New to Android development, and having googled around, I understand that this is the way to do stuff without blocking the main (UI) thread, while still setting the image on the UI thread after decoding. (at least according to android-developers) (which I have verified by logging Thread.currentThread().getName() at various places)

Now sometimes the image just doesn't show up, and stdout only says

I/System.out( 8066): bitmap: android.graphics.Bitmap@432f3ee8 img: android.widget.ImageView@4339d698
I/System.out( 8066): runnable posted

with not a trace of the messages from the Runnable. So appearantly the Runnable doesn't run(), although img.post() returns true. Pulling the ImageView in onCreate() and declaring it final doesn't help.

I'm clueless. Simply setting the bitmap directly, while blocking the UI thread, does fix things, but I want to get things right. Does anybody understand what's going on here?

(ps. this was all observed on an Android 1.6 phone and android-3 sdk)

like image 300
mvds Avatar asked Nov 03 '10 02:11

mvds


3 Answers

If you look at the docs for View.post there's some relevant info:

This method can be invoked from outside of the UI thread only when this View is attached to a window.

Since you're doing this in onCreate, it is likely that sometimes your View will not be attached to the window yet. You can verify this by overriding onAttachedToWindow and putting something in the logs and also logging when you post. You'll see that when the post fails, the post call happens before onAttachedToWindow.

As the others have mentioned, you can use Activity.runOnUiThread or provide your own handler. However, if you want to do it directly from the View itself, you can simply get the View's handler:

view.getHandler().post(...);

This is especially useful if you have a custom view that includes some sort of background loading. There's also the added bonus of not having to create a new separate handler.

like image 175
kabuko Avatar answered Nov 14 '22 22:11

kabuko


I think the problem is you are updating the UI (ImageView) with a separate thread, which is not the UI Thread. The UI can only be updated by the UI Thread.

You can solve this by using Handler:

Handler uiHandler;

public void onCreate(){
    ...
    uiHandler = new Handler(); // This makes the handler attached to UI Thread
    ...
}

Then replace your:

if ( !img.post(new Runnable() {

with

uiHandler.post(new Runnable() {

to make sure the imageview is updated in the UI Thread.

Handler is a quite confusing concept, I also took hours of research to really understand about this ;)

like image 44
xandy Avatar answered Nov 14 '22 23:11

xandy


I extended ImageView class to solve this problem. I collect runnables passed to post while view not attached to window and in onAttachedToWindow post collected runnable.

public class ImageView extends android.widget.ImageView
{
    List<Runnable> postQueue = new ArrayList<Runnable>();
    boolean attached;

    public ImageView(Context context)
    {
        super(context);
    }

    public ImageView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    public ImageView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onAttachedToWindow()
    {
        super.onAttachedToWindow();

        attached = true;

        for (Iterator<Runnable> posts = postQueue.iterator(); posts.hasNext();)
        {
            super.post(posts.next());
            posts.remove();
        }
    }

    @Override
    protected void onDetachedFromWindow()
    {
        attached = false;
        super.onDetachedFromWindow();
    }

    @Override
    public boolean post(Runnable action)
    {
        if (attached) return super.post(action);
        else postQueue.add(action);
        return true;
    }
}
like image 38
Nik Avatar answered Nov 15 '22 00:11

Nik