Вместо вступления
В большинстве 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()
проверяется код ответа и выполняется соответствующее действие. Вот и все.
Демо-приложение выглядит просто, оно имеет всего одну кнопку «Послать запрос».
Исходный код демо-проекта доступен на GitHub
Вместо заключения
Обработка команды в фоновом сервисе реализована до безобразия просто: поток просто ставится на паузу на некоторое время. Конечно же в реальных приложениях нужно в интенте передавать код команды (action), которую нужно выполнить, дополнительные параметры и прочее. ООП вам в руки. Также стоит помнить, что данные (например, модели), которые будучи упакованными в бандл должны быть Parcelable-объектами. Это повысит эффективность их сериализации.
Конечно же описанный подход не есть истина в последней инстанции. Мы вольны выбирать разные архитектурные подходы, средства и комбинации. Будь то AsyncTask’и, Service+Thread+BroadcastReceiver или «ручная» передача Message’ей посредством Handler’а в UI-поток. Выбирать, как говориться, вам. Но это уже совсем другая история.
ссылка на оригинал статьи http://habrahabr.ru/post/167679/
Добавить комментарий