简体   繁体   中英

Android Thread.start() CalledFromWrongThreadException

I am not sure if my understating is correct because I am not getting the expected output. I have a class within which I am calling a method that is supposed to start a thread.

public class MainActivity extends Activity {

protected void onCreate(Bundle savedInstanceState) {
beginListenForData()
}

The beginListenForData function is to start a thread and check at times if data is there for reading. If that's the case, it reads and updates a UI variable :

void beginListenForData()
{
    Thread workerThread = new Thread(new Runnable() {
        @Override
        public void run()
        {    
            int bytesAvailable = 3;
            while(!Thread.currentThread().isInterrupted())
            {
                try
                {
                    bytesAvailable = mmInStream.available();
                    if(bytesAvailable > 0)
                    {
                        byte[] packetBytes = new byte[bytesAvailable];
                        mmInStream.read(packetBytes);
                bytesAvailable = mmInStream.available();
                String s = new String(packetBytes);
                text.setText(s);
                    }
                }
                catch (Exception e)
                {
            // TODO Auto-generated catch block
                e.printStackTrace();
                }
            }
        }
    });

       workerThread.start();
   }

}

I am not getting the desired output. That thread should read the data or check if the data is available. If available then read it and set a UI variable to the values read.

Am I doing the implementation correctly? Do I have something wrong in my code?

A normal Thread should not access the UI thread . I would advise using an AsyncTask instead of using standard Threads or Runnables in Android. An AsyncTask can be used to both simultaneously work away from the UI thread, and then make changes to it. Everything called in the doInBackground() method is done away from the main UI thread, and everything called in the onPreExecute() and onPostExecute() methods can interact nicely with your UI.

An AsyncTask is recommended when your calling a new thread from something running on the UI Thread (like an Activity , as in your instance). Example below;

public class YourAsyncTask extends AsyncTask{

    @Override
    protected Object doInBackground(Object... arg0) {
        // Work to be done in the background.
        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        // Changes to be made to UI
    }

    @Override
    protected void onPreExecute() {
         // Changes to be made to UI
    }

}

Then you can just run the AysncTask from your activity like so;

new YourAsyncTask().execute("");

To work on your Activity , you may need to create a custom constructor for your AysncTask and pass your activity instance to it through the constructor, to be stored in an instance variable. I hope this helps.

Further Information;

 text.setText(s);

you can not touch the UI from your working thread. It should be the UI Thread that execute text.setText(s);

Thread can't update GUI You can use runOnUiThread() method or use Handler

EDIT : example of Handler : How to use an Android Handler to update a TextView in the UI Thread?

So here is how i would do this

create member variables of your activity:

private BackgroundWorkerThread m_bgThread = null;
protected Handler m_bgHandler = null;

See below my BackgroundWorkerThread class and then to initialize the thread and handler in your main activity in onCreate():

  // Notice we pass the Handler Constructor this because we are implementing the interface
  // in our activity
  m_bgHandler = new Handler(this)
  m_bgThread = new BackgroudnWorkerThread();
  m_bgThread.start();

make sure you have your activity implements Handler.Callback interface which will add this method to your Activity:

@Override public boolean handleMessage(Message msg)
 {
     switch(msg.what){

     case 0:
          // Will update the UI on the Main Thread
          updateUI(msg.obj);
      break;

     case 1:
         // rejoin the Thread with the main Thread
        try {
           m_bgThread.join();
        } catch (InterruptedException e) {
           // TODO Auto-generated catch block
          e.printStackTrace();
      }
      break;
   }
    return true;
 }

inside each case statement is where you do message passing to the background thread.

for example calling sendFirstMessage() somewhere in your activity:

 public void sendFirstMessage(){

    // Passes the 0 to the background Handler handleMessage
    m_bgThread.workerHandler.obtainMessage(0).sendToTarget();

 } 

So then in your Activity's handler handleMessage() override

 case 0:
     updateUI(msg.obj);

 break;

and then

public void updateUI(Object obj){

    text.setText(obj.toString());

    // Call to tell Background Thread to shut down looper
    m_bgThread.workerHandler.obtainMessage(1).sendToTarget();

    // Call to tell the Main Thread no more updates needed
    m_bgHandler.obtainMessage(1).sendToTarget();

}

Then create your background thread class as a child class of your activity:

private class BackgroundWorkerThread extends Thread implements Handler.Callback{
  private Looper workerLooper;
  protected Handler workerHandler;

 @Override public void run()
 {
     // Set up the Handler and Looper
     Looper.prepare();
     workerLooper = Looper.myLooper();
     workerHandler = new Handler(workerLooper, this);
     Looper.loop();
 }

 @Override public boolean handleMessage(Message msg)
 {
     switch(msg.what){

     case 0:
         // now this function is being called to run on the background Thread  
      beginListenForData();
      break;

     case 1:
      workerLooper.quit();
      break;
   }
    return true;
 }

}


  // This method is now being execute in the background
 public void beginListenForData()
 {

           int bytesAvailable = 3;
          while(!Thread.currentThread().isInterrupted())
          {
            try{
                bytesAvailable = mmInStream.available();
                if(bytesAvailable > 0)
                {
                byte[] packetBytes = new byte[bytesAvailable];
                mmInStream.read(packetBytes);
                bytesAvailable = mmInStream.available();
                String s = new String(packetBytes);
                // Instead of trying to set the text view here, send a message back to 
                // the Activity to update the UI on the Main thread
                // text.setText(s);
                // Passes 0, and the String Object to the Activity Handler
                 m_bgHandler.obtainMessage(0, s).sendToTarget();
                }
        }catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
          }
       }
  }

This is a little difficult to wrap your head around but this is how its done, manually creating a thread, using a looper to check for messages in the background, and then passing messages to the activity from the background thread to Update the UI.

Good Luck! any questions feel free to ask.

Or you could take the easiest route possible ideology taken from http://android-developers.blogspot.com/2009/05/painless-threading.html

 public void beginListenForData()
  {
   String s = null;

   new Thread(new Runnable(){

      public void run(){

           int bytesAvailable = 3;
          while(!Thread.currentThread().isInterrupted())
          {
            try{
                bytesAvailable = mmInStream.available();
                if(bytesAvailable > 0)
                {
                  byte[] packetBytes = new byte[bytesAvailable];
                  mmInStream.read(packetBytes);
                  bytesAvailable = mmInStream.available();
                  s = new String(packetBytes);

                   // Takes Care of updating the UI
                   text.post(new Runnable(){
                       public void run(){
                           text.setText(s);
                       }
                    });
                }
             }catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
             }
       }
     }).start();
  }

Did you call super.onCreate(savedInstanceState); first ?

Then you have to call setContentView(R.layout.yourlayout); .

Why bytesAvailable is set to 3 and not 0 ?

Why bytesAvailable = mmInStream.available(); is call twice ? Only one is enough !

Then are mmInStream and text ( !important! text = (TextView) findViewById(R.id.thetextviewid); ) initialised somewhere ?

If all of these things are done, everything should be ok.

EDIT: at the end you have to call text.postInvalidate();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM