Reputation: 8687
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?
Upvotes: 4
Views: 392
Reputation: 12952
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;
Upvotes: 4
Reputation: 13922
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();
}
Upvotes: 0
Reputation: 264
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();
Upvotes: -1
Reputation: 310
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?
Upvotes: 2
Reputation: 157437
text.setText(s);
you can not touch the UI from your working thread. It should be the UI Thread that execute text.setText(s);
Upvotes: 2