Pull to refresh

Еще один способ синхронизации потоков

Reading time4 min
Views3.3K
В этом топике рассматривается еще один способ синхронизации потоков с использованием Intent, BroadcastReceiver и Handler. Метод этот может показаться очень громоздким, но тем не менее вполне удобным, особенно если в приложении используется достаточно много ассинхронных потоков. Например, если реализуется сервис, который общается с вебсервисом в фоновом режиме. Еще один пример – приложение, которое портируется сразу на несколько платформ и большую часть кода работы с API можно совместно использовать и в Android, и в J2ME, и в Windows Phone 7.


План действий


  1. Подготавливаем статический класс с названиями интентов.
  2. Создаем методы в классе-наследнике Application для отправки интентов.
  3. Из тредов посылаем интенты событий.
  4. Перехватываем интенты в активити с помощью BroadcastReceiver.
  5. Передаем в Handler события и обрабатываем их.

Интенты


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

public final class Intents {
        // загрузилась очередная страница списка
        public final static String LIST_PAGE_RECEIVED = "com.olsoft.list.page.received";
        // загрузился имидж
        public final static String IMAGE_RECEIVED = "com.olsoft.image.received ";
        …
}

Рассылка интентов


Отправку интентов можно сделать через методы приложения: доступно из любого класса, не требуется передавать и хранить ссылку на контекст.
public class SampleApplication extends Application {
	private static SampleApplication mInstance;

	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
		…
	}

	/***
	 * Выдает экземпляр приложения
	 */
	public static SampleApplication getInstance() {
		return mInstance;
	}

	/***
	 * Запуск сообщения с указанным событием
	 * 
	 * @param action – события
	 */
	public void sendBroadcastIntent(String action) {
		final Intent intent = new Intent(action);
		sendBroadcast(intent);
	}

	/***
	 * Сообщаем, что загрузилась страница списка
	 */
	public static void notifyListPageLoaded() {
		getInstance().sendBroadcast(new Intent(Intents. LIST_PAGE_RECEIVED));
	}

	/***
	 * Сообщаем, что загрузилась картинка
	 */
	public static void notifyImageLoaded() {
		getInstance().sendBroadcast(new Intent(Intents.IMAGE_RECEIVED));
	}

        …
}

Поэтому рассылка интентов выглядит очень просто:

@Override
public void OnDownloadComplete(byte[] data) {
        …
        parseData(data);
        SampleApplication.notifyListPageLoaded();
        …
}

@Override
public void OnDownloadComplete(byte[] data) {
        …
        mImage = BitmapFactory.decodeByteArray(data, 0, data.length);
        SampleApplication.notifyListPageLoaded();
        …
}

В случае необходимости в интенте можно передать дополнительные параметры, например ID картинки. Для этого используем метод putExtra.

Перехват интентов


В активити интенты перехватываем с помощью анонимного экземпляра BroadcastReceiver. Устанавливаем его в момент перехода активити на передний план и убираем, когда активити уходит с экрана.

/***
 * При старте активити регистрируем обработчик событий
 */
@Override
protected void onResume() {
	super.onResume();

	IntentFilter f = new IntentFilter();
	f.addAction(Intents.LIST_PAGE_RECEIVED);
	f.addAction(Intents.IMAGE_RECEIVED);
	registerReceiver(mNotificationReceiver, f);
	…
}

/***
 * При остановке активити убираем обработчик событий
 */
@Override
protected void onPause() {
	unregisterReceiver(mNotificationReceiver);
	super.onPause();
}

/***
 * Перехватчик событий. Переправляем его в обработчик для запуска в UI треде
 */
private BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {

	@Override
	public void onReceive(Context context, Intent intent) {
		String action = intent.getAction();

		if (Intents.IMAGE_RECEIVED.equalsIgnoreCase(action)) {
			mHandler.removeMessages(MESSAGE_IMAGE_LOADED);
                        mHandler.sendEmptyMessageDelayed(MESSAGE_IMAGE_LOADED, 250);
		} else if (Intents. LIST_PAGE_RECEIVED.equalsIgnoreCase(action)) {
			mHandler.sendEmptyMessage(MESSAGE_NEXT_PAGE);
		}
	}
};

Сами события передаем в анонимный хендлер, поскольку обработка событий в активити чаще всего связано с обновлением компонентов на экране и требует исполнения в UI треде. Дополнительный плюс такого решения – можно обновлять компоненты с задержкой и отсекать часть событий, чтобы не вызывать мерцания на экране, например если каждый элемент списка содержит картинку, которая подгружается в отдельном треде (обратите внимание на методы removeMessages и sendEmptyMessageDelayed).

private final static int MESSAGE_NEXT_PAGE = 1;
private final static int MESSAGE_IMAGE_LOADED = 2;

/**
 * Обработчик сообщений. Вводим его, чтобы все события передавать в UI thread
 */
private Handler mHandler = new Handler() {

	@Override
	public void handleMessage(Message msg) {

		switch (msg.what) {
		case MESSAGE_NEXT_PAGE:
			mAdapter.notifyDataSetChanged();
			break;

		case MESSAGE_IMAGE_LOADED:
			mAdapter.notifyDataSetChanged();
			break;

		default:
			break;
		}
		super.handleMessage(msg);
	}

};


Заключение


Приведенный здесь метод выглядит достаточно громоздким, но если учесть что в большинстве активити возможно потребуется перехват событий от тредов, то целесообразно вынести этот функционал в базовую активити и остальные наследовать от этого базового класса. Сам код внести снипетом в Eclipse и не вводить каждый раз.
Tags:
Hubs:
Total votes 36: ↑34 and ↓2+32
Comments8

Articles