I have an Android app running a thread. I want a Toast message to show with a message.
When I do this, I get the below exception:
Logcat trace:
FATAL EXCEPTION: Timer-0
java.lang.RuntimeException: Can't create handler inside thread that has not
called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at android.widget.Toast$TN.<init>(Toast.java:322)
at android.widget.Toast.<init>(Toast.java:91)
at android.widget.Toast.makeText(Toast.java:238)
Is there a work around for pushing Toast messages from threads to the User Interface?
This question is related to
android
multithreading
toast
I got this error in a JobService
from the following code:
BluetoothLeScanner bluetoothLeScanner = getBluetoothLeScanner();
if (BluetoothAdapter.STATE_ON == getBluetoothAdapter().getState() && null != bluetoothLeScanner) {
// ...
} else {
Logger.debug(TAG, "BluetoothAdapter isn't on so will attempting to turn on and will retry starting scanning in a few seconds");
getBluetoothAdapter().enable();
(new Handler()).postDelayed(new Runnable() {
@Override
public void run() {
startScanningBluetooth();
}
}, 5000);
}
The service crashed:
2019-11-21 11:49:45.550 729-763/? D/BluetoothManagerService: MESSAGE_ENABLE(0): mBluetooth = null
--------- beginning of crash
2019-11-21 11:49:45.556 8629-8856/com.locuslabs.android.sdk E/AndroidRuntime: FATAL EXCEPTION: Timer-1
Process: com.locuslabs.android.sdk, PID: 8629
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:203)
at android.os.Handler.<init>(Handler.java:117)
at com.locuslabs.sdk.ibeacon.BeaconScannerJobService.startScanningBluetoothAndBroadcastAnyBeaconsFoundAndUpdatePersistentNotification(BeaconScannerJobService.java:120)
at com.locuslabs.sdk.ibeacon.BeaconScannerJobService.access$500(BeaconScannerJobService.java:36)
at com.locuslabs.sdk.ibeacon.BeaconScannerJobService$2$1.run(BeaconScannerJobService.java:96)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
So I changed from Handler
to Timer
as follows:
(new Timer()).schedule(new TimerTask() {
@Override
public void run() {
startScanningBluetooth();
}
}, 5000);
Now the code doesn't throw the RuntimeException
anymore.
Android basically works on two thread types namely UI thread and background thread. According to android documentation -
Do not access the Android UI toolkit from outside the UI thread to fix this problem, Android offers several ways to access the UI thread from other threads. Here is a list of methods that can help:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Now there are various methods to solve this problem. I will explain it by code sample
runOnUiThread
new Thread()
{
public void run()
{
myactivity.this.runOnUiThread(new runnable()
{
public void run()
{
//Do your UI operations like dialog opening or Toast here
}
});
}
}.start();
LOOPER
Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
AsyncTask
AsyncTask allows you to perform asynchronous work on your user interface. It performs the blocking operations in a worker thread and then publishes the results on the UI thread, without requiring you to handle threads and/or handlers yourself.
public void onClick(View v) {
new CustomTask().execute((Void[])null);
}
private class CustomTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... param) {
//Do some work
return null;
}
protected void onPostExecute(Void param) {
//Print Toast or open dialog
}
}
Handler
A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue.
Message msg = new Message();
new Thread()
{
public void run()
{
msg.arg1=1;
handler.sendMessage(msg);
}
}.start();
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if(msg.arg1==1)
{
//Print Toast or open dialog
}
return false;
}
});
You can simply use BeginInvokeOnMainThread(). It invokes an Action on the device main (UI) thread.
Device.BeginInvokeOnMainThread(() => { displayToast("text to display"); });
It is simple and works perfectly for me!
EDIT : Works if you're using C# Xamarin
From http://developer.android.com/guide/components/processes-and-threads.html :
Additionally, the Android UI toolkit is not thread-safe. So, you must not manipulate your UI from a worker thread—you must do all manipulation to your user interface from the UI thread. Thus, there are simply two rules to Android's single thread model:
- Do not block the UI thread
- Do not access the Android UI toolkit from outside the UI thread
You have to detect idleness in a worker thread and show a toast in the main thread.
Please post some code, if you want a more detailed answer.
After code publication :
In strings.xml
<string name="idleness_toast">"You are getting late do it fast"</string>
In YourWorkerThread.java
Toast.makeText(getApplicationContext(), getString(R.string.idleness_toast),
Toast.LENGTH_LONG).show();
Don't use AlertDialog, make a choice. AlertDialog and Toast are two different things.
Here's what I've been doing:
public void displayError(final String errorText) {
Runnable doDisplayError = new Runnable() {
public void run() {
Toast.makeText(getApplicationContext(), errorText, Toast.LENGTH_LONG).show();
}
};
messageHandler.post(doDisplayError);
}
That should allow the method to be called from either thread.
Where messageHandler is declared in the activity as ..
Handler messageHandler = new Handler();
I got this exception because I was trying to make a Toast popup from a background thread.
Toast needs an Activity to push to the user interface and threads don't have that.
So one workaround is to give the thread a link to the parent Activity and Toast to that.
Put this code in the thread where you want to send a Toast message:
parent.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(parent.getBaseContext(), "Hello", Toast.LENGTH_LONG).show();
}
});
Keep a link to the parent Activity in the background thread that created this thread. Use parent variable in your thread class:
private static YourActivity parent;
When you create the thread, pass the parent Activity as a parameter through the constructor like this:
public YourBackgroundThread(YourActivity parent) {
this.parent = parent;
}
Now the background thread can push Toast messages to the screen.
runOnUiThread(new Runnable(){
public void run() {
Toast.makeText(getApplicationContext(), "Status = " + message.getBody() , Toast.LENGTH_LONG).show();
}
});
this works for me
Source: Stackoverflow.com