Не вдається створити обробник всередині потоку, який не викликав Looper.prepare ()


981

Що означає наступний виняток; як я можу це виправити?

Це код:

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
Яке дурне повідомлення про помилку! Це могло бути таким же простим, як - не можна викликати це з потоку, що не є користувальницьким інтерфейсом, як це було зроблено, коли дотики торкаються потоку, що не використовується в інтерфейсі.
Dheeraj Bhaskar

11
Для тих, хто отримує одне і те ж повідомлення про виняток з іншого коду: Що означає повідомлення про виключення, це те, що ви викликаєте код за допомогою потоку, який не підготував Looper. Зазвичай це означає, що ви не телефонуєте, якщо з потоку інтерфейсу користувача, але вам слід (справа OP) - звичайний потік не готує Looper, але потік UI завжди робиться.
Хелін Ван

@OleksiiKropachov реалізація згаданої вами бібліотеки дуже схожа на виконання runOnUiThread ().
Хелін Ван

так, але це дуже корисна обгортка
Олексій К.

Відповіді:


696

Ви називаєте це з робочої нитки. Вам потрібно зателефонувати 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
Просто додав два мої центи до того, що сказав Клеггі. Бажано було б навести коротку демонстрацію того, що ви маєте на увазі (як би не було надумано), оскільки кодований приклад часто може говорити про себе.
кдата

5
для повної технічної відповіді дивіться це prasanta-paul.blogspot.kr/2013/09/…
tony9099

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

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

852

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

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

Це копіюється з іншої (копії) відповіді SO .


13
Чудова відповідь. Це мене на деякий час бентежило. Зауважу лише, що мені активність не потрібна. перед запускомOnUiThread.
Cen92

448

ОНОВЛЕННЯ - 2016

Кращою альтернативою є використання RxAndroid(конкретних прив'язок для RxJava) для Pв , MVPщоб взяти на себе відповідальність Fo даних.

Почніть з повернення Observableзі свого існуючого методу.

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

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

Використовуйте спостережуване так -

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 пропонує кілька способів доступу до потоку інтерфейсу з інших потоків. Ось перелік методів, які можуть допомогти:

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

ЛОПЕР

Клас, який використовується для запуску циклу повідомлень для потоку. Нитки за замовчуванням не мають циклу повідомлень, пов’язаних з ними; щоб створити його, зателефонуйте priprema () у потоці, який має запустити цикл, а потім цикл (), щоб він обробляв повідомлення до тих пір, поки цикл не припиняється.

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 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)! Стільки обробників даремно. :)
Фенікс Вольтрес

для тих, кого бентежить приклад Хендлера: до якої нитки пов'язується "новий обробник (зворотний виклик)"? Він пов'язаний з потоком, який створив обробник.
Хелін Ван

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

Перевагою цього методу є те, що ви можете використовувати його без діяльності чи контексту.


2
Дякую, що інші відповіді не працювали на мене. Я використовую бібліотечний запис цукру для управління стійкістю. А всередині цього я не маю активності. Але це чудово працює
cabaji99

91

Спробуйте це, коли ви побачите час виконання за допомогою Looper, не підготовленого до обробника.

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

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

Хендлер - це абстрактні класи. це не компілюється
Стелс-Раббі

2
@StealthRabbi import Handler з правильного простору імен, тобто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()); ?
Пиво мені

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 буде непрямим посиланням на Діяльність?
Пітер Г. Вільямс

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

}

Де ви використовуєте тостер ? У вашому першому фрагменті він не використовується ...
ІгорГанапольський

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

Отже, що ApplicationHolder.INSTANCEоцінює?
ІгорГанапольський

Статична змінна CustomApplicationбезлічі в CustomApplication.onCreate(), з огляду на застосування завжди існує в той час як процес існує, цей контекст може бути використаний у всьому світі
EpicPandaForce

11
 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(); });
лямбду


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

Ці дужки непотрібні
Борха

4

Я зіткнувся з тією ж проблемою, коли мої зворотні дзвінки намагалися показати діалог.

Я вирішив це спеціальними методами в діяльності - на рівні учасника екземпляра діяльності - які використовують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 потрібно запускати на потоці інтерфейсу користувача, ви можете використовувати Asynctask, щоб правильно їх використовувати в розробці андроїда. Але в деяких випадках нам потрібно налаштувати тайм-аути, тому ми використовуємо теми , але в потоках ми не можемо використовувати Toast, Alertdialogs, як ми використовуємо в AsyncTask.Так нам потрібен окремий обробник для спливаючих.

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.