Общение между потоками через ResultReceiver

в 15:25, , рубрики: android, Разработка под android, метки: ,

Как известно каждому Android-разработчику Android SDK предоставляет несколько способов заставить опреденный кусок кода выполнятся в параллельном потоке. Многопоточность это хорошо, но кроме ее организации нужно также наладить канал общения между потоками. Например, между UI-потоком и потоком, в котором выполняются фоновые задачи. В данном коротком эссе хочу осветить один из способов, основанный на применении встроенного класса ResultReceiver.

Вместо вступления

В большинстве Android-проектов приходится организовывать общение с внешним миром, т.е. организовывать сетевое взаимодействие. Не буду повторяться почему выполнять такой долгоиграющий код в UI-потоке плохо. Это всем известно. Более того, начиная с API 11 (Honeycomb который) система бьет разработчика по рукам исключением когда тот пытается в UI-потоке делать сетевые вызовы.
Одним из вариантов общения UI-потока с параллельным потоком (в котором, к примеру, выполняется http-запрос) есть подход, основанный на применении встроенного системного класса android.os.ResultReceiver совместно с сервисом.

Немного о архитектуре подхода

Для организации отдельного потока я выбрал IntentService. Почему именно он, а не простой Service? Потому, что IntentService при поступлении к нему команды автоматически начинает выполнять метод onHandleIntent(Intent intent) в отдельном от UI потоке. Простой Service такого не позволяет ибо он выполняется в основном потоке. Организовывать запуск потока из Service'а нужно самостоятельно.
Общение между Activity и IntentService-ом будет происходить с помощью Intent'ов.

Код

Сначала исходный код, потом ниже краткие комментарии к тому, что там и как происходит.

Реализация ResultReceiver'а

public class AppResultsReceiver extends ResultReceiver {
	
	public interface Receiver {
		public void onReceiveResult(int resultCode, Bundle data);
	}
	
	private Receiver mReceiver;
	
	public AppResultsReceiver(Handler handler) {
		super(handler);
	}
	
	public void setReceiver(Receiver receiver) {
		mReceiver = receiver;
	}

	@Override
	protected void onReceiveResult(int resultCode, Bundle resultData) {
		if (mReceiver != null) {
			mReceiver.onReceiveResult(resultCode, resultData);
		}
	}
}

Здесь следует обратить внимание на коллбэк (Receiver). При получении результата в onReceiveResult() делается проверка на не-null коллбэка. Дальше в коде активити будет показано как активировать и деактивировать ресивер с помощью этого коллбэка.

IntentService

public class AppService extends IntentService {
	public AppService() {
		this("AppService");
	}
	
	public AppService(String name) {
		super(name);
	}

	@Override
	protected void onHandleIntent(Intent intent) {
		final ResultReceiver receiver = intent.getParcelableExtra(Constants.RECEIVER);
		receiver.send(Constants.STATUS_RUNNING, Bundle.EMPTY);
		final Bundle data = new Bundle();
		
		try {
			Thread.sleep(Constants.SERVICE_DELAY);
			data.putString(Constants.RECEIVER_DATA, "Sample result data");
		} catch (InterruptedException e) {
			data.putString(Constants.RECEIVER_DATA, "Error");
		}
		receiver.send(Constants.STATUS_FINISHED, data);
	}
}

onHandleIntent() будет вызван после того, как вызывающий код (UI-классы etc.) выполнит startService(). Инстанс ResultReceiver'а будет извлечен из интента и ему тут же будет отослана команда «ОК, я пошел трудиться». После выполнения полезной работы в этом методе результаты (извлеченные из JSON классы-модели, строки, что-угодно) помещается в бандл и отправляется ресиверу. Причем для индикации типа ответа используются разные коды (описаны константами). Как ResultReceiver получает и отправляет данные можно почитать в его исходниках.

Посылка команды сервису и обработка результата (Activity)

public class MainActivity extends Activity implements AppResultsReceiver.Receiver {
	private AppResultsReceiver mReceiver;
	private ProgressBar mProgress;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mProgress = (ProgressBar) findViewById(R.id.progressBar); 
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		mReceiver = new AppResultsReceiver(new Handler());
		mReceiver.setReceiver(this);
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		mReceiver.setReceiver(null);
	}

	public void onStartButtonClick(View anchor) {
		final Intent intent = new Intent("SOME_COMMAND_ACTION", null, this, AppService.class);
		intent.putExtra(Constants.RECEIVER, mReceiver);
		startService(intent);
	}

	@Override
	public void onReceiveResult(int resultCode, Bundle data) {
		switch (resultCode) {
			case Constants.STATUS_RUNNING :
				mProgress.setVisibility(View.VISIBLE);
				break;
			case Constants.STATUS_FINISHED :
				mProgress.setVisibility(View.INVISIBLE);
				Toast.makeText(this, "Service finished with data: " 
						+ data.getString(Constants.RECEIVER_DATA), Toast.LENGTH_SHORT).show();
				break;
		}
	}
}

Здесь все просто. Activity реализует интерфейс AppResultsReceiver.Receiver. При старте создает экземпляр ресивера, при паузе — отвязывается от прослушивания ответов от сервиса. При клике на кнопку формируется команда (интент), в него помещается ссылка на наш ResultReceiver и стартуется сервис.
При получении ответа от сервиса в методе onReceiveResult() проверяется код ответа и выполняется соответствующее действие. Вот и все.

Демо-приложение выглядит просто, оно имеет всего одну кнопку «Послать запрос».
Общение между потоками через ResultReceiverОбщение между потоками через ResultReceiver

Исходный код демо-проекта доступен на GitHub

Вместо заключения

Обработка команды в фоновом сервисе реализована до безобразия просто: поток просто ставится на паузу на некоторое время. Конечно же в реальных приложениях нужно в интенте передавать код команды (action), которую нужно выполнить, дополнительные параметры и прочее. ООП вам в руки. Также стоит помнить, что данные (например, модели), которые будучи упакованными в бандл должны быть Parcelable-объектами. Это повысит эффективность их сериализации.
Конечно же описанный подход не есть истина в последней инстанции. Мы вольны выбирать разные архитектурные подходы, средства и комбинации. Будь то AsyncTask'и, Service+Thread+BroadcastReceiver или «ручная» передача Message'ей посредством Handler'а в UI-поток. Выбирать, как говориться, вам. Но это уже совсем другая история.

Автор: gshock

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js