10

Look, I have the following code:

My Action:

final Intent intent = new Intent(getApplicationContext(), MyService.class)
.putExtra(UploadService.EXTRA_RESULT_RECEIVER, new ResultReceiver(null) {
            @Override
            protected void onReceiveResult(int resultCode, Bundle resultData) {
                super.onReceiveResult(resultCode, resultData);
                String result = resultData.getString(MyService.EXTRA_RESULT_SUCCESS);
                ...
                imageView.setBackgroundDrawable(bitmap);// here my code fails
            }
        })

MyService:

    Bundle b = new Bundle();
    b.putString(EXTRA_RESULT_SUCCESS, response.toString());
    resultReceiver.send(0, b);

And my application fails on line "imageView.setBackgroundDrawable(bitmap)" with the following exception:

11-13 16:25:38.986: ERROR/AndroidRuntime(3586): FATAL EXCEPTION: IntentService[MyService]
    android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

But this doesn't happen when I define receiver like this (with handler):

new ResultReceiver(new Handler()){.../*here goes the same code as in the first example. nothing has been changed*/}

So. It doesn't fail when I pass a default Handler. And I ask Why? In both ways my code is invoked, but when no Handler specified it fails. What influence does Handler have?

unorsk
  • 9,371
  • 10
  • 36
  • 42

3 Answers3

9

The Handler ties into the Android framework and ensures that any code that is run in the Handler's callbacks is executed on the parent Activity's main Looper thread which is where all the Activity lifecycle callbacks and UI calls are made. If you really want to understand how it works you can wander through the source on Github, but running code in a Handler is pretty much guaranteed to put things in the right place.

Femi
  • 64,273
  • 8
  • 118
  • 148
  • I have a doubt. Is that ResultReceiver.onReceiveResult will always run on non-UI thread? – Mahendran Sep 10 '12 at 06:28
  • No, it is that using a Handler guarantees it will run on the right thread. Normally no guarantees are made as to which thread the receiver will be called on. – Femi Sep 10 '12 at 06:45
  • Got it! Its based on the thread calling the result receiver. Thanks for quick reply. – Mahendran Sep 10 '12 at 06:52
  • 1
    This answer has saved me hours of debugging time. Thank you! – Nathan Osman May 20 '13 at 19:26
  • If possible, can you provide the starting place to start exploring this in the source code? Would be best to start in ResultReceiver and work up or ...? – Jeel Shah Mar 16 '18 at 23:45
3

The problem is because imageView.setBackgroundDrawable() is called from your service's thread. This is incorrect. You need to make sure that any UI update is executed from the UI thread.

It is hard to explain exactly what needs to change from the snippets you provide.

Android provides a number of techniques to allow non-UI threads to interact with UI components (and the Handler class one of the option). IMHO, this is one of the most critical concept to get right if you want to develop good Android applications.

Some useful links:

http://developer.android.com/resources/articles/painless-threading.html

What is the Android UiThread (UI thread)

http://www.vogella.de/articles/AndroidPerformance/article.html

Community
  • 1
  • 1
kctang
  • 10,894
  • 8
  • 44
  • 63
  • 1
    Yeah, I see that problem happens because UI update is being executed from another thread. But this doesn't happen when I pass a default Handler. So when I pass it, does it make to work all stuff from a UI thread instead of creating new one? Thanks for links, look like an interesting reading! – unorsk Nov 14 '11 at 08:16
0

The following works for me. You can run the bitmap update in the same thread of your UI activity using "runOnUiThread" method. You should define the following class as inner class of your UI activity:

class UpdateUI implements Runnable
{
    Bitmap newBitmap;

    public UpdateUI(Bitmap newBitmap) {
        this.newBitmap = newBitmap;
    }
    public void run() {
         imageView.setBackgroundDrawable(newBitmap);
    }
}

Then in your resultReceiver:

@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
    // after get your bitmap
    runOnUiThread(new UpdateUI(receivedBitmap));
    ...
}
Nico
  • 858
  • 10
  • 20