Контакты

Коммуникация между Activity и Service. Переключение между экранами приложения Передача данных между активити

Как-то возникла у меня задача передавать данные из сервиса в активити. Начались поиски решения в стандартном 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

Заключение

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

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

Последнее обновление: 03.04.2018

Для передачи данных между двумя Activity используется объект Intent . Через его метод putExtra() можно добавить ключ и связанное с ним значение.

Например, передача из текущей activity в SecondActivity строки "Hello World" с ключом "hello":

// создание объекта Intent для запуска SecondActivity Intent intent = new Intent(this, SecondActivity.class); // передача объекта с ключом "hello" и значением "Hello World" intent.putExtra("hello", "Hello World"); // запуск SecondActivity startActivity(intent);

Для передачи данных применяется метод putExtra() , который в качестве значения позволяет передать данные простейших типов - String, int, float, double, long, short, byte, char, массивы этих типов, либо объект интерфейса Serializable.

Чтобы получить отправленные данные при загрузке SecondActivity, можно воспользоваться методом get() , в который передается ключ объекта:

Bundle arguments = getIntent().getExtras(); String name = arguments.get("hello").toString(); // Hello World

В зависимости от типа отправляемых данных при их получении мы можем использовать ряд методов объекта Bundle. Все они в качестве параметра принимают ключ объекта. Основные из них:

    get() : универсальный метод, который возвращает значение типа Object. Соответственно поле получения данное значение необходимо преобразовать к нужному типу

    getString() : возвращает объект типа String

    getInt() : возвращает значение типа int

    getByte() : возвращает значение типа byte

    getChar() : возвращает значение типа char

    getShort() : возвращает значение типа short

    getLong() : возвращает значение типа long

    getFloat() : возвращает значение типа float

    getDouble() : возвращает значение типа double

    getBoolean() : возвращает значение типа boolean

    getCharArray() : возвращает массив объектов char

    getIntArray() : возвращает массив объектов int

    getFloatArray() : возвращает массив объектов float

    getSerializable() : возвращает объект интерфейса Serializable

Пусть у нас в проекте будет определено две activity: MainActivity и SecondActivity.

В коде SecondActivity определим получение данных:

Package com.example.eugene.serializeapp; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView textView = new TextView(this); textView.setTextSize(20); textView.setPadding(16, 16, 16, 16); Bundle arguments = getIntent().getExtras(); if(arguments!=null){ String name = arguments.get("name").toString(); String company = arguments.getString("company"); int price = arguments.getInt("price"); textView.setText("Name: " + name + "\nCompany: " + company + "\nPrice: " + price); } setContentView(textView); } }

В данном случае в SecondActivity получаем все данных из объекта Bundle и выводим их в текстовое поле TextView. Предполагается, что данной activity будут передаваться три элемента - две строки с ключами name и company и число с ключом price.

Теперь определим передачу в SecondActivity данных. Например, определим для MainActivity следующий интерфейс в файле activity_main.xml :

Понравилась статья? Поделитесь ей