java.lang.RuntimeException: 不能在未调用Looper.prepare()的线程中创建handler。

95

我有一个运行线程的Android应用程序。我想要一个Toast消息显示一个信息。

当我这样做时,我会得到以下异常:

Logcat跟踪:

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消息的解决方案?

https://dev59.com/p2855IYBdhLWcg3wq2XL - Amar
而在重写的public void inactivitydetected()函数中,我使用了toast函数。 - Amar
7个回答

125
我因为在后台线程中尝试制作Toast弹出窗口而收到此异常。
Toast需要一个活动来推送到用户界面,而线程没有这个功能。
因此一个解决方法是将父活动的链接提供给线程并向其发送Toast消息。

将此代码放入您希望发送Toast消息的线程中:

parent.runOnUiThread(new Runnable() {
    public void run() {
        Toast.makeText(parent.getBaseContext(), "Hello", Toast.LENGTH_LONG).show();
    }
});

在创建此线程的后台线程中保留到父Activity的链接。 在您的线程类中使用parent变量:

private static YourActivity parent;

当您创建线程时,请通过构造函数将父活动作为参数传递,如下所示:

public YourBackgroundThread(YourActivity parent) {
    this.parent = parent;
}

现在后台线程可以向屏幕推送Toast消息。


2
我如何在这段代码中调用正在显示toast信息的Activity中的方法? - lxknvlk
请不要在评论中发布后续问题。评论是用来讨论顶部的问题以及我的答案是否回答了该问题的适当性。遵守规则非常重要。遵守规则是保持stackoverflow优秀的关键,也可以防止它变成像Yahoo Answers一样的马戏团。 - Eric Leschinski
1
对于 Xamarin Android,Activity.RunOnUiThread(() => Toast.MakeText(this.Activity.ApplicationContext, "Message Text", ToastLength.Short).Show()); - The Mitra Boy

33

Android基本上有两种线程类型,即UI线程和后台线程。根据Android文档 -

为了解决这个问题,请勿从UI线程外部访问Android UI工具包,Android提供了几种方法来从其他线程访问UI线程。以下是一些可以帮助的方法列表:

Activity.runOnUiThread(Runnable)  
View.post(Runnable)  
View.postDelayed(Runnable, long)
现在有多种方法来解决这个问题。我将通过代码示例来解释。
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

LOOPER类用于为线程运行消息循环。默认情况下,线程不与消息循环相关联;要创建一个消息循环,请在要运行循环的线程中调用prepare(),然后调用loop()以处理消息,直到停止循环。

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允许您在用户界面上执行异步工作。它在工作线程中执行阻塞操作,然后在UI线程上发布结果,无需自己处理线程和/或处理程序。

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)允许您发送和处理与线程的消息队列相关联的消息(Message)和可运行对象(Runnable)。

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

10

这是我一直在做的事情:

  public void displayError(final String errorText) {
    Runnable doDisplayError = new Runnable() {
        public void run() {
            Toast.makeText(getApplicationContext(), errorText, Toast.LENGTH_LONG).show();
        }
    };
    messageHandler.post(doDisplayError);
}

这样可以允许从任一线程调用该方法。

其中messageHandler在活动中声明为..

Handler messageHandler = new Handler();

6
http://developer.android.com/guide/components/processes-and-threads.html :

此外,Android的UI工具包不是线程安全的。因此,您不能从工作线程中操纵用户界面 - 您必须从UI线程中执行所有对用户界面的操作。因此,Android的单线程模型有两个简单的规则:

  1. 不要阻塞UI线程
  2. 不要从UI线程外访问Android UI工具包

您需要检测工作线程中的空闲并在主线程中显示toast。

如果您想获得更详细的答案,请发布一些代码。

代码发布后:

strings.xml

<string name="idleness_toast">"You are getting late do it fast"</string>

在你的工作线程类 YourWorkerThread.java
Toast.makeText(getApplicationContext(), getString(R.string.idleness_toast), 
    Toast.LENGTH_LONG).show();

不要使用AlertDialog,做出选择。AlertDialog和Toast是两个不同的东西。

idleTimer = new IdleTimer(5000, new IIdleCallback() { @Override public void inactivityDetected() { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder( getApplicationContext()); Toast.makeText(null, "你已经迟到了,请快点完成...", Toast.LENGTH_LONG) .show(); } }); if(idleTimer.checkIsTimerRunning()){ idleTimer.stopIdleTimer(); } idleTimer.startIdleTimer(); - Amar
我只希望在我的idleTimer线程中想要显示在我的主UI上的东西能够显示出来。 - Amar
抱歉,我搞糊涂了,代码大概是这样的。 - Amar
idleTimer = new IdleTimer(5000, new IIdleCallback() { @Override public void inactivityDetected() { Toast.makeText(getApplicationContext(), "你已经迟到了,请快点...", Toast.LENGTH_LONG) .show(); } }); if(idleTimer.checkIsTimerRunning()){ idleTimer.stopIdleTimer(); } idleTimer.startIdleTimer(); - Amar

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

这对我有帮助。

1
你可以简单地使用BeginInvokeOnMainThread()。它会在设备主(UI)线程上调用一个Action。
Device.BeginInvokeOnMainThread(() => { displayToast("text to display"); });

这很简单,对我来说完美无缺!

编辑:如果您正在使用C# Xamarin,则可以使用。


这不是Xamarin特定的API吗,与此关于Android Native(无Xamarin标签)的问题无关,对吧? - Splaktar
是的,你说得对。很抱歉,我没有注意到。 但是使用Xamarin寻找错误解决方案的人会着陆在这个页面上。所以我让帖子在这里,只是明确它是为Xamarin而设的。 - Vozek
“Device” 来自哪个程序集/命名空间? - MJ33
@MJ33 这是来自于 Xamarin.Forms。 - Hagbard

0

我在以下代码的JobService中遇到了这个错误:

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

服务崩溃了:

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)

所以我将代码从Handler改为Timer,如下所示:

   (new Timer()).schedule(new TimerTask() {
                @Override
                public void run() {
                    startScanningBluetooth();
                }
            }, 5000);

现在代码不再抛出RuntimeException异常了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接