Всем привет, хочу поделиться методологией применения инструмента созданного облегчить разработку приложений (на сцену выходит Handler).
В кратце android.os.Handler это абстракция позволяющая "выполнять" в указаной очередности другие абстракции - события. А что же такое событие? На реализации событиями являются android.os.Message и обычные Runnable.
В кратце android.os.Handler это абстракция позволяющая "выполнять" в указаной очередности другие абстракции - события. А что же такое событие? На реализации событиями являются android.os.Message и обычные Runnable.
Немного по делу. Знакомо CalledFromWrongThreadException? Если да, то runOnUiThread не будет открытием, иначе все впереди:). Рано или поздно у каждого из нас возникает необходимость обновить views после получения данных которое чаще всего выполняется в другом(не главном) потоке. "AsyncTask!" - скажите вы, "Отлично!" - отвечу я. Что общего между runOnUiThread и AsyncTask? Версия выхода не считается;)
Давайте посмотрим на реализацию в фреймфорке.
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
}
private static class InternalHandler extends Handler {
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
.........................
}
}
}
И так... Как видно в обоих случаях используется Handler как средство взаимодействие между потоками. Т.е. посылая в Handler сообщение оно будет передано на обработку в поток Handler'а. По умолчанию используется UiThread. С этого следует что Handler закреплен за потоком. Так как Handler'ов может быть много, а UiThread у нас один - прошу любить и жаловать - Looper.
Looper является промежуточным звеном между потоком выполнения и Handler'ом. Из другого потока Looper просто создать и запустить не получиться, для такой цели используется HandlerThread - расширенная версия стандартного потока.
public void handleMessage(Message msg) {
switch (msg.what) {
.......
}
}
};
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
После чего сообщение будет отправлено на обработку в метод handleMessage.
Кстати да...., сообщения - по сути это структура описывающая и хранящая контекстноть события. Для этого есть целых 4 поля класса: arg1, arg2, what и obj. Первые 3 это int, последний - объект.
Все сообщения "стоят" в очереди в android.os.MessageQueue который принимает решение какое сообщение и на какой target необходимо передать для обработки.
Если открыть реализацию android.os.ActivityThread, то мы увидим самую что не есть стандартную точку входа - main. Которая запускает обработку системных событий предварительно подготовив Looper.
public static void main(String[] args) {
// .....................................
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// .....................................
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Представим себе задачу: разработать компонент(класс) в который каждые 10 секунд должен стучатся на сервер и получать какуюто информацию, каждые 30 секунд должен обновлять список доступных точек Wi-Fi, и каждые 4 секунды опрашивать подключенное к usb host устройство. При этом real-time не критичен. Вот такой у нас терминатор получился.
private Handler mWorkHandler;
private HandlerThread mHandlerThread;
public enum State {
stopped,
starting,
started,
stopping
}
private State mState;
private final Runnable mActionNetworkCheck = new Runnable() {
@Override
public void run() {
// проверяем данные на сервере
if (isItWork()) {
mWorkHandler.postDelayed(mActionNetworkCheck, 10000);
}
}
};
private final Runnable mActionWifiScan = new Runnable() {
@Override
public void run() {
// работа со сканом сетей
if (isItWork()) {
mWorkHandler.postDelayed(mActionWifiScan, 30000);
}
}
};
private final Runnable mActionUsbIo = new Runnable() {
@Override
public void run() {
// работа по usb хосту
if (isItWork()) {
mWorkHandler.postDelayed(mActionUsbIo, 4000);
}
}
};
private synchronized boolean isItWork() {
return mState == State.started;
}
public void startup() {
synchronized (this) {
if (mState == State.stopped) {
mState = State.starting;
} else {
throw new IllegalStateException("Example must be stopped for start");
}
}
mHandlerThread = new HandlerThread("WorkHandlerThread") {
@Override
protected void onLooperPrepared() {
mWorkHandler = new Handler(getLooper());
synchronized (HandlerExample.this) {
mState = HandlerExample.State.started;
}
mWorkHandler.post(mActionNetworkCheck);
mWorkHandler.post(mActionWifiScan);
mWorkHandler.post(mActionUsbIo);
}
};
mHandlerThread.start();
}
public void shutdown() {
synchronized (this) {
if (mState == State.started) {
mState = State.stopping;
} else {
throw new IllegalStateException("Example must be started for shutdown");
}
}
mHandlerThread.quit();
try {
mHandlerThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
mWorkHandler = null;
mHandlerThread = null;
synchronized (this) {
mState = State.stopped;
}
}
}
Выводы: организовать работу многих событий можно и в один поток если при этом временные рамки не жесткие. Конечно можно вспомнить о java.util.Timer и TimerTask'e, но ИМХО Handler более удобный.
Спасибо всем кто дочитал до этой строчки!
Давайте посмотрим на реализацию в фреймфорке.
Случай №1:
public final void runOnUiThread(Runnable action) {if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
Случай №2. Реализация вызова onPostExecute в AsyncTask:
private Result postResult(Result result) {Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
}
private static class InternalHandler extends Handler {
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
.........................
}
}
}
И так... Как видно в обоих случаях используется Handler как средство взаимодействие между потоками. Т.е. посылая в Handler сообщение оно будет передано на обработку в поток Handler'а. По умолчанию используется UiThread. С этого следует что Handler закреплен за потоком. Так как Handler'ов может быть много, а UiThread у нас один - прошу любить и жаловать - Looper.
Looper является промежуточным звеном между потоком выполнения и Handler'ом. Из другого потока Looper просто создать и запустить не получиться, для такой цели используется HandlerThread - расширенная версия стандартного потока.
Пример обработки сообщений:
Handle handler = new Handler() {public void handleMessage(Message msg) {
switch (msg.what) {
.......
}
}
};
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
После чего сообщение будет отправлено на обработку в метод handleMessage.
Кстати да...., сообщения - по сути это структура описывающая и хранящая контекстноть события. Для этого есть целых 4 поля класса: arg1, arg2, what и obj. Первые 3 это int, последний - объект.
Все сообщения "стоят" в очереди в android.os.MessageQueue который принимает решение какое сообщение и на какой target необходимо передать для обработки.
Если открыть реализацию android.os.ActivityThread, то мы увидим самую что не есть стандартную точку входа - main. Которая запускает обработку системных событий предварительно подготовив Looper.
public static void main(String[] args) {
// .....................................
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// .....................................
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
И так...
Давайте не углубляться в детати, перейдем к cути. Чем же Handler может помочь?Представим себе задачу: разработать компонент(класс) в который каждые 10 секунд должен стучатся на сервер и получать какуюто информацию, каждые 30 секунд должен обновлять список доступных точек Wi-Fi, и каждые 4 секунды опрашивать подключенное к usb host устройство. При этом real-time не критичен. Вот такой у нас терминатор получился.
И реализация:
class HandlerExample {private Handler mWorkHandler;
private HandlerThread mHandlerThread;
public enum State {
stopped,
starting,
started,
stopping
}
private State mState;
private final Runnable mActionNetworkCheck = new Runnable() {
@Override
public void run() {
// проверяем данные на сервере
if (isItWork()) {
mWorkHandler.postDelayed(mActionNetworkCheck, 10000);
}
}
};
private final Runnable mActionWifiScan = new Runnable() {
@Override
public void run() {
// работа со сканом сетей
if (isItWork()) {
mWorkHandler.postDelayed(mActionWifiScan, 30000);
}
}
};
private final Runnable mActionUsbIo = new Runnable() {
@Override
public void run() {
// работа по usb хосту
if (isItWork()) {
mWorkHandler.postDelayed(mActionUsbIo, 4000);
}
}
};
private synchronized boolean isItWork() {
return mState == State.started;
}
public void startup() {
synchronized (this) {
if (mState == State.stopped) {
mState = State.starting;
} else {
throw new IllegalStateException("Example must be stopped for start");
}
}
mHandlerThread = new HandlerThread("WorkHandlerThread") {
@Override
protected void onLooperPrepared() {
mWorkHandler = new Handler(getLooper());
synchronized (HandlerExample.this) {
mState = HandlerExample.State.started;
}
mWorkHandler.post(mActionNetworkCheck);
mWorkHandler.post(mActionWifiScan);
mWorkHandler.post(mActionUsbIo);
}
};
mHandlerThread.start();
}
public void shutdown() {
synchronized (this) {
if (mState == State.started) {
mState = State.stopping;
} else {
throw new IllegalStateException("Example must be started for shutdown");
}
}
mHandlerThread.quit();
try {
mHandlerThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
mWorkHandler = null;
mHandlerThread = null;
synchronized (this) {
mState = State.stopped;
}
}
}
Выводы: организовать работу многих событий можно и в один поток если при этом временные рамки не жесткие. Конечно можно вспомнить о java.util.Timer и TimerTask'e, но ИМХО Handler более удобный.
Спасибо всем кто дочитал до этой строчки!
Прочитал. Спасибо автору :)
ОтветитьУдалитьСпасибо за статью. Не совсем понятно почему все проверки происходят в цикле? По идеи лупер должен прогнать каждую задачу один раз и всё, а получается беспрерывный цикл?
ОтветитьУдалить