Как организовать передачу данных из Service в Activity? Передача данных между Activity. Сериализация Передача данных между activity android

Здравствуйте.

Необходимо передать данные, полученные через UART в Activity. Это можно сделать, создав в Activity поток, в котором организовать цикл while (!isInterrupted()) и вычитывать данные из буфера UART. После этого, вызвав UI поток Activity - MainActivity.this.runOnUiThread(new Runnable() , выполнить необходимые действия с этой Activity. Но если из основной Activity мы вызываем другие Activity, то организованный поток не позволяет передавать данные во вновь созданные Activity. Если я правильно понимаю, то для того чтобы данные из потока можно было передать в любую Activity, поток необходимо создавать не в Activity, а в Service.

Вопрос: по UART пришли данные, в потоке (который создан в Servce) необходимо передать данные в Activity, которая сейчас является активной, как это можно сделать и так ли это вообще делается?

1 ответ

В каждой Activity создаёте Handler. В методе onResume() этого Activity делает bindService(). Там одним из параметров выступает interface ServiceConnection. Имплементите его хоть тем же Activity. Реализуете в нём метод onServiceConnected(). В этом callback-е одним из параметров приходит сам Service. Вот и вызовите у этого Service свой собственный метод setHandler(). Передайте туда тот Handler, который именно в текущем Activity. А вот приходящие данные по UART кидайте в Service на этот Handler. Кстати, Handler традиционно работает в главном потоке, поэтому не нужно будет runOnUiThread выполнять.

Как-то возникла у меня задача передавать данные из сервиса в активити. Начались поиски решения в стандартном SDK, но так как времени не было, то сваял плохое решение в виде использования базы данных. Но вопрос был открыт и спустя некоторое время я разобрался с более верным способом, который есть в SDK - использование классов Message, Handler, Messenger.

Идея

Нам нужно передавать данные из активити в сервис и обратно. Как нам это сделать? Для решения нашей задачи у нас уже есть все необходимое. Все что нужно - это привязать сервис к ативити, используя bindService, передать нужные параметры и немного магии в виде использования классов Message. А магия заключается в том, чтобы использовать переменные экземпляра Message и в частности, replyTo. Данная переменная нужна нам, чтобы мы могли обратиться к экземпляру Messanger сервиса из активити и в сервисе к экземпляру Messanger-а активити. На самом деле, не так уж и просто. По крайней мере для моего не самого одаренного ума. Отчасти я просто улучшаю документацию, которая уже есть - Services Также, есть хороший пример на StackOverflow. В любом случае, надеюсь статья будет полезна хоть кому-то и я потрудился не зря.

Пример

В качестве примера реализуем сервис, который будем увеличивать и уменьшать значение счетчика и возвращать результат в активити, в TextView. Код макета опущу, ибо там две кнопки и текстовое поле - все просто.

Реализация

Приведу полностью код активити:

Public class MainActivity extends Activity { public static final String TAG = "TestService"; TestServiceConnection testServConn; TextView testTxt; final Messenger messenger = new Messenger(new IncomingHandler()); Messenger toServiceMessenger; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); testTxt = (TextView)findViewById(R.id.test_txt); bindService(new Intent(this, TestService.class), (testServConn = new TestServiceConnection()), Context.BIND_AUTO_CREATE); } @Override public void onDestroy(){ super.onDestroy(); unbindService(testServConn); } public void countIncrClick(View button){ Message msg = Message.obtain(null, TestService.COUNT_PLUS); msg.replyTo = messenger; try { toServiceMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } public void countDecrClick(View button){ Message msg = Message.obtain(null, TestService.COUNT_MINUS); msg.replyTo = messenger; try { toServiceMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } private class IncomingHandler extends Handler { @Override public void handleMessage(Message msg){ switch (msg.what) { case TestService.GET_COUNT: Log.d(TAG, "(activity)...get count"); testTxt.setText(""+msg.arg1); break; } } } private class TestServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { toServiceMessenger = new Messenger(service); //отправляем начальное значение счетчика Message msg = Message.obtain(null, TestService.SET_COUNT); msg.replyTo = messenger; msg.arg1 = 0; //наш счетчик try { toServiceMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } } }

Поясню. При создании активити мы сразу привязываемся к сервису, реализуя интерфейс ServiceConnection и в нем оправляем сообщение сервису «установить значение счетчика», передавая ноль и создавая toServiceMessanger, передавая в конструктор интерфейс IBinder. Кстати, в сервисе обязательно нужно вернуть этот экемпляр, иначе будет NPE. С помощью этого класса мы и отправляем сообщения сервису. И вот она магия - в переменную replyTo мы сохраняем наш другой экземпляр Messenger - тот который получает ответ от сервера и именно через него и будет осуществляться связь с активити.

Для получения сообщения от сервиса используем свой Handler и просто ищем нужные нам переменные и делаем по ним действия. По кликам на кнопки(методы countIncrClick, countDecrClick) отправляем запросы к сервису, указывая нужное действие в переменной msg.what.

Package com.example.servicetest; import android.app.Service; import android.content.*; import android.os.*; import android.os.Process; import android.util.Log; public class TestService extends Service { public static final int COUNT_PLUS = 1; public static final int COUNT_MINUS = 2; public static final int SET_COUNT = 0; public static final int GET_COUNT = 3; int count = 0; IncomingHandler inHandler; Messenger messanger; Messenger toActivityMessenger; @Override public void onCreate(){ super.onCreate(); HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); inHandler = new IncomingHandler(thread.getLooper()); messanger = new Messenger(inHandler); } @Override public IBinder onBind(Intent arg0) { return messanger.getBinder(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } //обработчик сообщений активити private class IncomingHandler extends Handler { public IncomingHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg){ //super.handleMessage(msg); toActivityMessenger = msg.replyTo; switch (msg.what) { case SET_COUNT: count = msg.arg1; Log.d(MainActivity.TAG, "(service)...set count"); break; case COUNT_PLUS: count++; Log.d(MainActivity.TAG, "(service)...count plus"); break; case COUNT_MINUS: Log.d(MainActivity.TAG, "(service)...count minus"); count--; break; } //отправляем значение счетчика в активити Message outMsg = Message.obtain(inHandler, GET_COUNT); outMsg.arg1 = count; outMsg.replyTo = messanger; try { if(toActivityMessenger != null) toActivityMessenger.send(outMsg); } catch (RemoteException e) { e.printStackTrace(); } } } }

Все по аналогии с логикой в активити. Даже не знаю, нужно ли что-то пояснять. Единственный момент - это то, что я сразу отправляю запрос обратно в активити в handleMessage, используя для этого волшебную переменную replyTo и вытаскивая выше нужный Messenger. И второй момент о котором я уже говорил - это:

@Override public IBinder onBind(Intent arg0) { return messanger.getBinder(); }

без которого все упадет. Именно данный экземпляр интерфейса и будет передан в ServiceConnection

Заключение

Вцелом все. Такой вот надуманный пример взаимодействия активити и сервиса. Мне кажется, довольно таки нетривиальное взаимодействие, хотя кому-то может показаться иначе.

Вопросы, уточнения и прочее в личку. Могут быть неточности по поводу каких-либо аспектов, поэтому смело пишите и поправляйте.
Надеюсь, пост был полезен читателям.

Приложение не всегда состоит из одного экрана. Например, мы создали очень полезную программу и пользователю хочется узнать, кто же её автор. Он нажимает на кнопку «О программе» и попадает на новый экран, где находится полезная информация о версии программы, авторе, адресе сайта, сколько у автора котов и т.д. Воспринимайте экран активности как веб-страницу с ссылкой на другую страницу. Если вы посмотрите на код в файле MainActivity.java из прошлых уроков, то увидите, что наш класс MainActivity тоже относится к Activity (или его наследникам) или, если говорить точнее, наследуется от него.

Public class MainActivity extends AppCompatActivity

Как нетрудно догадаться, нам следует создать новый класс, который может быть похож на MainActivity и затем как-то переключиться на него при нажатии кнопки.

Для эксперимента мы возьмём программу из первого урока и будем использовать для опытов кнопку (или создайте новый проект с одной кнопкой на экране). Далее создадим новую форму для отображения полезной информации. Например, покажем пользователю, что делает кот, когда идёт налево и направо. Согласитесь, это очень важная информация, дающая ключ к разгадке Вселенной.

Создавать новую активность будем вручную, хотя в студии есть готовые шаблоны. Но там ничего сложного и для лучшего понимания полезно всё делать руками.

Создадим новый XML-файл разметки activity_about.xml в папке res/layout . Щёлкните правой кнопкой мыши на папке layout и выберите из контекстного меню New | Layout resource file . Появится диалоговое окно. В первом поле вводим имя файла activity_about . Во втором нужно ввести корневой элемент. По умолчанию там стоит ConstraintLayout . Стираем текст и вводим ScrollView . Ввода нескольких символов достаточно, чтобы студия подсказала готовые варианты, можно сразу нажать Enter, не дожидаясь полного ввода слова:

Получится соответствующая заготовка, в которую вставим элемент TextView .

Информация будет извлекаться из ресурсов, а именно из строкового ресурса about_text . Сейчас он подсвечен красным цветом, сигнализируя об отсутствии информации. Можно было нажать Alt+Enter и ввести текст в диалоговом окне. Но для нашего примера этот способ не подойдёт, так как наш текст будет многострочным, с использованием управляющих символов. Поэтому поступим по-другому. Откроем файл res/values/strings.xml и вводим следующий текст вручную:

У лукоморья дуб зелёный;\n Златая цепь на дубе том:\n И днём и ночью кот учёный\n Всё ходит по цепи кругом;\n Идёт направо - песнь заводит,\n Налево - сказку говорит.

Мы использовали простейшие HTML-теги форматирования текста типа , , . Для нашего примера достаточно выделить жирным слова, которые относятся к коту и направлению движения. Для перевода текста на новую строку используйте символы \n . Добавим ещё один строковый ресурс для заголовка нового экрана:

О программе

С разметкой разобрались. Далее необходимо создать класс для окна AboutActivity.java . Выбираем в меню File | New | Java Class и заполняем нужные поля. На первых порах достаточно указать только имя. Потом разберётесь с другими полями.

Получим заготовку.

Сейчас класс практически пустой. Добавим код вручную. Класс должен наследоваться от абстрактного класса Activity или его родственников типа FragmentActivity , AppCompatActivity и т.д. Дописываем extends Activity . У класса активности должен быть метод onCreate() . Ставим курсор мыши внутри класса и выбираем в меню Code | Override Methods (Ctrl+O). В диалоговом окне ищем нужный класс, можно набирать на клавиатуре первые символы для быстрого поиска. В созданном методе нужно вызвать метод setContentView() , который подгрузит на экран подготовленную разметку. У нас получится такой вариант.

Package ru.alexanderklimov.helloworld; import android.app.Activity; import android.os.Bundle; /** * Created by Alexander Klimov on 01.12.2014. */ public class AboutActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_about); } }

Теперь начинается самое главное. Наша задача - перейти на новый экран при щелчку кнопки на первом экране. Переходим обратно к классу MainActivity . Напишем обработчик щелчка кнопки:

Public void onClick(View view) { Intent intent = new Intent(MainActivity.this, AboutActivity.class); startActivity(intent); }

Здесь я использовал способ обработки нажатия кнопки, о котором рассказывалось в занятии .

Для запуска нового экрана необходимо создать экземпляр класса Intent и указать в первом параметре текущий класс, а во втором - класс для перехода, у нас это AboutActivity . После этого вызывается метод startActivity() , который и запускает новый экран.

Если вы сейчас попытаетесь проверить работу приложения в эмуляторе, то получите сообщение об ошибке. Что мы сделали неправильно? Мы пропустили один важный шаг. Необходимо зарегистрировать новый Activity в манифесте AndroidManifest.xml . Найдите этот файл в своем проекте и дважды щёлкните на нём. Откроется окно редактирования файла. Добавьте новый тег после закрывающего тега для первой активности. Печатайте самостоятельно и активно используйте подсказки. Получится следующее:

Вот и пригодился строковый ресурс about_title . Запускаем приложение, щёлкаем на кнопке и получаем окно О программе . Таким образом мы научились создавать новое окно и вызывать его по щелчку кнопки. А в нашем распоряжении появилась мегаудобная программа - теперь всегда под рукой будет подсказка, что делает кот, когда идёт налево.

Ещё раз обращаю внимание, что второй создаваемый класс активности должен наследоваться от класса Activity или ему похожих (ListActivity и др.), иметь XML-файл разметки (если требуется) и быть прописан в манифесте.

После вызова метода startActivity() запустится новая активность (в данном случае AboutActivity ), она станет видимой и переместится на вершину стека, содержащего работающие компоненты. При вызове метода finish() из новой активности (или при нажатии аппаратной клавиши возврата) она будет закрыта и удалена из стека. Разработчик также может перемещаться к предыдущей (или к любой другой) активности, используя всё тот же метод startActivity() .

Создаём третий экран - способ для ленивых

Программисты, как и коты, существа ленивые. Постоянно помнить, что для активности нужно создать разметку и класс, который наследуется от Activity , а затем не забыть прописать класс в манифесте - да ну нафиг.

В этом случае выберите в меню File | New | Activity | Basic Activity (или другой шаблон). Дальше появится знакомое вам окно создания новой активности. Заполняем необходимые поля.

Нажимаем на кнопку Finish и активность будет готова. Чтобы убедиться в этом, откройте файл манифеста и проверьте наличие новой записи. Про файлы класса и разметки я уже не говорю, они сами появятся перед вами.

Самостоятельно добавьте новую кнопку на экране главной активности и напишите код для перехода на созданную активность.

На первых порах я бы посоветовал вам вручную создавать все необходимые компоненты для новой активности, чтобы понимать взаимосвязь между классом, разметкой и манифестом. А когда набьёте руку, то можете использовать мастер создания активности для ускорения работы.

Передача данных между активностями

Мы использовали простейший пример для вызова другого экрана активности. Иногда требуется не только вызвать новый экран, но и передать в него данные. Например, имя пользователя. В этом случае нужно задействовать специальную область extraData , который имеется у класса Intent .

Область extraData - это список пар ключ/значение , который передаётся вместе с намерением. В качестве ключей используются строки, а для значений можно использовать любые примитивные типы данных, массивы примитивов, объекты класса Bundle и др.

Для передачи данных в другую активность используется метод putExtra() :

Intent.putExtra("Ключ", "Значение");

Принимающая активность должна вызвать какой-нибудь подходящий метод: getIntExtra() , getStringExtra() и т.д.:

Int count = getIntent().getIntExtra("name", 0);

Переделаем предыдущий пример. У нас уже есть три активности. У первой активности разместим два текстовых поля и кнопку. Внешний вид может быть следующим:

У второй активности SecondActivity установим элемент TextView , в котором будем выводить текст, полученный от первой активности. Напишем следующий код для метода onCreate() у второй активности.

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); String user = "ЖЫвотное"; String gift = "дырку от бублика"; TextView infoTextView = (TextView)findViewById(R.id.textViewInfo); infoTextView.setText(user + " , вам передали " + gift); }

Если сейчас запустить программу и просто вызвать второе окно, как это было описано в первой части статьи, то мы увидим надпись по умолчанию ЖЫвотное, вам передали дырку от бублика . Согласитесь, довольно обидно получать такие сообщения.

Исправляем ситуацию. Добавляем код у первой активности:

Public void onClick(View view) { EditText userEditText = (EditText) findViewById(R.id.editTextUser); EditText giftEditText = (EditText) findViewById(R.id.editTextGift); Intent intent = new Intent(MainActivity.this, SecondActivity.class); // в ключ username пихаем текст из первого текстового поля intent.putExtra("username", userEditText.getText().toString()); // в ключ gift пихаем текст из второго текстового поля intent.putExtra("gift", giftEditText.getText().toString()); startActivity(intent); }

Мы поместили в специальный контейнер объекта Intent два ключа со значениями, которые берутся из текстовых полей. Когда пользователь введёт данные в текстовые поля, они попадут в этот контейнер и будут переданы второй активности.

Вторая активность должна быть готова к тёплому приёму сообщений следующим образом (выделено жирным).

// Значения по умолчанию String user = "ЖЫвотное"; String gift = "дырку от бублика"; user = getIntent().getExtras().getString("username"); gift = getIntent().getExtras().getString("gift"); TextView infoTextView = (TextView)findViewById(R.id.textViewInfo); infoTextView.setText(user + " , вам передали " + gift);

Теперь сообщение выглядит не столь обидным, а даже приятным для кое-кого. В сложных примерах желательно добавить проверку при обработке данных. Возможны ситуации, когда вы запустите вторую активность с пустыми данными типа null , что может привести к краху приложения.

В нашем случае мы знаем, что ждём строковое значение, поэтому код можно переписать так:

Intent intent = getIntent(); user = intent.getStringExtra("username");

User = getIntent().getStringExtra("username");

У программы есть недостаток - не понятно, от кого мы получаем приветы. Любая хорошо воспитанная мартышка не возьмет подарок от анонимного источника. Поэтому в качестве домашнего задания добавьте ещё одно текстовое поле для ввода имени пользователя, который отправляет сообщение.

Google рекомендует для ключей использовать следующий формат: имя вашего пакета в качестве префикса, а затем сам ключ. В этом случае можно быть уверенным в уникальности ключа при взаимодействии с другими приложениями. Приблизительно так:

Public final static String USER = "ru.alexanderklimov.myapp.USER";

Кто подставил кота Ваську - получаем результат обратно

Не всегда бывает достаточно просто передать данные другой активности. Иногда требуется получить информацию обратно от другой активности при её закрытии. Если раньше мы использовали метод startActivity(Intent intent) , то существует родственный ему метод startActivityForResult(Intent intent, int RequestCode) . Разница между методами заключается в дополнительном параметре RequestCode . По сути это просто целое число, которое вы можете сами придумать. Оно нужно для того, чтобы различать от кого пришёл результат. Допустим у вас есть пять дополнительных экранов и вы присваиваете им значения от 1 до 5, и по этому коду вы сможете определить, чей результат вам нужно обрабатывать. Вы можете использовать значение -1, тогда это будет равносильно вызову метода startActivity() , т.е. никакого результата не получим.

Если вы используете метод startActivityForResult() , то вам необходимо переопределить в коде метод для приёма результата onActivityResult() и обработать полученный результат. Запутались? Давайте разберём пример.

Предположим, вы сыщик. Поступила информация, что в ресторане со стола влиятельного человека украли два кусочка колбасы и другие продукты. Подозрение пало на трёх подозреваемых - ворона, сраный пёсик и кот Васька.

Один из посетителей предоставил серию фотографий со своего понтового айфона:


Также имеются показания другого свидетеля: А Васька слушает, да ест .

Создаём новый проект Sherlock с двумя активностями. На первом экране будет кнопка для переключения на второй экран и текстовая метка, в которой будет отображено имя воришки.