无法在未调用Looper.prepare()的线程中创建处理程序

1211
以下异常的意思是什么?我该如何修复它?
这是代码:
Toast toast = Toast.makeText(mContext, "Something", Toast.LENGTH_SHORT);

这是异常情况:

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.<init>(Toast.java:68)
     at android.widget.Toast.makeText(Toast.java:231)

13
请检查这个库 compile 'com.shamanland:xdroid-toaster:0.0.5',它不需要 runOnUiThread()Context 变量,所有例行程序都已消失!只需调用 Toaster.toast(R.string.my_msg); 就可以了。这里有一个示例:https://github.com/shamanland/xdroid-toaster-example - Oleksii K.
181
多么愚蠢的错误消息!它本可以简单明了,比如"不能在非UI线程中调用此函数,就像当视图从非UI线程被触摸时一样。" - Dheeraj Bhaskar
19
对于那些在不同代码中遇到相同异常消息的人:该异常消息的意思是您正在通过未准备好Looper的线程调用代码。通常这意味着您没有从UI线程调用它,但是您应该这样做(在OP的情况下)-普通线程不会准备Looper,但UI线程总是会准备。 - Helin Wang
1
@OleksiiKropachov,你提到的库的实现方式与执行runOnUiThread()非常相似。 - Helin Wang
1
是的,但它是一个非常有用的封装。 - Oleksii K.
@DheerajBhaskar 我不会轻易评判一个专业开发者的决定,当你不了解这个异常还包含其他情况时,这样做是愚蠢的。对我来说,我遇到了与UI线程无关的相同异常,谷歌自己提供的用于应用内购买的IABHandler,在没有调用Looper.getMainLooper()的情况下使用了新的Handler,出于一些“愚蠢”的原因;当我正常使用它时没有任何问题,但当我在Executors线程池中使用它时,就会出现相同的异常,从未在其中使用任何与UI相关的方法。 - Alireza Jamali
30个回答

983

您需要从UI线程调用 Toast.makeText(...)

activity.runOnUiThread(new Runnable() {
  public void run() {
    Toast.makeText(activity, "Hello", Toast.LENGTH_SHORT).show();
  }
});

这段内容是从另一个(重复的)SO答案复制粘贴过来的。


18
好的回答。这让我困惑了一段时间。只需注意,我在调用runOnUiThread之前不需要这个活动。 - Cen92
1
@Cen92 实际上你需要 >_<。runOnUiThread 是一个 Activity 的方法。 - Eugene Troyanskii
它可以在没有“activity”的情况下工作,在toast中我使用了MainActivity.this。">_<"是什么意思? - Sriram Nadiminti

837

你正在从一个工作线程中调用它。你需要在主线程中调用Toast.makeText()(以及大多数其他处理UI的函数)。例如,你可以使用Handler。

在文档中查找与UI线程通信。简而言之:

// Set this up in the UI thread.

mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message message) {
        // This is where you do your work in the UI thread.
        // Your worker tells you in the message what to do.
    }
};

void workerThread() {
    // And this is how you call it from the worker thread:
    Message message = mHandler.obtainMessage(command, parameter);
    message.sendToTarget();
}

其他选项:

如果您有一个Activity,您可以使用Activity.runOnUiThread()。十分简单直接:

@WorkerThread
void workerThread() {
    myActivity.runOnUiThread(() -> {
        // This is where your UI code goes.
    }
}

如果您只有一个Context,那么您也可以发布到主循环程序中。这样做非常有效。

@WorkerThread
void workerThread() {
    ContextCompat.getMainExecutor(context).execute(()  -> {
        // This is where your UI code goes.
    }
}

已废弃:

您可以使用AsyncTask来运行大多数后台操作,它具有可调用的钩子函数以指示进度和完成状态。

虽然方便,但如果使用不当可能会泄漏上下文。它已被正式废弃,不应再使用。


原问题怎么样了(它与AlertDialog无关)? - Ivan G.
5
我来补充一些Cleggy说的话。最好提供一个简短的演示来说明你的意思(即使有点勉强),因为一个编码示例常常可以自我说明许多。 - cdata
5
要获得完整的技术答案,请查看此链接:http://prasanta-paul.blogspot.kr/2013/09/android-looper-and-toast-from.html。 - tony9099
3
据我所知,几乎所有支持GUI的编程语言,如果您直接更新/更改/显示/交互GUI,则应在程序的主线程上完成。 - Ahmed
(以及大多数其他处理UI的函数)可从后台使用的UI函数示例是android.support.design.widget.Snackbar - 当不从UI线程调用时,其功能不会减弱。 - Scruffy
显示剩余3条评论

461

更新-2016

最佳替代方案是使用RxAndroid(专门为RxJava提供的绑定)来使用 MVP 中的P 来负责数据处理。

从您现有的方法开始返回 Observable

private Observable<PojoObject> getObservableItems() {
    return Observable.create(subscriber -> {

        for (PojoObject pojoObject: pojoObjects) {
            subscriber.onNext(pojoObject);
        }
        subscriber.onCompleted();
    });
}

像这样使用此Observable -

getObservableItems().
subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread()).
subscribe(new Observer<PojoObject> () {
    @Override
    public void onCompleted() {
        // Print Toast on completion
    }

    @Override
    public void onError(Throwable e) {}

    @Override
    public void onNext(PojoObject pojoObject) {
        // Show Progress
    }
});
}

----------------------------------------------------------------------------------------------------------------------------------

我知道我来晚了,但是这里有解释。 Android基本上有两种线程类型,即UI线程后台线程。根据安卓文档 -

不要从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

该类用于为线程运行消息循环。默认情况下,线程没有与之关联的消息循环;要创建一个消息循环,需要在要运行循环的线程中调用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
    }
}

处理器

一个处理器允许您发送和处理与线程的消息队列相关联的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;
    }
});

8
这正是我所寻找的。尤其是第一个例子中的 runOnUiThread - Navin
6
谢谢!我做了5年的安卓编程,从未知道 View 还有 post(Runnable)postDelayed(Runnable, long) 方法!之前用那么多 Handler 都白费了 :) - Fenix Voltres
1
为什么这是最佳选择 - IgorGanapolsky
我使用doInBackground,想要返回一个ArrayList,但总是出现错误:无法在未调用Looper.prepare()的线程中创建处理程序。这是我的问题 https://stackoverflow.com/questions/45562615/looper-prepare-error-from-asyncthread-doinbackground?noredirect=1#comment78084303_45562615 但我无法从这个答案中得到解决方案。 - WeSt
多年以后,这就是我一直在寻找的。谢谢你。observeOn(AndroidSchedulers.mainThread())。 - Zeek Aran
显示剩余4条评论

231

Toast.makeText() 只能从主/UI线程调用。 Looper.getMainLooper() 可以帮助你实现这一点:

JAVA

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        // write your code here
    }
});

KOTLIN

Handler(Looper.getMainLooper()).post {
        // write your code here
}

这种方法的优点是您可以在没有Activity或Context的情况下运行UI代码。


3
谢谢其他答案,但它们对我没有用。我正在使用一个名为Sugar Record的库来管理持久性。在其中,我没有活动(activity)对象。但这个方法非常有效。 - cabaji99
@dcarl661,感谢您的注意。已经进行了更正。 - Ayaz Alifov
1
@AyazAlifov你说“它不需要上下文”,那么mContext是指什么? - akhil nair
1
嗨@akhilnair。在运行Android代码时,不需要上下文来执行Main/UI线程。Main/UI线程可以包含任何代码。在这个特定的例子中,有一个Toast方法,它需要通过实现来获取上下文。 - Ayaz Alifov
最小而强大的答案。感谢。 - A.I.Shakil

99

当您看到由于处理程序之前未准备好Looper而导致的RuntimeException时,请尝试这样做。

Handler handler = new Handler(Looper.getMainLooper()); 

handler.postDelayed(new Runnable() {
  @Override
  public void run() {
  // Run your task here
  }
}, 1000 );

处理程序是一个抽象类。这不能编译。 - Stealth Rabbi
2
@StealthRabbi 从正确的命名空间,即 android.os.Handler 导入 Handler。 - NightFury
这可能不是问题所在。循环器可能不存在于调用类中,就是这样。 - IgorGanapolsky

44
我遇到了同样的问题,以下是我解决问题的方法:
private final class UIHandler extends Handler
{
    public static final int DISPLAY_UI_TOAST = 0;
    public static final int DISPLAY_UI_DIALOG = 1;

    public UIHandler(Looper looper)
    {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg)
    {
        switch(msg.what)
        {
        case UIHandler.DISPLAY_UI_TOAST:
        {
            Context context = getApplicationContext();
            Toast t = Toast.makeText(context, (String)msg.obj, Toast.LENGTH_LONG);
            t.show();
        }
        case UIHandler.DISPLAY_UI_DIALOG:
            //TBD
        default:
            break;
        }
    }
}

protected void handleUIRequest(String message)
{
    Message msg = uiHandler.obtainMessage(UIHandler.DISPLAY_UI_TOAST);
    msg.obj = message;
    uiHandler.sendMessage(msg);
}

创建UIHandler需要进行以下步骤:
    HandlerThread uiThread = new HandlerThread("UIHandler");
    uiThread.start();
    uiHandler = new UIHandler((HandlerThread) uiThread.getLooper());

希望这有所帮助。

我尝试使用你的代码,但是我迷失了方向,不确定该如何从onCreate方法或AsyncTask中调用。在我的情况下,您能否发布整个代码以便学习其工作原理? - Nick Kahn
2
最后一行不应该是这样吗?uiHandler = new UIHandler(uiThread.getLooper()); - Beer Me

40

错误的原因:

工作线程用于执行后台任务,除非调用像runOnUiThread这样的方法,否则无法在工作线程中显示任何UI内容。如果您尝试在没有调用runOnUiThread的情况下在UI线程上显示任何内容,将会出现java.lang.RuntimeException

因此,如果您在一个activity中但是从工作线程调用Toast.makeText(),请按照以下步骤操作:

runOnUiThread(new Runnable() 
{
   public void run() 
   {
      Toast toast = Toast.makeText(getApplicationContext(), "Something", Toast.LENGTH_SHORT).show();    
   }
}); 

上述代码确保您在UI线程中显示Toast消息,因为您将其调用在runOnUiThread方法内。因此不会再出现java.lang.RuntimeException错误。


24

这就是我所做的。

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Toast(...);
    }
});

可视化组件被“锁定”以防止受到外部线程的更改影响。 因此,由于 Toast 显示的内容在主线程中管理并显示在主屏幕上,所以您需要在该线程上运行此代码。 希望这能帮到你 :)


1
我使用了同样的方法。但是,这是否会留下泄漏的可能性,因为Runnable的匿名内部类将持有对Activity的隐式引用? - Peter G. Williams
1
这是一个很好的观点 :) 只需使用getApplicationContext()或类似的东西,以确保安全。虽然我知道那段代码从未出现过任何问题。 - eiran

22

我做了以下操作后,这个错误就不再出现了。

public void somethingHappened(final Context context)
{
    Handler handler = new Handler(Looper.getMainLooper());
    handler.post(
        new Runnable()
        {
            @Override
            public void run()
            {
                Toast.makeText(context, "Something happened.", Toast.LENGTH_SHORT).show();
            }
        }
    );
}

并将其变为单例类:

public enum Toaster {
    INSTANCE;

    private final Handler handler = new Handler(Looper.getMainLooper());

    public void postMessage(final String message) {
        handler.post(
            new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(ApplicationHolder.INSTANCE.getCustomApplication(), message, Toast.LENGTH_SHORT)
                        .show();
                }
            }
        );
    }

}

你在哪里使用 Toaster?在你的第一个片段中它没有被使用... - IgorGanapolsky
1
这是一个方便的类,我像这样使用它:Toaster.INSTANCE.postMessage(ResourceUtils.getString(R.string.blah));(有点冗长!我们后来缩短了代码),虽然我已经有一段时间没有使用过 Toasts 了。 - EpicPandaForce
ApplicationHolder.INSTANCE 会被评估为什么? - IgorGanapolsky
CustomApplication.onCreate()中设置CustomApplication的静态变量,考虑到应用程序在进程存在时始终存在,因此可以全局使用该上下文。 - EpicPandaForce

15
 runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(mContext, "Message", Toast.LENGTH_SHORT).show();
            }
        });

2
这对我有用,我使用lambda runOnUiThread(() -> { Toast toast = Toast.makeText(getApplicationContext(), "Message", Toast.LENGTH_SHORT); toast.show(); }); - Black_Zerg

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