Handler

| 04.03.2015

Handler — это помощник/хэлпер, который позволяет нам организовать «коммуникацию» основного UI-потока и отдельно создаваемого нами потока для выполнения различного рода задач. Как известно, мы не можем обращаться из не UI-потока к элементам экрана. Однако, данная задача достаточно легко решается с использованием Handler-а.

Чтобы лучше понять суть Handler-а, представьте, что для основного UI-потока есть некая очередь сообщений MessageQueue. Мы можем передавать свои «сообщения» в эту очередь из собственного потока, но не напрямую, а с помощью Handler-а. Получается, что Handler эти сообщения отправляет и сам же их получает (обрабатывает). Handler связан с главным потоком, отсюда и выгода от работы с ним — он имеет доступ до элементов экрана.

Handler может принимать не только обычные сообщения (Messages), но и объекты типа Runnable.

Рассмотрим простой пример. Создадим небольшое приложение с экраном, содержащим два элемента: TextView и Button. При нажатии на кнопку мы будем запускать новый поток, в котором будет выполняться цикл с задержкой в 10 секунд. После паузы с помощью Handler-а мы будем отправлять сообщение в основной поток с номером текущей итерации. Этот номер мы будем выводить на экране в TextView.

1. Добавим строковые ресурсы res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">HandlerExample</string>
    <string name="result">result</string>
    <string name="start_loop">Start Loop</string>

</resources>

2. Создадим layout — res/layout/activity_main.xml

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" 
    tools:context=".MainActivity">

    <TextView
        android:text="@string/result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/txtV" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/start_loop"
        android:id="@+id/button"
        android:layout_below="@+id/txtV"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="70dp"
        android:onClick="startLoop"/>

</RelativeLayout>

В атрибут android:onClick мы добавили название метода для обработки клика.

Добавьте значения отступов в res/values/dimens.xml

<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
</resources>

3. Создадим код Активити

package ru.androiddocs.handlerexample;

import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import java.util.concurrent.TimeUnit;


public class MainActivity extends ActionBarActivity {

    private Handler mHandler;
    private TextView mCounter;
    public static final String LOG_TAG = "my_tag";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mCounter = (TextView)findViewById(R.id.txtV);

        mHandler = new Handler() {
            public void handleMessage(android.os.Message msg) {
                mCounter.setText("Номер итерации: " + msg.what);
            };
        };
    }

    public void startLoop(View v) {
        new Thread(new Runnable() {
            public void run() {
                for (int i = 1; i <= 7; i++) {

                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    mHandler.sendEmptyMessage(i);


                    Log.d(LOG_TAG, "current i: " + i);
                }
            }
        }).start();
    }
}

В общем-то, код несложный. Тут вся изюминка в создании экземпляра Handler с методом handleMessage(), который на вход получает сообщение (это объект android.os.Message).

mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
        mCounter.setText("Номер итерации: " + msg.what);
    };
};

Мы достаем значение свойства what, что и является нашим сообщением и выводим его в TextView.

Отправка сообщения делается в нашем новом потоке:

mHandler.sendEmptyMessage(i);

где i — наше сообщение.

Запустим приложение и нажмем на кнопку. Сообщение на экране начнет обновляться.

Handler

Как передать в Handler несколько сообщений

Чтобы передать больше сообщений, в том числе и различного типа (какой-то объект, например), можно использовать перегруженные вариации метода obtainMessage(). Например, используя obtainMessage(int what, int arg1, int arg2), можно передать три параметра:

Message msg = mHandler.obtainMessage(4, 3, 0);
mHandler.sendMessage(msg);

При получении все аргументы доступны как свойства объекта Message (аналогично what):

int whatArg = msg.what;
int firstArg = msg.arg2;
int secondArg = msg.arg2;

Как передать в Handler объект типа Runnable

Помимо обычных сообщений мы можем отправлять в Handler объекты типа Runnable. Мы как бы говорим помощнику: запусти этот поток. При этом из запущенного потока мы будем иметь доступ до элементов UI. Простой пример:

new Handler().postDelayed(new Runnable() {
    @Override public void run() {
        mCounter.setText("Привет из нового потока!");
    }
}, 5000);

Здесь мы используем метод postDelayed(), которому передаем Runnable и указываем время задержки 5 секунд, т.е. запустить новый поток с задержкой в 5 секунд. Обрабатывать здесь что-либо (получать сообщения) нам не нужно. В потоке мы изменяем текст в TextView.

Если задержка в запуске не нужна, то используйте метод post():

new Handler().post(new Runnable() {
    @Override public void run() {
        mCounter.setText("Привет из нового потока!");
    }
});

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*