English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Механизм сообщений Android
1. Обобщение
При запуске Android-приложения автоматически создается основной поток (UI-поток), в котором ассоциируется сообщение очереди (MessageQueue), все операции封装ируются в очередь сообщений и передаются на обработку основному потоку. Для предотвращения выхода основного потока используется операция очереди сообщений в цикле, что означает, что программа постоянно выполняет циклическую операцию, на каждом шаге цикла извлекается сообщение из внутреннего сообщения очереди, вызывается функция обработки сообщения (handlerMessage), после выполнения сообщения продолжается цикл. Если очередь сообщений пуста, поток будет блокироваться и ожидать. Таким образом, поток не выходит. Как показано на следующем рисунке:
Как связаны Handler, Looper и Message?
В подпотоке выполняются долгие операции, и часто необходимо обновить UI, наиболее часто это делается через Handler, который отправляет сообщение в поток UI, а затем обрабатывает его в методе handlerMessage Handler. Каждый Handler связан с очередью сообщений (MessageQueue), и Looper отвечает за создание MessageQueue, а каждый Looper связан с потоком (Looper упакован в ThreadLocal). По умолчанию, у MessageQueue есть только один, это очередь сообщений главного потока.
Вот основные принципы механизма сообщений Android. Если вы хотите узнать больше, начнем с исходного кода.
2. Разбор исходного кода
(1) Запуск цикла сообщений Looper в основном потоке ActivityThread
public final class ActivityThread { public static void main(String[] args) { //Код пропущен //1. Создание Looper для цикла сообщений Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); //2. Выполнение цикла сообщений Looper.loop(); throw new RuntimeException("Loop of main thread unexpectedly exited"); } }
ActivityThread создает очередь сообщений главного потока с помощью Looper.prepareMainLooper(), и в конце выполняет Looper.loop() для запуска очереди сообщений. Handler связывает очередь сообщений и поток
(2) Handler связывает очередь сообщений и поток
public Handler(Callback callback, boolean async) { //Код пропущен //Получение Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"; } //Получение очереди сообщений mQueue = mLooper.mQueue; }
Handler внутри использует метод Looper.getLooper() для получения объекта Looper и его связи с ним, а также для получения очереди сообщений. Как же работает Looper.getLooper()?
public static @Nullable Looper myLooper() { return sThreadLocal.get(); } public static @NonNull MessageQueue myQueue() { return myLooper().mQueue; } public static void prepare() { prepare(true); } // Установить Looper для текущего потока private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } // Установить Looper UI потока public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
В классе Looper, метод myLooper(), через sThreadLocal.get() получает, в prepareMainLooper() вызывается метод prepare(), в этом методе создается объект Looper, и объект устанавливается в sThreadLocal(). Таким образом, очередь связывается с потоком. Через метод sThreadLocal.get() гарантируется, что различные потоки не могут доступа к сообщениям других очередей.
Почему Handler для обновления UI должен создаваться в основном потоке?
Потому что Handler должен быть связан с的消息 очередью основного потока, чтобы handlerMessage выполнялся в UI-потоке, в этот момент UI-поток безопасен.
(3) Цикл сообщений, обработка сообщений
Создание цикла сообщений осуществляется через метод Looper.loop(). Пример исходного кода следующий:
/** * Запустите очередь сообщений в этой строке. Убедитесь, что вы вызвали * {@link #quit()} для завершения цикла. */ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // 1. Получение очереди сообщений final MessageQueue queue = me.mQueue; // 2. Смертельный цикл, то есть цикл сообщений for (;;) { // 3. Получение сообщения, может блокироваться Message msg = queue.next(); // может блокироваться if (msg == null) { // Отсутствие сообщений означает, что очередь сообщений выходит из строя. return; } // 4. Обработка сообщения msg.target.dispatchMessage(msg); // Восстановление сообщения msg.recycleUnchecked(); } }
Из вышеуказанного кода можно看出, метод loop() по сути создает死循环, затем逐 за раз извлекает сообщения из очереди сообщений и в конце обрабатывает их. Для Looper: Создается объект Looper с помощью Looper.prepare() (объект Looper содержит очередь сообщений), который сохраняется в sThreadLocal, и затем выполняется цикл сообщений с помощью Looper.loop(), эти два шага обычно появляются вместе.
public final class Message implements Parcelable { // обработка target Handler target; // Runnable типа callback Runnable callback; // Следующее сообщение, очередь сообщений хранится в виде цепочки Message next; }
Из исходного кода可以看出, target является типом Handler. На самом деле это просто один виток, через Handler отправляется сообщение в очередь сообщений, а очередь сообщений снова распределяет сообщения для обработки Handler. В классе Handle:
//Функция обработки сообщений, переопределяется подклассом public void handleMessage(Message msg) { } private static void handleCallback(Message message) { message.callback.run(); } //Дistributes messages public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
Из вышеуказанного кода可以看出, dispatchMessage - это просто метод分发, если callback типа Runnable пуст, то вызывается handleMessage для обработки сообщения, этот метод пуст, поэтому мы пишем код обновления UI в этой функции; если callback не пуст, то вызывается handleCallback для обработки, этот метод вызывает метод run callback. Это два типа分发 Handler, например, post(Runnable callback), где callback не пуст, когда мы используем Handler для sendMessage, обычно не устанавливается callback, поэтому вызывается handleMessage.
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } public String getMessageName(Message message) { if (message.callback != null) { return message.callback.getClass().getName(); } return "0x" + Integer.toHexString(message.what); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue\ Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
Из предыдущего кода можно видеть, что при post(Runnable r) Runnable 包装ается в объект Message, и объект Runnable устанавливается как callback для объекта Message, в конце концов, объект вставляется в очередь сообщений. Реализация sendMessage также аналогична:
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
Будь то post Runnable или Message, всегда вызывается метод sendMessageDelayed(msg, time). В конце концов, сообщение добавляется к MessageQueue, а Looper постоянно читает сообщения из MessageQueue и вызывает dispatchMessage Handler для分发 сообщений, таким образом, сообщения постоянно генерируются, добавляются к MessageQueue и обрабатываются Handler, и приложение Android работает.
3. Проверка
new Thread(){ Handler handler = null; public void run () { handler = new Handler(); ; .start();
Есть ли проблема в этом коде?
Объект Looper является ThreadLocal, то есть каждый поток использует свой Looper, который может быть пуст. Однако, когда в подпотоке создается объект Handler, если Looper пуст, то возникает исключение.
public Handler(Callback callback, boolean async) { //Код пропущен //Получение Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"; } //Получение очереди сообщений mQueue = mLooper.mQueue; }
Если mLooper пуст, то выбрасывается исключение. Это потому, что объект Looper не был создан, поэтому sThreadLocal.get() возвращает null. Основной принцип Handler заключается в том, чтобы устанавливать связь с MessageQueue и отправлять сообщения MessageQueue. Если нет MessageQueue, то Handler не имеет смысла существовать, а MessageQueue заперта в Looper. Поэтому при создании Handler Looper не может быть пуст. Решение проблемы таково:
new Thread(){ Handler handler = null; public void run () { //Создание Looper для текущей нити и绑定 к ThreadLocal Looper.prepare() handler = new Handler(); //Запуск цикла сообщений Looper.loop(); ; .start();
Если создать Looper, но не запустить цикл сообщений, то, хотя и не возникает исключений, постить или sendMessage() через handler также не будет эффективно. Потому что хотя сообщения будут добавлены в очередь сообщений, но цикл сообщений не был запущен, и поэтому сообщения не будут получены из очереди сообщений и выполнены.
Спасибо за чтение, надеюсь, это поможет вам, спасибо за поддержку нашего сайта!