Làm thế nào để tạo một chuỗi Looper, sau đó gửi tin nhắn ngay lập tức?


76

Tôi có một chuỗi công nhân nằm ở chế độ nền, xử lý tin nhắn. Một cái gì đó như thế này:

class Worker extends Thread {

    public volatile Handler handler; // actually private, of course

    public void run() {
        Looper.prepare();
        mHandler = new Handler() { // the Handler hooks up to the current Thread
            public boolean handleMessage(Message msg) {
                // ...
            }
        };
        Looper.loop();
    }
}

Từ chuỗi chính (chuỗi giao diện người dùng, không quan trọng), tôi muốn làm điều gì đó như sau:

Worker worker = new Worker();
worker.start();
worker.handler.sendMessage(...);

Vấn đề là điều này đặt ra cho tôi một điều kiện chạy đua đẹp: tại thời điểm worker.handlerđược đọc, không có cách nào để chắc chắn rằng chuỗi công nhân đã được chỉ định cho trường này!

Tôi không thể chỉ đơn giản tạo ra Handlertừ hàm tạo của Worker', bởi vì hàm tạo chạy trên luồng chính, vì vậy Handlersẽ liên kết chính nó với luồng sai.

Đây dường như là một kịch bản không phổ biến. Tôi có thể nghĩ ra một số cách giải quyết, tất cả chúng đều xấu xí:

  1. Một cái gì đó như thế này:

    class Worker extends Thread {
    
        public volatile Handler handler; // actually private, of course
    
        public void run() {
            Looper.prepare();
            mHandler = new Handler() { // the Handler hooks up to the current Thread
                public boolean handleMessage(Message msg) {
                    // ...
                }
            };
            notifyAll(); // <- ADDED
            Looper.loop();
        }
    }
    

    Và từ chủ đề chính:

    Worker worker = new Worker();
    worker.start();
    worker.wait(); // <- ADDED
    worker.handler.sendMessage(...);
    

    Nhưng điều này cũng không đáng tin cậy: nếu điều này notifyAll()xảy ra trước wait(), thì chúng ta sẽ không bao giờ bị đánh thức!

  2. Chuyển một ký tự đầu tiên Messagecho hàm tạo Workercủa ', có run()phương thức đăng nó. Một giải pháp đặc biệt, sẽ không hoạt động cho nhiều thư hoặc nếu chúng tôi không muốn gửi ngay nhưng phải gửi ngay sau đó.

  3. Bận-chờ cho đến khi handlerruộng không còn null. Đúng, phương sách cuối cùng ...

Tôi muốn tạo HandlerMessageQueuethay mặt cho Workerchuỗi, nhưng điều này dường như không thể thực hiện được. Cách thanh lịch nhất trong số này là gì?


12
Bất kỳ lý do cụ thể nào bạn không sử dụng HandlerThread?
CommonsWare

2
@CommonsWare: Hmm, không biết rằng nó tồn tại. Không có tham chiếu chéo nào trong tài liệu. getLooper()Phương thức của nó chặn cho đến khi chúng ta có một Looper, sau đó chúng ta có thể sử dụng new Handler(worker.getLooper()) từ luồng chính để khởi tạo Handler. Điều đó sẽ giải quyết vấn đề, phải không?
Thomas

Tôi nghĩ vậy. OTOH, bản thân tôi không sử dụng nó nhiều, và vì vậy tôi có thể thiếu thứ gì đó.
CommonsWare

3
@CommonsWare: Nó đã giải quyết được vấn đề. Bây giờ nếu bạn muốn gửi đó như là một câu trả lời, tôi sẽ đặt một dấu kiểm màu xanh lá cây lớn bên cạnh nó;)
Thomas

9
Thực ra, tôi nghĩ sẽ tốt hơn nếu bạn tự trả lời nó, giải thích mức độ HandlerThreadphù hợp với Workerkhuôn mẫu của bạn . Ít nhất, bạn sẽ giải thích nó tốt hơn tôi có thể, vì đó là vấn đề của bạn và việc bạn triển khai giải pháp - Tôi chỉ chỉ ra một lớp trợ giúp để giải quyết vấn đề.
CommonsWare

Câu trả lời:


64

Giải pháp cuối cùng (trừ kiểm tra lỗi), nhờ CommonsWare:

class Worker extends HandlerThread {

    // ...

    public synchronized void waitUntilReady() {
        d_handler = new Handler(getLooper(), d_messageHandler);
    }

}

Và từ chủ đề chính:

Worker worker = new Worker();
worker.start();
worker.waitUntilReady(); // <- ADDED
worker.handler.sendMessage(...);

Điều này hoạt động nhờ vào ngữ nghĩa của HandlerThread.getLooper()khối nào cho đến khi khởi tạo vòng lặp.


Ngẫu nhiên, điều này tương tự với giải pháp số 1 của tôi ở trên, vì HandlerThreadnó được triển khai gần như như sau (phải thích mã nguồn mở):

public void run() {
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Looper.loop();
}

public Looper getLooper() {
    synchronized (this) {
        while (mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

Sự khác biệt chính là nó không kiểm tra xem luồng công nhân có đang chạy hay không, nhưng nó đã thực sự tạo một trình lặp; và cách để làm như vậy là lưu trữ trình lặp trong một trường riêng tư. Đẹp!


1
Cám ơn vì cái này. Tôi đang bình luận chỉ để chỉ ra rằng waitUntilReady () phải được gọi sau worker.start (). Nhìn lại nó có vẻ khá rõ ràng, nhưng tôi phải mất một chút để hiểu những gì tôi đã sai khi nhận được một ngoại lệ con trỏ null.
fedepaol

4
@Snicolas bạn không thể, vì trình xử lý được liên kết với luồng nơi nó được tạo. Khởi tạo nó trong phương thức khởi tạo của HandlerThread sẽ dẫn đến một trình xử lý trong luồng chính hoặc bất cứ nơi nào HandlerThread được tạo. Nếu bạn muốn sử dụng phương thức khởi tạo Handler với tham số của trình vòng lặp của Handler (cái được sử dụng trong waituntilready), tôi khá chắc chắn rằng nó sẽ dẫn đến bế tắc.
fedepaol

1
Ok, đó là một điểm tốt. Thx để đăng. Vì vậy, để bảo vệ trình xử lý khỏi bị lạm dụng, bạn có thể bọc các phương thức của trình xử lý vào HandlerThread có thể hoạt động như một mặt tiền và ủy quyền từng lệnh gọi cho trình xử lý, sau khi kiểm tra trình xử lý được khởi tạo. Đó sẽ là một cách tốt hơn để gói gọn nó và ngăn chặn việc chờ đợiUntilReady bị lãng quên.
Snicolas,

2
@Thomas mục đích chính của mục đích là d_handlergì? d_messageHandler xử lý các tin nhắn, phải không? Nhưng bạn đang gửi meesage sử dụngwork.handler
woyaru

1
Điều này rất thú vị, nhưng sẽ hữu ích hơn nếu bạn cung cấp một số ghi chú về d_handler và d_messageHandler là gì và cách chúng được sử dụng.
RenniePet

1

hãy xem mã nguồn của HandlerThread

@Override
     public void run() {
         mTid = Process.myTid();
         Looper.prepare();
         synchronized (this) {
             mLooper = Looper.myLooper();
             notifyAll();
         }
         Process.setThreadPriority(mPriority);
         onLooperPrepared();
         Looper.loop();
         mTid = -1;
     }

Về cơ bản, nếu bạn đang mở rộng Thread trong worker và triển khai Looper của riêng mình, thì lớp luồng chính của bạn sẽ mở rộng worker và đặt trình xử lý của bạn ở đó.


1

Đây là giải pháp của tôi: MainActivity:

//Other Code

 mCountDownLatch = new CountDownLatch(1);
        mainApp = this;
        WorkerThread workerThread = new WorkerThread(mCountDownLatch);
        workerThread.start();
        try {
            mCountDownLatch.await();
            Log.i("MsgToWorkerThread", "Worker Thread is up and running. We can send message to it now...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Toast.makeText(this, "Trial run...", Toast.LENGTH_LONG).show();
        Message msg = workerThread.workerThreadHandler.obtainMessage();
        workerThread.workerThreadHandler.sendMessage(msg);

Lớp WorkerThread:

public class WorkerThread extends Thread{

    public Handler workerThreadHandler;
    CountDownLatch mLatch;

    public WorkerThread(CountDownLatch latch){

        mLatch = latch;
    }


    public void run() {
        Looper.prepare();
        workerThreadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {

                Log.i("MsgToWorkerThread", "Message received from UI thread...");
                        MainActivity.getMainApp().runOnUiThread(new Runnable() {

                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.getMainApp().getApplicationContext(), "Message received in worker thread from UI thread", Toast.LENGTH_LONG).show();
                                //Log.i("MsgToWorkerThread", "Message received from UI thread...");
                            }
                        });

            }

        };
        Log.i("MsgToWorkerThread", "Worker thread ready...");
        mLatch.countDown();
        Looper.loop();
    }
}

0
    class WorkerThread extends Thread {
            private Exchanger<Void> mStartExchanger = new Exchanger<Void>();
            private Handler mHandler;
            public Handler getHandler() {
                    return mHandler;
            }
            @Override
            public void run() {
                    Looper.prepare();
                    mHandler = new Handler();
                    try {
                            mStartExchanger.exchange(null);
                    } catch (InterruptedException e) {
                            e.printStackTrace();
                    }
                    Looper.loop();
            }

            @Override
            public synchronized void start() {
                    super.start();
                    try {
                            mStartExchanger.exchange(null);
                    } catch (InterruptedException e) {
                            e.printStackTrace();
                    }
            }
    }
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.