0

My app combines a SurfaceView running on its own thread with a bunch of regular Views running on the main application thread. For the most part, this works fine. However, when I try to have something on the SurfaceView's thread trigger a change to one of the UI elements in the main application thread, I get android.View.ViewRoot$CalledFromWrongThreadException.

Is there a right way around this? Should I use an asynctask? Runonuithread()? Or is mixing a SurfaceView on its own thread with other UI elements on the main thread just an inherently bad thing to do?

If my question doesn't make sense, here's pseudocode that may be clearer.

// Activity runs on main application thread
public class MainActivity extends Activity {
    RelativeLayout layout;
    TextView popup;
    MySurfaceView mysurfaceview;

    protected void onCreate(Bundle savedInstanceState) {
        ...
        setContentView(layout);
        layout.addView(mysurfaceview);  // Surface View is displayed
        popup.setText("You won");       // created but not displayed yet
    }
    public void showPopup() {
        layout.addView(popup);
    }
}

// Surface View runs on its own separate thread
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, OnTouchListener {
    private ViewThread mThread;

    public boolean onTouch(View v, MotionEvent event) { 
        ...
        if (someCondition == true) {
            mainactivity.showPopup();
            // This works because onTouch() is called by main app thread
        }
    }
    public void Draw(Canvas canvas) { 
        ...
        if (someCondition == true) {
            mainactivity.showPopup();
            // This crashes with ViewRoot$CalledFromWrongThreadException
            // because Draw is called by mThread
            // Is this fixable?
        }
    }
}
DevOfZot
  • 1,362
  • 1
  • 13
  • 26

5 Answers5

4

You cannot change UI elements from other threads other than the UI thread. However, you can still post commands to the UI thread from another thread to update the elements.

You can do the following:

Instantiate a handler in the UI thread like:

final Handler handler = new Handler();

Then in your other thread, you can post a message to the UI thread to update your view through the handler as follows:

handler.post(new Runnable(){

    public void run(){
        //Update your view here
    }
});

And you should be goos to go. Just remember, you MUST instantiate the handler in the UI thread.

Or

If you are running your other thread inside an Activity instance, you can use:

this.runOnUiThread(new Runnable(){

     public void run(){
         //your UI update code here
     }
});
hqt
  • 29,632
  • 51
  • 171
  • 250
AxiomaticNexus
  • 6,190
  • 3
  • 41
  • 61
  • +1 for another method - Am I correct in thinking that this will do pretty much what runOnUiThread does, and either will work about as well? – DevOfZot Sep 13 '13 at 19:21
  • They both do the same thing, but you can use the first option anywhere while the second option can only be used inside an `Activity`, since the `runOnUiThread()` method is only declared in the `Activity` class. – AxiomaticNexus Sep 13 '13 at 19:26
1

Should I use an asynctask? Runonuithread()?

Either of these should be fine. I typically use AsyncTask for most things that need to update the UI after doing a background process. But either should work.

AsyncTask Docs

runOnUiThread example and Docs

Community
  • 1
  • 1
codeMagic
  • 44,549
  • 13
  • 77
  • 93
  • Can you clarify where it would live? Should it be created in the MainActivity, and then mysurfaceview.draw() would call what? – DevOfZot Sep 13 '13 at 19:10
  • I guess in this case `runOnUiThread()` may be easier to implement and less cluttered. See my example link (just updated-had the wrong link) – codeMagic Sep 13 '13 at 19:18
  • You're welcome. Typically I use `AsyncTask` for background operations when I need to update the `UI` but after looking at what you had again, `runOnUiThread()` is probably easier. But read through the `AsyncTask` docs a few times if you haven't to be sure you understand how that works for the future...very helpful. – codeMagic Sep 13 '13 at 19:21
1

Why don't you use callback? In MySurfaceView, you can add an interface,

public interface onClickSurfaceView{
     public void change();
}

then in your activity implement MySurfaceView.onClickSurfaceView, and you can have a reference to surfaceView. Then call the surfaceView.setListener(this) to register the activity as a subscriber, and you can call the update your UI in the change() method.

Frank Bryce
  • 8,076
  • 4
  • 38
  • 56
0

simple answer is you cannot update UI elements from a non-UI thread.

you need to have a callback method or something that calls back to the main thread to update the UI

tyczj
  • 71,600
  • 54
  • 194
  • 296
0

You need to use this -

runOnUiThread(new Runnable() {

        @Override
        public void run() {
            // TODO Populate UI

        }
    });
Vishal Pawale
  • 3,416
  • 3
  • 28
  • 32