例外: 不能在线程内部创建未调用 Looper.ready()的处理程序;

我有一个 Android 应用程序运行一个线程。我想要一个吐司消息显示一个消息。

当我这样做的时候,我得到了下面的例外:

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)

是否有一种将 Toast 消息从线程推送到用户界面的方法?

144080 次浏览

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:

  1. Do not block the UI thread
  2. 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.

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.

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();

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

runOnUiThread(new Runnable(){
public void run() {
Toast.makeText(getApplicationContext(), "Status = " + message.getBody() , Toast.LENGTH_LONG).show();
}
});

this works for me

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.