I don't understand how an handler can be used to update a TextView during the flow of a program. I'm testing it on a simple code like this
public class MainActivity extends AppCompatActivity {
TextView text;
boolean isReady; //boolean to check a new Message
String update; //String to send to TextView
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.textView);
Handler handler = new MyHandler(); //defined down
MyHandlerThread thr = new MyHandlerThread(handler); //defined in other .class file
thr.start();
for(int i=0;i<100;i++){ //a simple for
if(i%2==0)
thr.setMessage(i + ": Divisible for 2");
else
thr.setMessage(i+": Not Divisible for 2");
}
}
private class MyHandler extends Handler { //inner class
#Override
public void handleMessage(Message msg) {
Bundle bundle = msg.getData();
if(bundle.containsKey("refresh")) {
String value = bundle.getString("refresh");
text.setText(value);
}
}
}}
and this is the thread code
public class MyHandlerThread extends Thread {
private Handler handler;
private boolean isSent;
String text;
public MyHandlerThread(Handler handler) {
this.handler = handler;
isSent=false;
text="";
}
public void run() {
try {
while(true) {
if(isSent){
notifyMessage(text);
Thread.sleep(1000);
isSent=false;
}
}
}catch(InterruptedException ex) {}
}
public void setMessage(String str){
text=str;
isSent=true;
}
private void notifyMessage(String str) {
Message msg = handler.obtainMessage();
Bundle b = new Bundle();
b.putString("refresh", ""+str);
msg.setData(b);
handler.sendMessage(msg);
}}
this code just print the last expected string "99: not divible for 2"
You are just calling 100 times your thread setMessage, so texts will overwrite each other before your thread loop has any chance to print them.
You should implement a queue in your setMessage, then the thread loop should pop the next element from the queue, print it (sending a message via the Handler) then sleep. If no more elements are in the queue, just loop until one becomes available.
Related
I am working on a Bluetooth communication project where I need to transfer data among devices.Upon receiving an InputStream I pass the data to the UI thread from the worker thread using the following code:-
// Read from the InputStream.
numBytes = mmInStream.read(mmBuffer);
// Send the obtained bytes to the UI activity.
Message readMsg = handler.obtainMessage(MessageConstants.MESSAGE_READ,numBytes, -1,mmBuffer);
readMsg.sendToTarget();
Below is my handler class:-
public Handler mHandler = new Handler() {
public synchronized void handleMessage(Message msg) {
byte[] readBuf=(byte[])msg.obj;
String readMsg=new String(readBuf,0,msg.arg1);
TextView textView=findViewById(R.id.textview);
textView.setText(readMsg);
}
}
But This shows the following warning:
This Handler class should be static or leaks might occur(anonymous android.os.Handler).
I tried making the class static but then it gives the following error:-
Non-static method findViewById(int) can't be referenced from a static context.
What should I do to resolve this?
public MyHandler mHandler;
public static class MyHandler extends Handler {
WeakReference<TextView> mTextViewReference;
public MyHandler(TextView textView) {
mTextViewReference = new WeakReference<TextView>(textView);
}
public synchronized void handleMessage(Message msg) {
byte[] readBuf=(byte[])msg.obj;
String readMsg = new String(readBuf,0,msg.arg1);
TextView textView = mTextViewReference.get();
if(textView != null) {
textView.setText(readMsg);
};
}
public void clear() {
mTextViewReference.clear();
mTextViewReference = null;
}
}
protected void onCreate(final Bundle savedInstanceState) {
....
mHandler = new MyHandler(findViewById(R.id.textView));
....
}
#Override
public void onDestroy() {
if(mHandler != null) {
mHandler.clear();
mHandler = null;
}
super.onDestroy();
}
EDIT
Fix above works fine if you just want to update one single TextView. However, very often, you need to take more actions and update more stuff (not only a single TextView). So, I think you can create a Interface that is invoked every time a message is received. Something like:
public class MyActivity extends Activity {
public MyHandler mHandler;
protected final void onCreate(final Bundle savedInstanceState) {
//....
mHandler = new MyHandler(new MyHandler.OnMessageReceivedListener() {
#Override
public void handleMessage(final String message) {
// Update the views as you with
}
});
//....
}
#Override
public void onDestroy() {
super.onDestroy();
mHandler.clear();
}
public static class MyHandler extends Handler {
WeakReference<OnMessageReceivedListener> mListenerReference;
public MyHandler(OnMessageReceivedListener listener) {
mListenerReference = new WeakReference<>(listener);
}
public synchronized void handleMessage(Message msg) {
byte[] readBuf=(byte[])msg.obj;
String readMsg = new String(readBuf,0,msg.arg1);
OnMessageReceivedListener listener = mListenerReference.get();
if(listener != null) {
listener.handleMessage(readMsg);
};
}
public void clear() {
mListenerReference.clear();
}
public interface OnMessageReceivedListener {
void handleMessage(String message);
}
}
}
You're not doing very heavy staff in your handleMessage part, so no need to extend Handler keep it simple and ligthweight; just add a callback instead. Create a callback in your Activity/Fragment:
private class MessageCallback implements Handler.Callback {
#Override
public boolean handleMessage(#NonNull Message message) {
// Here you can call any UI component you want
TextView textView=findViewById(R.id.textview);
textView.setText(readMsg);
return true;
}
}
Then call it as:
Handler handler = new Handler(getMainLooper(), new MessageCallback());
Message readMsg = handler.obtainMessage(what, arg1, arg2, object);
readMsg.sendToTarget();
Wrote an application to an Android to understand the control of view items from the child thread. Took the main looper from named Mainactivity and passed it through the constructor to a class that implements a child thread and that just changes the contents of TextView through callback.
public class MainActivity extends AppCompatActivity {
private TextView textView = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("create", Thread.currentThread().toString());
textView = (TextView) findViewById(R.id.textView);
HandlerTextView handlerTextView = new HandlerTextView(getApplicationContext().getMainLooper());
handlerTextView.registerMessage(new Message() {
#Override
public void handleMessage(String msg) {
textView.setText(msg);
}
});
HandlerThread handlerThread = new HandlerThread("two", HandlerThread.NORM_PRIORITY);
if(handlerThread != null){
Handler a = new Handler(handlerThread.getLooper());
if(a != null) {
a.post(handlerTextView);
}
}
}
}
public class HandlerTextView implements Runnable {
private Looper mainLooper = null;
private TextView textView = null;
private Socket socket = null;
private Message message = null;
public HandlerTextView(Looper looper) {
this.mainLooper = looper;
}
public void registerMessage(Message m) {
this.message = m;
}
private void setText(final String str) {
Handler handler = new Handler(mainLooper);
handler.post(new Runnable() {
#Override
public void run() {
message.handleMessage(str);
}
});
}
#Override
public void run() {
setText("dfsdfsdfsdf");
}
}
Created a new Handlerthread thread handler object, took it looper
and passed it to the constructor of the new handler handler and it
The error: FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity
ComponentInfo{elaneturn.com.myapplication/elaneturn.com.myapplication.MainActiv Caused by: java.lang.NullPointerException
Call handlerThread.start() before creating Handler:
handlerThread.start();
Handler a = new Handler(handlerThread.getLooper());
I have one class in which a listener is implemented and depending on the methods call in listener, I am to update the data in corresponding activities. For this, i am interested in using the different-different handlers for each activity.
Since, my handler class in each activity is static and hence, handler object is non-static in activity.
So, problem is how to use those activity's handlers to send Message to them.? Help?
This is one of my class in which I am to update the data from another class: How to do?
public class HandlerActivity extends Activity {
TextView tv;
int counter;
Handler handler = new HandlerExtension(this);
//handler static class
private static class HandlerExtension extends Handler {
private final WeakReference<HandlerActivity> targetActivity;
//constructor
HandlerExtension(HandlerActivity activity){
this.targetActivity = new WeakReference<HandlerActivity>(activity);
}
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
HandlerActivity activity = targetActivity.get();
if(activity != null){
final String data = msg.getData().getString("counter");//gettting msg data
activity.tv.setText(data);
}
}
}//HandlerExtension
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handler_activity);
Button startB = (Button) findViewById(R.id.button1);
tv = (TextView) findViewById(R.id.textView1);
final Runnable runnable = new Runnable() {//object to be executed in the thread
#Override
public void run() {
counter+=10;
String result = String.valueOf(counter);
Message msg = new Message();
Bundle bundle = new Bundle();
bundle.putString("counter", result);
msg.setData(bundle);
handler.sendMessage(msg);//Now, updating the view from background thread via handler
SystemClock.sleep(1000);
}
};
//starting the background thread on button click
startB.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Thread(runnable).start();
}
});
}//onCreate
}//HandlerActivity
getting CalledFromWrongThreadException, Only the original thread that created a view hierarchy can touch its views.
on the line, handler2.sendMessage(msg); which is for sending a short text message from the UI thread to MulitThreaderSendback class below.
calling the sendMessage(msg) works perfectly when calling it from inside of the onCreate method, however when i put it inside of the onClick listener it is causing this exception crash.
how can I avoid this crash and be able to call it from an onclick listener so I can use a button press to send the message?
FROM LOGCAT:
E/AndroidRuntime(14366): FATAL EXCEPTION: Thread-1433
E/AndroidRuntime(14366): android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime(14366): android.view.ViewRootImpl.checkThread(ViewRootImpl.java:5432)
E/AndroidRuntime(14366): android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:970)
Code is:
public class MainActivity extends Activity implements Runnable {
TextView textViewOne;
TextView textViewTwo;
TextView textViewThree;
TextView textViewFour;
TextView textViewFive;
RelativeLayout relativeLayoutOne;
Handler handler;
Handler handler2;
Message msg;
Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
relativeLayoutOne = (RelativeLayout) findViewById(R.id.relativeLayout1);
textViewOne = (TextView) findViewById(R.id.textView1);
textViewTwo = (TextView) findViewById(R.id.textView2);
textViewThree = (TextView) findViewById(R.id.textView3);
textViewFour = (TextView) findViewById(R.id.textView4);
textViewFour = (TextView) findViewById(R.id.textView5);
handler = new Handler();
// starting this class that implements Runnable
this.run();
// starting class that extends Thread
MultiThreader multiThreader = new MultiThreader(handler);
multiThreader.start();
// starting class that implements Runnable
MultiThreader2 multiThreader2 = new MultiThreader2(handler);
Thread thread = new Thread(multiThreader2);
thread.start();
MulitThreaderSendback multiSendBack = new MulitThreaderSendback();
Thread thread2 = new Thread(multiSendBack);
thread2.start();
synchronized (multiSendBack) {
while (handler2 == null) {
try {
multiSendBack.wait();
} catch (InterruptedException e) {
//Ignore and try again.
}
}
}
Bundle bundle = new Bundle();
String messageToSend = "message from UI class";
bundle.putString("message", messageToSend);
Random random = new Random();
int messageText = random.nextInt(30);
String putIt = String.valueOf(messageText);
bundle.putString("message", putIt);
msg = Message.obtain();
msg.setData(bundle);
// handler2.sendMessage(msg); // <-- WORKS OK HERE, NO CRASH
relativeLayoutOne.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
handler2.sendMessage(msg); // <-- FATAL ERROR: CalledFromWrongThreadException
}
});
} // end oncreate
public void setTextViewThree(String textToSet) {
textViewFour.setText(textToSet);
}
public void setTextViewFour(String textToSet) {
textViewFive.setText(textToSet);
}
// local class runnable
#Override
public void run() {
textViewOne.setText("message from local class runnable");
}
// multiThreader inner class
class MultiThreader extends Thread {
Handler handler;
public MultiThreader(Handler handler) {
this.handler = handler;
}
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
textViewTwo.setText("message from multithreader class");
} // end inner run
});
} // end outer run
} // end MultiThreader inner class
// external multiThreader2 inner class
class MultiThreader2 implements Runnable {
Handler handler;
public MultiThreader2(Handler handler) {
this.handler = handler;
}
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
textViewThree.setText("message from MultiThreader2 class");
} // end inner run
});
} // end outer run
} // end MultiThreader2 inner class
class MulitThreaderSendback implements Runnable {
int x = 0;
#Override
public void run() {
// TODO Auto-generated method stub
Looper.prepare();
synchronized(this) {
handler2 = new Handler() {
public void handleMessage(Message msg) {
Bundle bundle = msg.getData();
String receivedString = bundle.getString("message");
setTextViewThree("string received from UI thread: " + receivedString);
}
};
notifyAll();
}
Looper.loop();
} // end run method
} // end MultiThreaderSendback inner class
} // end outer class mainactivity
You can embed the line handler2.sendMessage(msg); in this method
runOnUiThread(new Runnable() {
public void run() {
}
});
Try this
I am having great difficulty to convert a non static handler into a static handler.
I have tried various approaches but every time I end up in doing something messy.
In the provided code please correct me how to achieve this. I did tried one example and I had to change most of my variables and functions into static which were referenced from the handler. But still i got additional errors.
public class MainActivity extends Activity implements Runnable {
private TextView textView;
boolean connectionToTupleSpace=false;
private TupleSpace ts = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.myText);
textView.setText("it is testing");
findViewById(R.id.login_button).setOnClickListener(buttonLogin);
}
public final Button.OnClickListener buttonLogin = new Button.OnClickListener() {
#Override
public void onClick(View v) {
Thread thread = new Thread(MainActivity.this);
thread.start();
}
};
#Override
public void run() {
Looper.prepare();
try {
ts = new TupleSpace("192.168.1.100",2525,"Orientation");
connectionToTupleSpace = true;
}catch (TupleSpaceException e) {
connectionToTupleSpace = false;
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
private Handler handler = new Handler() {
private Looper myLooper;
#Override
public void handleMessage(Message msg) {
if(connectionToTupleSpace == true)
{
Toast.makeText(getBaseContext(), " Tuple Space Server is Connected", Toast.LENGTH_SHORT).show();
showTuples();
}
else
{
/*Toast.makeText(getBaseContext(), " No connection to Tuple Space Server", Toast.LENGTH_SHORT).show();*/
showDialog("Connection", "there is no connection");
}
myLooper = Looper.myLooper();
Looper.loop();
myLooper.quit();
}
};
public void showTuples()
{
Tuple template = new Tuple(String.class, Integer.class);
Tuple[] returnTuple = null;
try {
returnTuple = ts.readAll(template);
} catch (TupleSpaceException e) {
e.printStackTrace();
}
int num = returnTuple.length;
if (num == 0)
System.out.print("No tuples in the space");
else
for(int i=0;i<num;i++)
{
System.out.print("\nTotal tuples are" + num+"\nYou found " + returnTuple[i]);
showDialog(returnTuple[i].getField(0).toString(),returnTuple[i].getField(1).toString());
}
}
private void showDialog(String title, String message)
{
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton("OK", null);
builder.show();
}
}
Just do like this
private Handler handler = new MyHandler(this);
private static class MyHandler extends Handler {
MainActivity activity;
public MyHandler(MainActivity activity) {
this.activity = activity;
}
#Override
public void handleMessage(Message msg) {
...
activity.showTuples();
...
}
}
To use your Activity inside static class (inside MyHandler), you must pass it like argument into the constructor.
EDIT: added constructor with MainActivity argument.