Khái niệm cơ bản về Android: chạy mã trong luồng UI


450

Trong quan điểm chạy mã trong luồng UI, có sự khác biệt nào giữa:

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

hoặc là

MainActivity.this.myView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}

Để làm rõ câu hỏi của tôi: Tôi cho rằng các mã đó được gọi từ một luồng dịch vụ, thường là người nghe. Tôi cũng cho rằng có một công việc nặng nhọc để hoàn thành chức năng doInBackground () của AsynkTask hoặc trong Tác vụ mới (...) được gọi trước hai đoạn đầu tiên. Dù sao, onPostExecute () của AsyncTask đang được đặt ở cuối hàng đợi sự kiện, phải không?
Luky

Câu trả lời:


288

Không ai trong số đó là chính xác như nhau, mặc dù tất cả chúng sẽ có cùng hiệu ứng ròng.

Sự khác biệt giữa cái đầu tiên và cái thứ hai là nếu bạn tình cờ ở trên luồng ứng dụng chính khi thực thi mã, thì cái đầu tiên ( runOnUiThread()) sẽ thực thi Runnablengay lập tức. Cái thứ hai ( post()) luôn đặt Runnableở cuối hàng đợi sự kiện, ngay cả khi bạn đã ở trên luồng ứng dụng chính.

Cái thứ ba, giả sử bạn tạo và thực hiện một thể hiện của BackgroundTask, sẽ lãng phí rất nhiều thời gian để lấy một luồng ra khỏi nhóm luồng, để thực thi một no-op mặc định doInBackground(), trước khi cuối cùng thực hiện một số tiền cho a post(). Đây là hiệu quả thấp nhất trong ba. Sử dụng AsyncTasknếu bạn thực sự có công việc phải làm trong một chủ đề nền, không chỉ để sử dụng onPostExecute().


27
Cũng lưu ý rằng dù sao AsyncTask.execute()bạn cũng yêu cầu bạn gọi từ luồng UI, điều này làm cho tùy chọn này trở nên vô dụng đối với trường hợp sử dụng mã chạy đơn giản trên luồng UI từ luồng nền trừ khi bạn di chuyển tất cả công việc nền của mình vào doInBackground()và sử dụng AsyncTaskđúng cách.
kabuko

@kabuko làm thế nào tôi có thể kiểm tra xem tôi có gọi AsyncTasktừ luồng UI không?
Neil Galiaskarov

@NeilGaliaskarov Điều này có vẻ như là một lựa chọn vững chắc: stackoverflow.com/a/7897562/1839500
Dick Lucas

1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
ban-

1
@NeilGaliaskarov cho các phiên bản lớn hơn hoặc bằng với M sử dụngLooper.getMainLooper().isCurrentThread
Balu Sangem

251

Tôi thích nhận xét từ HPP , nó có thể được sử dụng ở bất cứ đâu mà không cần bất kỳ tham số nào:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

2
Làm thế nào là hiệu quả này? Có giống như các tùy chọn khác?
EmmanuelMess

59

Có một cách thứ tư sử dụng Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

56
Bạn nên cẩn thận với điều này. Bởi vì nếu bạn tạo một trình xử lý trong một luồng không phải UI, bạn sẽ gửi tin nhắn đến Chủ đề không phải UI. Một trình xử lý mặc định gửi tin nhắn đến chủ đề nơi nó được tạo.
lujop 17/03/13

107
để thực thi trên luồng UI chính new Handler(Looper.getMainLooper()).post(r), đây là cách ưa thích khi Looper.getMainLooper()thực hiện một cuộc gọi tĩnh đến chính, trong khi postOnUiThread()phải có một thể hiện MainActivitytrong phạm vi.
HPP

1
@HPP Tôi không biết phương pháp này, sẽ là một cách tuyệt vời khi bạn không có Hoạt động cũng không có Chế độ xem. Hoạt động tuyệt vời! cảm ơn bạn rất nhiều rất nhiều
Sulfkain

@lujop Tương tự là trường hợp với phương thức gọi lại onPreExecute của AsyncTask.
Saletanth Karumanaghat

18

Câu trả lời của Pomber là chấp nhận được, tuy nhiên tôi không phải là một fan hâm mộ lớn của việc tạo ra các đối tượng mới nhiều lần. Các giải pháp tốt nhất luôn là những giải pháp cố gắng giảm thiểu bộ nhớ hog. Có, có bộ sưu tập rác tự động nhưng bảo tồn bộ nhớ trong thiết bị di động nằm trong giới hạn thực hành tốt nhất. Mã dưới đây cập nhật một TextView trong một dịch vụ.

TextViewUpdater textViewUpdater = new TextViewUpdater();
Handler textViewUpdaterHandler = new Handler(Looper.getMainLooper());
private class TextViewUpdater implements Runnable{
    private String txt;
    @Override
    public void run() {
        searchResultTextView.setText(txt);
    }
    public void setText(String txt){
        this.txt = txt;
    }

}

Nó có thể được sử dụng từ bất cứ nơi nào như thế này:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);

7
+1 để đề cập đến GC và tạo đối tượng. TUY NHIÊN, tôi không nhất thiết phải đồng ý với The best solutions are always the ones that try to mitigate memory hog. Có nhiều tiêu chí khác cho best, và điều này có mùi nhẹ premature optimization. Đó là, trừ khi bạn biết bạn đang gọi nó đủ, số lượng đối tượng được tạo là một vấn đề (so với mười nghìn cách khác mà ứng dụng của bạn có thể tạo rác.), Điều có thể bestlà viết đơn giản nhất (dễ hiểu nhất ) mã, và chuyển sang một số nhiệm vụ khác.
ToolmakerSteve

2
BTW, textViewUpdaterHandlersẽ được đặt tên tốt hơn một cái gì đó như uiHandlerhoặc mainHandler, vì nó thường hữu ích cho bất kỳ bài đăng nào cho chủ đề UI chính; nó hoàn toàn không gắn với lớp TextViewUpdater của bạn. Tôi sẽ chuyển nó ra khỏi phần còn lại của mã đó và làm rõ rằng nó có thể được sử dụng ở nơi khác ... Phần còn lại của mã là không rõ ràng, vì để tránh tạo một đối tượng một cách linh hoạt, bạn hãy ngắt một cuộc gọi có thể thành một cuộc gọi hai bước setTextpost, dựa vào một đối tượng tồn tại lâu dài mà bạn sử dụng tạm thời. Sự phức tạp không cần thiết, và không an toàn chủ đề. Không dễ bảo trì.
ToolmakerSteve

Nếu bạn thực sự có một tình huống mà điều này được gọi rất nhiều lần thì nó đáng để lưu vào bộ đệm uiHandlertextViewUpdatersau đó cải thiện lớp của bạn bằng cách thay đổi public void setText(String txt, Handler uiHandler)và thêm dòng phương thức uiHandler.post(this); Sau đó, người gọi có thể thực hiện trong một bước : textViewUpdater.setText("Hello", uiHandler);. Sau đó, trong tương lai, nếu cần phải an toàn cho luồng, phương thức có thể bọc các câu lệnh của nó bên trong khóa uiHandlervà người gọi vẫn không thay đổi.
ToolmakerSteve

Tôi khá chắc chắn rằng bạn chỉ có thể chạy Runnable một lần. Mà hoàn toàn phá vỡ ý tưởng của bạn, đó là tốt đẹp trong tổng thể.
Nativ

@Nativ Không, Runnable có thể được chạy nhiều lần. Một chủ đề không thể.
Zbyszek

8

Kể từ Android P, bạn có thể sử dụng getMainExecutor():

getMainExecutor().execute(new Runnable() {
  @Override public void run() {
    // Code will run on the main thread
  }
});

Từ tài liệu dành cho nhà phát triển Android :

Trả về một Executor sẽ chạy các nhiệm vụ mê hoặc trên luồng chính liên quan đến bối cảnh này. Đây là luồng được sử dụng để gửi các cuộc gọi đến các thành phần ứng dụng (hoạt động, dịch vụ, v.v.).

Từ CommonsBlog :

Bạn có thể gọi getMainExecutor () trên Ngữ cảnh để nhận Executor sẽ thực thi các công việc của nó trên luồng ứng dụng chính. Có nhiều cách khác để thực hiện điều này, sử dụng Looper và triển khai Executor tùy chỉnh, nhưng cách này đơn giản hơn.


7

Nếu bạn cần sử dụng trong Fragment, bạn nên sử dụng

private Context context;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }


    ((MainActivity)context).runOnUiThread(new Runnable() {
        public void run() {
            Log.d("UI thread", "I am the UI thread");
        }
    });

thay vì

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

Bởi vì sẽ có ngoại lệ con trỏ null trong một số trường hợp như phân đoạn máy nhắn tin


1

chào các bạn, đây là câu hỏi cơ bản

sử dụng Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

Có bất kỳ lý do tại sao bạn chọn sử dụng Trình xử lý mục đích chung trên runOnUiThread không?
ThePartyTurtle

Điều này sẽ đưa ra java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()nếu nó không được gọi từ luồng UI.
Roc Boronat
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.