пятница, 24 января 2014 г.

Handler - маленький помошник Android разработчика

Всем привет, хочу поделиться методологией применения инструмента созданного облегчить разработку приложений (на сцену выходит Handler).

В кратце android.os.Handler это абстракция позволяющая "выполнять" в указаной очередности другие абстракции - события. А что же такое событие? На реализации событиями являются android.os.Message и обычные Runnable.

Немного по делу. Знакомо CalledFromWrongThreadException? Если да, то runOnUiThread не будет открытием, иначе все впереди:).  Рано или поздно у каждого из нас возникает необходимость обновить views после получения данных которое чаще всего выполняется в другом(не главном) потоке. "AsyncTask!" - скажите вы, "Отлично!" - отвечу я. Что общего между runOnUiThread и AsyncTask? Версия выхода не считается;)
Давайте посмотрим на реализацию в фреймфорке.

Случай №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 более удобный.

Спасибо всем кто дочитал до этой строчки!

2 комментария:

  1. Прочитал. Спасибо автору :)

    ОтветитьУдалить
  2. Спасибо за статью. Не совсем понятно почему все проверки происходят в цикле? По идеи лупер должен прогнать каждую задачу один раз и всё, а получается беспрерывный цикл?

    ОтветитьУдалить