Không thể tạo trình xử lý bên trong chuỗi chưa được gọi là Looper.prepare ()


981

Ngoại lệ sau có nghĩa là gì; Làm thế nào tôi có thể sửa chữa nó?

Đây là mã:

Toast toast = Toast.makeText(mContext, "Something", Toast.LENGTH_SHORT);

Đây là ngoại lệ:

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
kiểm tra thư viện này compile 'com.shamanland:xdroid-toaster:0.0.5', nó không yêu cầu runOnUiThread()hoặc Contextbiến, tất cả các thói quen đã biến mất! chỉ cần gọi Toaster.toast(R.string.my_msg);ở đây là ví dụ: github.com/shamanland/xdroid-toaster-example
Oleksii K.

120
Thật là một thông báo lỗi ngu ngốc! Nó có thể đơn giản như - không thể gọi điều này từ một luồng không phải UI như được thực hiện khi các khung nhìn được chạm từ một luồng không phải UI.
Dheeraj Bhaskar

11
Đối với những người nhận được cùng một thông báo ngoại lệ từ các mã khác nhau: Thông báo ngoại lệ có nghĩa là bạn đang gọi mã thông qua một chuỗi chưa chuẩn bị Looper. Thông thường, điều đó có nghĩa là bạn không gọi nếu từ luồng UI nhưng bạn nên (trường hợp của OP) - một luồng bình thường không chuẩn bị Looper, nhưng luồng UI luôn làm.
Helin Wang

@OleksiiKropachov việc triển khai thư viện mà bạn đề cập rất giống với việc thực hiện runOnUiThread ().
Helin Wang

vâng, nhưng nó là một trình bao bọc rất hữu ích
Oleksii K.

Câu trả lời:


696

Bạn đang gọi nó từ một chủ đề công nhân. Bạn cần gọi Toast.makeText()(và hầu hết các chức năng khác liên quan đến giao diện người dùng) từ trong luồng chính. Bạn có thể sử dụng một xử lý, ví dụ.

Tra cứu Giao tiếp với Giao diện người dùng trong tài liệu. Tóm lại:

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

Sự lựa chọn khác:

Bạn có thể sử dụng AsyncTask , hoạt động tốt cho hầu hết mọi thứ đang chạy trong nền. Nó có các móc mà bạn có thể gọi để chỉ ra tiến trình và khi hoàn thành.

Bạn cũng có thể sử dụng Activity.runOnUiThread () .


Còn vấn đề ban đầu (không phải là về AlertDialog) thì sao?
Ivan G.

5
Chỉ cần thêm hai xu của tôi vào những gì Cleggy nói. Sẽ là tốt hơn để cung cấp một minh chứng ngắn gọn về những gì bạn có ý nghĩa (tuy nhiên được đặt ra), vì một ví dụ được mã hóa thường có thể nói lên khối lượng cho chính nó.
cdata

5
để có câu trả lời kỹ thuật đầy đủ, hãy xem prasanta-paul.blogspot.kr/2013/09/
tony9099

3
Trong hầu hết tất cả các ngôn ngữ lập trình AFAIK hỗ trợ GUI, nếu bạn cập nhật / thay đổi / hiển thị / tương tác với GUI trực tiếp, thì nên thực hiện trên luồng chính của chương trình.
Ahmed

(and most other functions dealing with the UI)Một ví dụ về chức năng UI có thể sử dụng từ nền là android.support.design.widget.Snackbar- chức năng của nó không bị suy giảm khi không gọi từ luồng UI.
Scruffy

854

Bạn cần gọi Toast.makeText(...)từ luồng UI:

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

Đây là bản sao được dán từ một câu trả lời SO (trùng lặp) khác .


13
Câu trả lời chính xác. Điều này đã làm tôi bối rối trong một thời gian. Chỉ cần lưu ý, tôi không cần hoạt động. trước khi chạyOnUiThread.
Cen92

448

CẬP NHẬT - 2016

Giải pháp thay thế tốt nhất là sử dụng RxAndroid(các ràng buộc cụ thể cho RxJava) cho Ptrong MVPphụ trách để có fo dữ liệu.

Bắt đầu bằng cách trở về Observabletừ phương thức hiện tại của bạn.

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

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

Sử dụng cái này có thể quan sát như thế này -

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

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

Tôi biết tôi đến hơi muộn nhưng ở đây đi. Android về cơ bản hoạt động trên hai loại luồng là luồng UIluồng nền . Theo tài liệu android -

Không truy cập bộ công cụ UI Android từ bên ngoài luồng UI để khắc phục sự cố này, Android cung cấp một số cách để truy cập luồng UI từ các luồng khác. Dưới đây là danh sách các phương pháp có thể giúp:

Activity.runOnUiThread(Runnable)  
View.post(Runnable)  
View.postDelayed(Runnable, long)

Bây giờ có nhiều phương pháp khác nhau để giải quyết vấn đề này.

Tôi sẽ giải thích nó bằng mẫu mã:

chạyOnUiThread

new Thread()
{
    public void run()
    {
        myactivity.this.runOnUiThread(new Runnable()
        {
            public void run()
            {
                //Do your UI operations like dialog opening or Toast here
            }
        });
    }
}.start();

CUỘC SỐNG

Lớp được sử dụng để chạy một vòng lặp thông báo cho một chủ đề. Chủ đề theo mặc định không có một vòng lặp thông báo liên quan đến chúng; để tạo một cái, hãy gọi Chuẩn bị () trong luồng chạy vòng lặp và sau đó lặp () để xử lý thông báo cho đến khi vòng lặp dừng lại.

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 cho phép bạn thực hiện công việc không đồng bộ trên giao diện người dùng của bạn. Nó thực hiện các hoạt động chặn trong luồng công nhân và sau đó xuất bản kết quả trên luồng UI, mà không yêu cầu bạn tự xử lý các luồng và / hoặc xử lý.

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

Xử lý

Trình xử lý cho phép bạn gửi và xử lý các đối tượng Message và Runnable được liên kết với MessageQueue của một luồng.

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
Đây chính xác là những gì tôi đang tìm kiếm. Đặc biệt là ví dụ đầu tiên vớirunOnUiThread
Navin

5
Cảm ơn, 5 năm lập trình Android và tôi không bao giờ biết Viewcũng có phương pháp post(Runnable)postDelayed(Runnable, long)! Vì vậy, nhiều xử lý vô ích. :)
Fenix ​​Voltres

đối với những người bị nhầm lẫn bởi ví dụ của Handler: chủ đề mà "Handler mới (gọi lại)" được liên kết là gì? Nó bị ràng buộc với các chủ đề đã tạo ra xử lý.
Helin Wang

1
Tại sao đây là sự thay thế tốt nhất ?
IgorGanapolsky

Tôi đã sử dụng doInBackground và tôi muốn lấy lại ArrayList nhưng tôi luôn gặp lỗi: Không thể tạo trình xử lý bên trong luồng không được gọi là Looper.prepare (). Xem đây là câu hỏi stackoverflow.com/questions/45562615/ của tôi nhưng tôi không thể có được giải pháp từ câu trả lời này tại đây
WeSt

120

Toast.makeText()chỉ nên được gọi từ luồng Main / UI. Looper.getMainLooper () giúp bạn đạt được nó:

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

Một lợi thế của phương pháp này là bạn có thể sử dụng nó mà không cần Hoạt động hoặc Ngữ cảnh.


2
Cảm ơn các câu trả lời khác làm việc cho tôi. Tôi đang sử dụng một hồ sơ đường thư viện để quản lý sự kiên trì. Và bên trong nó tôi không có hoạt động. Nhưng điều này hoạt động tuyệt vời
cabaji99

91

Hãy thử điều này, khi bạn thấy runtimeException do Looper không được chuẩn bị trước khi xử lý.

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

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

Handler là một lớp trừu tượng. cái này không được biên dịch
Rabbi tàng hình

2
@StealthRabbi nhập Trình xử lý từ không gian tên chính xác, tức làandroid.os.Handler
NightFury 17/2/2015

Đây có thể không phải là vấn đề. Một looper có thể không tồn tại từ lớp gọi, giai đoạn.
IgorGanapolsky

42

Tôi gặp vấn đề tương tự, và đây là cách tôi khắc phục nó:

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

Để tạo UIHandler, bạn cần thực hiện các thao tác sau:

    HandlerThread uiThread = new HandlerThread("UIHandler");
    uiThread.start();
    uiHandler = new UIHandler((HandlerThread) uiThread.getLooper());

Hi vọng điêu nay co ich.


Tôi đã cố sử dụng mã của bạn nhưng tôi bị mất và không chắc cách gọi từ onCreate methodhoặc từ AsyncTask trong tình huống của tôi, bạn có vui lòng đăng toàn bộ mã chỉ để tìm hiểu cách mọi thứ hoạt động không?
Nick Kahn

2
Không nên đọc dòng cuối cùng uiHandler = new UIHandler(uiThread.getLooper()); ?
Bia Me

36

Lý do cho một lỗi:

Các luồng công nhân có nghĩa là để thực hiện các tác vụ nền và bạn không thể hiển thị bất cứ điều gì trên UI trong luồng công nhân trừ khi bạn gọi phương thức như runOnUiThread . Nếu bạn cố gắng hiển thị bất cứ điều gì trên luồng UI mà không gọi runOnUiThread, sẽ có một java.lang.RuntimeException.

Vì vậy, nếu bạn đang ở trong một activitycuộc gọi nhưng Toast.makeText()từ luồng công nhân, hãy làm điều này:

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

Đoạn mã trên đảm bảo rằng bạn đang hiển thị thông báo Toast UI threadvì bạn đang gọi nó trong runOnUiThreadphương thức. Vì vậy, không còn nữa java.lang.RuntimeException.


23

đó là những gì tôi đã làm

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

Các thành phần trực quan được "khóa" để thay đổi từ các chủ đề bên ngoài. Vì vậy, vì bánh mì nướng hiển thị nội dung trên màn hình chính được quản lý bởi luồng chính, bạn cần chạy mã này trên luồng đó. Mong rằng sẽ giúp :)


Tôi đã sử dụng phương pháp tương tự. Tuy nhiên, điều này có để ngỏ khả năng rò rỉ không, bởi vì lớp bên trong ẩn danh của Runnable sẽ giữ một tham chiếu ngầm cho Hoạt động?
Peter G. Williams

1
Đó là một điểm tốt :) chỉ cần sử dụng getApplicationContext () hoặc một cái gì đó tương tự, để ở bên an toàn. Tôi không bao giờ có bất kỳ vấn đề nào với mã mà tôi biết
eiran

22

Tôi đã nhận được lỗi này cho đến khi tôi làm như sau.

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

Và biến nó thành một lớp đơn:

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

}

Bạn đang sử dụng máy nướng bánh mì ở đâu? Trong đoạn trích đầu tiên của bạn, nó không được sử dụng ...
IgorGanapolsky

1
đó là một lớp tiện lợi mà tôi đã sử dụng như Toaster.INSTANCE.postMessage(ResourceUtils.getString(R.string.blah));(tôi biết rất lâu rồi! chúng tôi đã giảm cái này sau), mặc dù tôi đã không sử dụng bánh mì trong một thời gian
EpicPandaForce

Vậy ApplicationHolder.INSTANCEđánh giá để làm gì?
IgorGanapolsky

Một biến tĩnh của CustomApplicationthiết lập CustomApplication.onCreate(), xem xét ứng dụng luôn tồn tại trong khi quá trình tồn tại, bối cảnh này có thể được sử dụng trên toàn cầu
EpicPandaForce

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

2
Điều này làm việc cho tôi và tôi sử dụng lambdarunOnUiThread(() -> { Toast toast = Toast.makeText(getApplicationContext(), "Message", Toast.LENGTH_SHORT); toast.show(); });
Black_Zerg

Cảm ơn. Nó làm việc cho tôi
Amin

11

Giải pháp tuyệt vời của Kotlin:

runOnUiThread {
    // Add your ui thread code here
}

4
runOnUiThreadlà một phần của Hoạt động tức làactivity?.runOnUiThread { ... }
AtomicStrongForce

9

Điều này là do Toast.makeText () đang gọi từ một luồng công nhân. Nó sẽ được gọi từ luồng UI chính như thế này

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

8

Câu trả lời của ChicoBird đã làm việc cho tôi. Sự thay đổi duy nhất tôi đã thực hiện là trong việc tạo ra UIHandler nơi tôi phải làm

HandlerThread uiThread = new HandlerThread("UIHandler");

Eclipse từ chối chấp nhận bất cứ điều gì khác. Làm cho ý nghĩa tôi cho rằng.

Ngoài ra uiHandlerrõ ràng là một lớp toàn cầu được xác định ở đâu đó. Tôi vẫn không khẳng định để hiểu Android đang làm điều này như thế nào và những gì đang diễn ra nhưng tôi rất vui vì nó hoạt động. Bây giờ tôi sẽ tiến hành nghiên cứu nó và xem liệu tôi có thể hiểu Android đang làm gì không và tại sao người ta phải trải qua tất cả các vòng và vòng lặp này. Cảm ơn sự giúp đỡ của ChicoBird.


6

cuộc gọi đầu tiên Looper.prepare()và sau đó gọi cuộc gọi Toast.makeText().show()cuối cùng Looper.loop()như:

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

Tại sao câu trả lời này bị đánh giá thấp?
DkPathak

5

Đối với người dùng Rxjava và RxAndroid:

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

Những dấu ngoặc đó là không cần thiết
Borja

4

Tôi đã gặp vấn đề tương tự khi các cuộc gọi lại của tôi sẽ cố gắng hiển thị một hộp thoại.

Tôi đã giải quyết nó bằng các phương thức chuyên dụng trong Hoạt động - ở cấp thành viên đối tượng Hoạt động - sử dụngrunOnUiThread(..)

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

Bây giờ handler2 sẽ sử dụng một Thread khác để xử lý các thông báo so với Thread chính.


1

Để hiển thị hộp thoại hoặc máy nướng bánh mì trong một luồng, cách ngắn gọn nhất là sử dụng đối tượng Activity.

Ví dụ:

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

Toast, AlertDialogs cần chạy trên luồng UI, bạn có thể sử dụng Asynctask để sử dụng chúng đúng cách trong phát triển Android. Nhưng một số trường hợp chúng tôi cần tùy chỉnh thời gian chờ, vì vậy chúng tôi sử dụng Chủ đề , nhưng trong các luồng chúng tôi không thể sử dụng Toast, Alertdialog trong AsyncTask. Vì vậy, chúng tôi cần Handler riêng để bật lên.

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

trong Ví dụ ở trên tôi muốn ngủ chủ đề của tôi trong 3 giây và sau khi tôi muốn thể hiện một thông điệp Toast, cho rằng trong bạn mainthread thực hiện xử lý.

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

Tôi đã sử dụng trường hợp chuyển đổi ở đây, bởi vì nếu bạn cần hiển thị thông báo khác nhau theo cùng một cách, bạn có thể sử dụng trường hợp chuyển đổi trong lớp Handler ... hy vọng điều này sẽ giúp bạn


0

Điều này thường xảy ra khi một cái gì đó trên luồng chính được gọi từ bất kỳ luồng nền nào. Hãy xem xét một ví dụ, ví dụ.

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


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

Trong ví dụ trên, chúng tôi đang thiết lập văn bản trên textview nằm trong luồng UI chính từ phương thức doInBackground (), chỉ hoạt động trên một luồng công nhân.


0

Tôi gặp vấn đề tương tự và tôi đã khắc phục nó đơn giản bằng cách đặt Toast trong hàm ghi đè onPostExecute () của Asynctask <> và nó đã hoạt động.


-2

tôi sử dụng đoạn mã sau để hiển thị thông báo từ "bối cảnh" chủ đề không chính,

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

sau đó sử dụng như sau:

class myClass implements IShowMessage{

  showMessage("your message!");
 @Override
    public Context getContext() {
        return getApplicationContext();
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.