Невозможно создать обработчик внутри потока, который не вызвал Looper.prepare ()


984

Что означает следующее исключение; как я могу это исправить?

Это код:

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)

8
проверьте эту библиотеку compile 'com.shamanland:xdroid-toaster:0.0.5', она не требует runOnUiThread()или Contextпеременной, вся рутина ушла! просто Toaster.toast(R.string.my_msg);приведу вот пример: github.com/shamanland/xdroid-toaster-example
Алексей К.

120
Что за глупое сообщение об ошибке! Это могло бы быть так же просто, как - нельзя вызывать это из потока, не являющегося пользовательским интерфейсом, как это было сделано при касании представлений из потока без пользовательского интерфейса.
Дхирадж Бхаскар

11
Для тех, кто получает одно и то же сообщение об исключении из другого кода: сообщение об исключении означает, что вы вызываете код через поток, который не подготовил Looper. Обычно это означает, что вы не звоните, если из потока пользовательского интерфейса, но вы должны (в случае OP) - нормальный поток не подготавливает Looper, но поток пользовательского интерфейса всегда делает.
Хелин Ван

@OleksiiKropachov реализация библиотеки, которую вы упомянули, очень похожа на выполнение runOnUiThread ().
Хелин Ван

да, но это очень полезная обертка
Алексей К.

Ответы:


697

Вы звоните из рабочего потока. Вы должны вызывать Toast.makeText()(и большинство других функций, связанных с пользовательским интерфейсом) из основного потока. Вы можете использовать обработчик, например.

Посмотрите на Общение с потоком пользовательского интерфейса в документации. В двух словах:

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

Другие опции:

Вы можете использовать AsyncTask , который хорошо работает для большинства вещей, работающих в фоновом режиме. У него есть хуки, которые вы можете вызвать, чтобы указать прогресс и когда это будет сделано.

Вы также можете использовать Activity.runOnUiThread () .


как насчет оригинальной проблемы (это не было о AlertDialog)?
Иван Григорьевич

5
Просто добавляю мои два цента к тому, что сказал Клегги. Было бы предпочтительнее дать краткую демонстрацию того, что вы имеете в виду (хотя и надуманный), поскольку закодированный пример часто говорит сам за себя.
cdata

5
полный технический ответ см. в этом prasanta-paul.blogspot.kr/2013/09/…
tony9099

3
Почти во всех языках программирования AFAIK, которые поддерживают GUI, если вы обновляете / изменяете / отображаете / взаимодействуете с GUI напрямую, это следует делать в основном потоке программы.
Ахмед

(and most other functions dealing with the UI)Пример функции пользовательского интерфейса, которую можно использовать из фона, android.support.design.widget.Snackbar- ее функциональность не уменьшается при отсутствии вызова из потока пользовательского интерфейса.
Скраффи

855

Вам нужно позвонить Toast.makeText(...)из потока пользовательского интерфейса:

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

Это скопировано из другого (дублирующего) SO ответа .


13
Отличный ответ. Это меня немного смутило. Просто чтобы заметить, мне не нужно было активности. перед запуском OnUiThread.
Cen92

449

ОБНОВЛЕНИЕ - 2016

Лучшей альтернативой является использование RxAndroid(конкретных привязок для RxJava) для Pв , MVPчтобы взять на себя ответственность Fo данных.

Начните с возврата 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 в основном работает с двумя типами потоков, а именно с пользовательским интерфейсом и фоновым потоком . Согласно документации Android -

Не обращайтесь к инструментарию пользовательского интерфейса Android извне потока пользовательского интерфейса, чтобы решить эту проблему, Android предлагает несколько способов доступа к потоку пользовательского интерфейса из других потоков. Вот список методов, которые могут помочь:

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

AsyncTask позволяет выполнять асинхронную работу на вашем пользовательском интерфейсе. Он выполняет операции блокировки в рабочем потоке, а затем публикует результаты в потоке пользовательского интерфейса, не требуя от вас обработки потоков и / или обработчиков самостоятельно.

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, связанные с 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;
    }
});

7
Это именно то , что я искал. Особенно первый пример сrunOnUiThread
Навин

5
Спасибо, 5 лет Android-программирования, и я никогда не знал, Viewтакже есть методы post(Runnable)и postDelayed(Runnable, long)! Так много хендлеров напрасно. :)
Fenix ​​Voltres

для тех, кто смущен примером обработчика: с каким потоком связан новый обработчик (обратный вызов)? Это связано с потоком, который создал обработчик.
Хелин Ван

1
Почему это лучшая альтернатива ?
Игорь Ганапольский

Я использую doInBackground и хочу вернуть ArrayList, но всегда получаю ошибку: не могу создать обработчик внутри потока, который не вызвал Looper.prepare (). Посмотрите, это мой вопрос stackoverflow.com/questions/45562615/… но я не могу получить решение из этого ответа здесь
WeSt

120

Toast.makeText()должен вызываться только из потока Main / UI. Looper.getMainLooper () поможет вам достичь этого:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Toast toast = Toast.makeText(mContext, "Something", Toast.LENGTH_SHORT);
    }
});

Преимущество этого метода в том, что вы можете использовать его без Activity или Context.


2
Спасибо, другие ответы не работали на меня. Я использую сахарную запись библиотеки для управления сохранением. И внутри меня нет активности. Но это прекрасно работает
cabaji99

91

Попробуйте это, когда увидите runtimeException из-за того, что Looper не подготовлен перед обработчиком.

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

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

Обработчик - это абстрактная классика. это не компилируется
Раввин Стелс

2
@StealthRabbi Импорт обработчика из правильного пространства имен, т.е.android.os.Handler
NightFury

Это не может быть проблемой. Петель может не существовать из вызывающего класса, точка.
Игорь Ганапольский

42

Я столкнулся с той же проблемой, и вот как я ее исправил:

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 methodили из AsyncTask в моей ситуации. Не могли бы вы опубликовать весь код, чтобы узнать, как все это работает?
Ник Кан

2
Разве эта последняя строка не должна читаться uiHandler = new UIHandler(uiThread.getLooper()); ?
Beer Me

36

Причина ошибки:

Рабочие потоки предназначены для выполнения фоновых задач, и вы не можете ничего показать в пользовательском интерфейсе в рабочем потоке, если вы не вызовете метод, подобный runOnUiThread . Если вы попытаетесь что-то показать в потоке пользовательского интерфейса без вызова runOnUiThread, появится сообщение java.lang.RuntimeException.

Итак, если вы находитесь в режиме activityвызова, но звоните Toast.makeText()из рабочего потока, сделайте следующее:

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

Приведенный выше код гарантирует, что вы отображаете сообщение Toast, UI threadпоскольку вы вызываете его внутри runOnUiThreadметода. Так больше не надо java.lang.RuntimeException.


23

это то, что я сделал.

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

Визуальные компоненты «заблокированы» для изменений извне потоков. Итак, так как тост показывает материал на главном экране, который управляется основным потоком, вам нужно запустить этот код в этом потоке. Надеюсь, это поможет:)


Я использовал этот же метод. Однако, оставляет ли это открытой возможность утечек, потому что анонимный внутренний класс Runnable будет содержать неявную ссылку на Activity?
Питер Дж. Уильямс

1
Это хорошо :) просто используйте getApplicationContext () или что-то в этом роде, чтобы быть в безопасности. хотя у меня никогда не было проблем с тем кодом, который я знаю
Эйран

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

}

Где вы используете тостер ? В вашем первом фрагменте он не используется ...
IgorGanapolsky

1
это был удобный класс, который я использовал как Toaster.INSTANCE.postMessage(ResourceUtils.getString(R.string.blah));(долго я знаю! мы сократили это позже), хотя я некоторое время не использовал тосты
EpicPandaForce

Так что же ApplicationHolder.INSTANCEоценивать?
Игорь Ганапольский

Статическая переменная CustomApplicationset in CustomApplication.onCreate(), учитывая, что приложение всегда существует, а процесс существует, этот контекст можно использовать глобально
EpicPandaForce

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

2
Это сработало для меня, и я использую лямбдуrunOnUiThread(() -> { Toast toast = Toast.makeText(getApplicationContext(), "Message", Toast.LENGTH_SHORT); toast.show(); });
Black_Zerg

Спасибо. Это сработало для меня
Амин

11

Замечательное решение Kotlin:

runOnUiThread {
    // Add your ui thread code here
}

4
runOnUiThreadявляется частью Деятельности, то естьactivity?.runOnUiThread { ... }
AtomicStrongForce

9

Это потому, что Toast.makeText () вызывается из рабочего потока. Это должен быть вызов из основного потока пользовательского интерфейса, как это

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

8

Ответ от ChicoBird работал для меня. Единственное изменение, которое я сделал, было в создании UIHandler, где я должен был сделать

HandlerThread uiThread = new HandlerThread("UIHandler");

Затмение отказалось принять что-либо еще. Имеет смысл, я полагаю.

Также uiHandlerявно определен глобальный класс. Я до сих пор не утверждаю, что понимаю, как Android это делает и что происходит, но я рад, что это работает. Теперь я приступлю к его изучению и выясню, могу ли я понять, что делает Android и почему нужно пройти через все эти обручи и петли. Спасибо за помощь ChicoBird.


6

первый звонок, Looper.prepare()а затем позвонить Toast.makeText().show()последний звонок, Looper.loop()как:

Looper.prepare() // to be able to make toast
Toast.makeText(context, "not connected", Toast.LENGTH_LONG).show()
Looper.loop()

Почему этот ответ недооценен?
DkPathak

5

Для пользователей Rxjava и RxAndroid:

public static void shortToast(String msg) {
    Observable.just(msg)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(message -> {
                Toast.makeText(App.getInstance(), message, Toast.LENGTH_SHORT).show();
            });
}

Эти скобки не нужны
Borja

4

Я столкнулся с той же проблемой, когда мои обратные вызовы пытались показать диалог.

Я решил это с помощью специальных методов в Activity - на уровне члена экземпляра Activity - которые используютrunOnUiThread(..)

public void showAuthProgressDialog() {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mAuthProgressDialog = DialogUtil.getVisibleProgressDialog(SignInActivity.this, "Loading ...");
        }
    });
}

public void dismissAuthProgressDialog() {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (mAuthProgressDialog == null || ! mAuthProgressDialog.isShowing()) {
                return;
            }
            mAuthProgressDialog.dismiss();
        }
    });
}

2
Handler handler2;  
HandlerThread handlerThread=new HandlerThread("second_thread");
handlerThread.start();
handler2=new Handler(handlerThread.getLooper());

Теперь handler2 будет использовать другой поток для обработки сообщений, отличный от основного потока.


1

Чтобы отобразить диалог или тостер в потоке, наиболее кратким способом является использование объекта Activity.

Например:

new Thread(new Runnable() {
    @Override
    public void run() {
        myActivity.runOnUiThread(new Runnable() {
            public void run() {
                myActivity.this.processingWaitDialog = new ProgressDialog(myActivity.this.getContext());
                myActivity.this.processingWaitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                myActivity.this.processingWaitDialog.setMessage("abc");
                myActivity.this.processingWaitDialog.setIndeterminate(true);
                myActivity.this.processingWaitDialog.show();
            }
        });
        expenseClassify.serverPost(
                new AsyncOperationCallback() {
                    public void operationCompleted(Object sender) {
                        myActivity.runOnUiThread(new Runnable() {
                            public void run() {
                                if (myActivity.this.processingWaitDialog != null 
                                        && myActivity.this.processingWaitDialog.isShowing()) {
                                    myActivity.this.processingWaitDialog.dismiss();
                                    myActivity.this.processingWaitDialog = null;
                                }
                            }
                        }); // .runOnUiThread(new Runnable()
...

0

Тост, AlertDialogs должна работать на UI потоке, вы можете использовать AsyncTask использовать их должным образом в Android development.but некоторых случаях нам нужно настроить тайм - аутов, поэтому мы используем темы , но в потоках мы не можем использовать Toast, Alertdialogs как мы используем в AsyncTask.So нам нужен отдельный обработчик для всплывающих тем.

public void onSigned() {
    Thread thread = new Thread(){
        @Override
        public void run() {
            try{
                sleep(3000);
                Message message = new Message();
                message.what = 2;
                handler.sendMessage(message);
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    };
    thread.start();
}

В приведенном выше примере я хочу спать мой поток в 3 секунды, и после того, как я хочу показать сообщение Toast, для этого в вашем обработчике реализации mainthread .

handler = new Handler() {
       public void handleMessage(Message msg) {
           switch(msg.what){
              case 1:
              Toast.makeText(getActivity(),"cool",Toast.LENGTH_SHORT).show();
              break;
           }
           super.handleMessage(msg);
       }
};

Я использовал здесь регистр коммутатора, потому что если вам нужно показать разные сообщения одинаково, вы можете использовать регистр коммутатора в классе Handler ... надеюсь, это поможет вам


0

Это обычно происходит, когда что-то в основном потоке вызывается из какого-либо фонового потока. Давайте посмотрим на пример, например.

private class MyTask extends AsyncTask<Void, Void, Void> {


@Override
protected Void doInBackground(Void... voids) {
        textView.setText("Any Text");
        return null;
    }
}

В приведенном выше примере мы устанавливаем текст в textview, который находится в основном потоке пользовательского интерфейса, из метода doInBackground (), который работает только в рабочем потоке.


0

У меня была та же проблема, и я исправил ее, просто поместив Toast в функцию переопределения onPostExecute () в Asynctask <>, и это сработало.


-2

я использую следующий код, чтобы показать сообщение из не основного потока "контекста",

@FunctionalInterface
public interface IShowMessage {
    Context getContext();

    default void showMessage(String message) {
        final Thread mThread = new Thread() {
            @Override
            public void run() {
                try {
                    Looper.prepare();
                    Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
                    Looper.loop();
                } catch (Exception error) {
                    error.printStackTrace();
                    Log.e("IShowMessage", error.getMessage());
                }
            }
        };
        mThread.start();
    }
}

затем используйте как следующее:

class myClass implements IShowMessage{

  showMessage("your message!");
 @Override
    public Context getContext() {
        return getApplicationContext();
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.