Can some please suggest an alternative to using two different handlers in the one activity, or let me know that this method is OK?
Details:
I'm in the process of developing some code for use with Microchips ADK Android starter kit. Everything is running smoothly, however, I have an issue where there are two different handlers running in the code, it works but I have noticed that it is not recommended.
The first handler I use to create a time delay, and use the post command to launch the result. The second handler is used in Microchip's sample code that they supplied to communicate with their USB accessory framework files. I prefer to only tinker with the Microchip code rather than rewriting it.
Similar code:
public class MainActivity extends Activity
{ ...
final Handler mHandler = new Handler(); // handler for startTimeDelay
...
protected void onCreate(Bundle savedInstanceState)
{ super.onCreate(savedInstanceState);
...
Button btnSetTimeDelay = (Button) findViewById(R.id.btnChangeTimeDelay);
btnSetTimeDelay.setOnClickListener
( new View.OnClickListener()
{ public void onClick(View v)
{
setBackgroundColorLinearLayout(color.holo_red_dark); //red background
displayLockIsOpen(false); // display closed lock message
startTimeDelay(); // wait to open the lock
}
}
);
} // onCreate
...
final Runnable mUpdateResults = new Runnable()
{ public void run()
{ setBackgroundColorLinearLayout(color.holo_green_dark); //green background
displayLockIsOpen(true); // display open lock message
}
};
protected void startTimeDelay()
{ Thread t = new Thread()
{ #override
public void run()
{ SystemClock.sleep(global_delay);
mHandler.post(mUpdateResults); // run mUpdateResults code
};
};
t.start();
} // startTimeDelay
// USB accessory handler from Microchip, for ADK
private Handler handler = new Handler()
{ #override
public void handleMessage(Message msg)
{
...
switch(msg.what)
{ case USBAccessoryWhat:
...lots of code here from Microchip
} // switch msg.what
} // handleMessage
}; // Handler
} // MainActivity
You can create a single Handler itself both for startTimeDelay and USB accessory.
From the code of android.
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Its says when you have a callback(Runnable) it will call handleCallback which will call your Runnable.run Method, else handleMessage will be called.
So you can have a single handler.
Thanks Vivek for your answer. I've also found that I can remove the line:
final Handler mHandler = new Handler(); // handler for startTimeDelay
and rename
mHandler.post(mUpdateResults); // run mUpdateResults code
to
handler.post(mUpdateResults); // run mUpdateResults code
The fact that Microchip's code overrides handleMessage has no effect on the .post method and it works normally. Giving me the below code.
public class MainActivity extends Activity
{ ...
// final Handler mHandler = new Handler(); // handler for startTimeDelay, not needed
...
protected void onCreate(Bundle savedInstanceState)
{ super.onCreate(savedInstanceState);
...
Button btnSetTimeDelay = (Button) findViewById(R.id.btnChangeTimeDelay);
btnSetTimeDelay.setOnClickListener
( new View.OnClickListener()
{ public void onClick(View v)
{
setBackgroundColorLinearLayout(color.holo_red_dark); //red background
displayLockIsOpen(false); // display closed lock message
startTimeDelay(); // wait to close the lock
}
}
);
} // onCreate
...
final Runnable mUpdateResults = new Runnable()
{ public void run()
{ setBackgroundColorLinearLayout(color.holo_green_dark); //green background
displayLockIsOpen(true); // display open lock message
}
};
protected void startTimeDelay()
{ Thread t = new Thread()
{ #override
public void run()
{ SystemClock.sleep(global_delay);
handler.post(mUpdateResults); // run mUpdateResults code
// changed from mHandler to handler, defined below
};
};
t.start();
} // startTimeDelay
// USB accessory handler from Microchip, for ADK
private Handler handler = new Handler()
{ #override
public void handleMessage(Message msg)
{
...
switch(msg.what)
{ case USBAccessoryWhat:
...lots of code here from Microchip
} // switch msg.what
} // handleMessage
}; // Handler
} // MainActivity
For me this was simple as I didn't need to rewrite any code, but I'm not sure whether its better practise or not. Your code would atleast direct the code reader to one location for the handler whereas in my version the running of mUpdateResults is not explicitly visible in the handler routine.
Related
I'm trying to understand how Handler works in a pair with Looper, but i have some problem. I need to do some long operation in a back thread and then to send some result in a textView.
I get the following error after pressing a button:
Only the original thread that created a view hierarchy can touch its views.
public class MainActivity extends AppCompatActivity {
Button mButton;
TextView mTextView;
ConsumeThread mConsumeThread;
class ConsumeThread extends Thread{
public Handler mHandler;
#Override
public void run() {
Looper.prepare();
mHandler = new Handler(){
#Override
public void handleMessage(Message msg){
int arg = msg.what;
someLongOperation(arg);
}
};
Looper.loop();
}
private void someLongOperation(int arg){
// do some long operation
arg += 1000;
mTextView.setText("Operation's code is " +arg); // fatal exception
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.txt_view);
mButton = (Button) findViewById(R.id.button);
mConsumeThread = new ConsumeThread();
mConsumeThread.start();
mButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
if(mConsumeThread.mHandler != null){
Message msg = mConsumeThread.mHandler.obtainMessage(10);
mConsumeThread.mHandler.sendMessage(msg);
}
}
});
}
To get Main Thread Handler You have get Handler as follows .
Because:-
Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it
So you need to get Handler which is associated with MainThread. For that you can use one of the following:-
With Context
Handler mainHandler = new Handler(getMainLooper()){
#Override
public void handleMessage(Message msg) {
}
};
Or Directly with Looper even when do not have Context
Handler mainHandler = new Handler(Looper.getMainLooper()){
#Override
public void handleMessage(Message msg) {
}
};
I assume that you are doing some long running task . So its better if you go with AsyncTask.
private void someLongOperation(int arg){
// do some long operation
arg =+ 1000;
mTextView.setText("Operation's code is " +arg); // fatal exception
}
//see here, you are in worker thread, so you can't excess UI toolkit or else exception, so if you want to do something ui related task in worker thread, use runOnUi, see here
runOnUiThread(new Runnable() {
#Override
public void run() {
textview.setText("");
}
})
You can't update the UI from another thread. You have to move the code that updates the UI to the UIThread.
Try Using:
runOnUiThread(new Runnable() {
#Override
public void run() {
//TextView Update Code
}
});
Tip: Try to reduce the number of lines of code you put inside this, as then there would be no purpose of using another thread.
Only the original thread that created a view hierarchy can touch its views
You have to do ui related work on the main thread...
So you can do it like this...
private void someLongOperation(int arg){
// do some long operation
arg =+ 1000;
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mTextView.setText("Operation's code is " +arg); // fatal exception
}
});
}
Can I create onClickListener() for a button in more than 1 threads that are executing simultaneously?
Would that listener be called individually in every thread?
No, a button has only one onClickListener. Setting a second one overwrites any listener set previously. And that function will only be called on the UI thread. You can pass messages to multiple threads from that function though.
There are a lot of ways to communicate between threads. Since you are wondering how to pass something over to the thread, here is a simple example:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
...
//Start a thread you need to
anotherThread = new AnotherThread();
anotherThread.start();
}
protected void onResume() {
...
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
anotherThread.sendData(message);
}
});
}
}
public class AnotherThread extends Thread {
//Instantiate handler to associate it with the current thread.
//Handler enqueues all tasks in the MessageQueue using Looper
//and execute them upon coming out of queue;
private Handler handler;
#Override
public void run() {
try {
//Here we create a unique Looper for this thread.
//The main purpose of which to keep thread alive looping through
//MessageQueue and send task to corresponding handler.
Looper.prepare();
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
//manage incoming messages here
String value = msg.getData().getString("key");
}
};
Looper.loop();
} catch (Throwable e) {
e.printStackTrace();
}
}
public synchronized void sendData(Message message) {
handler.post(new Runnable() {
#Override
public void run() {
handler.sendMessage(message);
}
});
}
}
To get more about thread communication I'd recommend you the following:
1, 2, 3.
I am new to Android dev, and am trying to solve this problem that has been giving me some frustration. I am trying to close this progressDialog. When I run the app, it displays, the information is fetched, and the UI is updated. However, the dialog is never dismissed.
progDialog = ProgressDialog.show(HomeActivity.this, "", "Fetching info...", true, false);
new Thread(new Runnable() {
public void run() {
fetchInfomation(userID); //fetches information - Works
runOnUiThread(new Runnable() {
public void run() {
setLayoutList(); //updates UI - Works
progDialog.dismiss(); //doesn't seem to close progress dialog
firstView(); //displays prompt - Works
}
});
progDialog.dismiss(); //doesn't close dialog either
}
}).start();
Any ideas?
You can't interact with UI inside an external thread. There is some techniques to do that but it's not necessary.
You can use Handler.
For example:
...
private Handler mHandler;
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mHandler = new Handler() {
#Override
public void handleMessage (Message msg) {
switch (msg.what) {
case 0: ...
case 1: ...
}
}
}
}
And:
....
new Thread() {
#Override
public void run() {
... // do your background jobs here
mHandler.sendEmptyMessage(...); // interact with UI
});
}
}.start();
It will be good practice if you do any GUI updates on UI thread. Inside any thread you can run your UI thread where you can do the UI stuffs or else you can go for message handler also which will do the same for you.
Runnable run_in_ui = new Runnable() {
#Override
public void run() {
// do your UI stuffs here
}
};
runOnUiThread(run_in_ui);
So I'm using this code to show the message "Installing..." while the database is setup with the function 'setUpDB' and is then removed when the database function has completed. This works fine in Gingerbread and honeycomb, but causes the application to crash in ICS
final ProgressDialog pd=ProgressDialog.show(this,"","Installing...");
final Handler handler = new Handler()
{
public void handleMessage(Message msg)
{
if(msg.what==0)
{
pd.dismiss();
}
}
};
//have subcategory heading???
Thread thread = new Thread()
{
#Override
public void run() {
setUpDB();
handler.sendEmptyMessage(0);
}
};
thread.start();
Without dismissing the message, the app will continue to run in ICS (but you can't do anything), and without displaying the message if the user does anything that accesses the database before it is finished being setup it will crash (thus why I need the installing message)..
Okay, here is the code using AsyncTask
final ProgressDialog pd=ProgressDialog.show(this,"","Installing...");
final Handler handler = new Handler()
{
public void handleMessage(Message msg)
{
if(msg.what==0)
{
pd.dismiss();
}
}
};
new databaseInstallTask().execute(handler);
And
private class databaseInstallTask extends AsyncTask<Handler, Void, Handler>
{
#Override
protected Handler doInBackground(Handler... params) {
setUpDB();
return params[0];
}
protected void onPostExecute(Handler handler) {
handler.sendEmptyMessage(0);
}
}
Why don't you try using Asynctask which I suppose is the most appropriate way to handle your case.
I have the following:-
public class resApp extends MapActivity implements Runnable {
public void run() {
searchImage.setVisibility(View.GONE);
}
}
I also have a background thread that runs before this but that seems to run ok.
When i run the app the run() never gets called.
Can you help?
This code did work about 6 months ago but the device was 2.1.
Thanks
Chris
edit
I had already implemented
private Handler handler;
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (msg.toString().equalsIgnoreCase("1")) {
ad.dismiss();
} else {
pd.dismiss();
}
}
};
as an example and I already have an asynchronous task that runs in the back ground and in 2.1 I could have getters and setters in there. I have now had to pull these out and put them into the run() method as 2.2 doesn't like setting onclicklistener in an async task.
All I need to do is call the run() method on post execute but have tried everything:-
protected void onPostExecute(Object result) {
// Pass the result data back to the main activity
if (dialog != null) {
resApp.this.dialog.dismiss();
}
}
Could I just do:-
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
this.resApp.run();
}
};
You can call the run() method by using Handler.
Handler myHandler = new Handler();
resApp myObj;
And call it by using myHandler.post(myObj);