Статьи

Функциональность обмена между приложениями Android с AIDL

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

Чтобы передавать данные между процессами, их необходимо маршалировать и демаршировать соответственно. Это маршалинг и демаршмализация данных в примитивные типы такова, что ОС может понять, что передача через межпроцессное взаимодействие (IPC) может быть утомительной и подверженной ошибкам, если она выполняется вручную. Язык определения интерфейса Android (AIDL) помогает решить эту проблему. AIDL обычно состоит из трех компонентов:

Библиотека интерфейса

Определяет интерфейс, использующий AIDL между клиентом и сервисом. Эта библиотека будет зависеть как от клиента, так и от службы, реализующей интерфейс.

Сервис

Это будет отдельное приложение (apk), которое реализует интерфейс, определенный библиотекой интерфейса.

Клиент

Клиент может быть отдельным приложением (apk), которое будет выполнять вызовы к сервису, используя интерфейсную библиотеку.

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

Определение AIDL

Чтобы определить AIDL, нам нужен файл с расширением .aidl . В этом руководстве мы поместим AIDL в отдельный библиотечный проект под названием androidaidllibrary . Android SDK берет AIDL и генерирует «Binder», реализованный сервисом.

Синтаксис AIDL подобен Java. Вы можете использовать все примитивные типы в своем определении AIDL и такие объекты, как «Строки», «Список» и «Карты». Если вы хотите определить свои собственные объекты, тогда вы можете, но вам нужно предоставить определение класса, которое реализует интерфейс Parcelable .

В этом примере мы определим интерфейс под названием IRemoteProductService который будет иметь два метода, addProduct и getProduct . Чтобы создать AIDL в Android Studio, выберите пункт меню Файл -> Создать -> AIDL -> Файл AIDL, чтобы создать файл с именем IRemoteProductService и добавить в него следующий код. Вам нужно будет изменить пакет и импортировать имена в соответствии с вашим приложением:

 // IRemoteProductService.aidl package com.androidaidl.androidaidllibrary; import com.androidaidl.androidaidllibrary.Product; interface IRemoteProductService { void addProduct(String name , int quantity, float cost); Product getProduct(String name); } 

Если вы попытаетесь построить проект до сих пор, он потерпит неудачу из-за использования пользовательского типа product , который еще не определен. Чтобы определить пользовательский тип, нам нужно сделать две вещи. Сначала определите класс Product который реализует интерфейс Parcelable, и файл aidl, который определяет Product . Создайте файлы Product.java и Product.aidl, как показано ниже (измените имена пакетов соответствующим образом):

 package com.androidaidl.androidaidllibrary; import android.os.Parcel; import android.os.Parcelable; public class Product implements Parcelable { private String name; private int quantity; private float cost; public Product(String name, int quantity, float cost) { this.name = name; this.quantity = quantity; this.cost = cost; } private Product(Parcel in) { name = in.readString(); quantity = in.readInt(); cost = in.readFloat(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeString(name); out.writeInt(quantity); out.writeFloat(cost); } public static final Parcelable.Creator<Product> CREATOR = new Parcelable.Creator<Product>() { public Product createFromParcel(Parcel in) { return new Product(in); } public Product[] newArray(int size) { return new Product[size]; } }; public String getName() { return name; } public int getQuantity() { return quantity; } public float getCost() { return cost; } @Override public String toString() { return "Product{" + "name='" + name + '\'' + ", quantity=" + quantity + ", cost=" + cost + '}'; } } 
 // Product.aidl package com.androidaidl.androidaidllibrary; parcelable Product; 

Product.java — это простой файл Java, который определяет поля и функции для Product и реализует необходимые методы. writeToParcel записывает поля класса в участок и создает статический объект с именем CREATOR который может создать объект из участка.

Теперь вы сможете собрать проект и увидеть автоматически сгенерированный IRemoteProductService.java в папке build / generate .

Реализация интерфейса AIDL.

После того, как мы создали интерфейсную библиотеку, нам нужно реализовать интерфейс в сервисе. Мы создадим отдельное приложение для этого. Под «отдельным приложением» я подразумеваю отдельный файл apk. Это может быть новый проект или модуль в том же проекте. Используя другой apk, вызовы будут межпроцессными, для чего предназначен AIDL.

Добавьте androidaidllibrary как зависимость от приложения и создайте сервис, как показано ниже:

 package com.androidaidl.androidaidlservice; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import com.androidaidl.androidaidllibrary.IRemoteProductService; import com.androidaidl.androidaidllibrary.Product; import java.util.ArrayList; import java.util.List; import java.util.Collections; public class ProductService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { // Return the interface return mBinder; } private final IRemoteProductService.Stub mBinder = new IRemoteProductService.Stub() { List <Product> products = Collections.synchronizedList(new ArrayList<Product>()); @Override public void addProduct(String name, int quantity, float cost) throws RemoteException { //Add product called on the service. //Idealy you should store the product in a local data base //or in some remote service. //You can add that code here . We are just storing in In memory list. Product product = new Product(name, quantity, cost); products.add(product); } @Override public Product getProduct(String name) throws RemoteException { //getProduct product called on the service. //Idealy you should store the product in a local data base //or in some remote service. Hence the product should be fetched from there. //You can add that code here . //We are just storing in In memory list.So fetching from in memory list. for(Product product : products) { if(product.getName().equalsIgnoreCase(name)) { return product; } } return null; } }; } 

Когда мы создали файл IRemoteProductService.aidl, он сгенерировал класс IRemoteProductService.Stub() который должен реализовать наш IRemoteProductService.Stub() . Выше мы реализовали это и сохранили продукты в карте памяти. Служба возвращает эту onBind клиенту в функции onBind . Нам нужно объявить сервис в AndroidManifest.xml :

 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.androidaidl.androidaidlservice"> <application android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme"> <service android:name="com.androidaidl.androidaidlservice.ProductService"> <intent-filter> <action android:name="com.androidaidl.androidaidlservice.ProductService" /> </intent-filter> </service> </application> </manifest> 

Выполнение вызова IPC от клиента.

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

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.androidaidl.androidaidl.MainActivity" android:id="@+id/mainLayout"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Product name" android:id="@+id/txtName" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/edtName" android:ems="10" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Product Quantity" android:id="@+id/txtQuantity" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="number" android:ems="10" android:id="@+id/edtQuantity" android:layout_below="@+id/editName" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Product Cost" android:id="@+id/txtCost" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="numberDecimal" android:ems="10" android:id="@+id/edtCost" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Add Product" android:id="@+id/btnAdd" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Search Product" android:id="@+id/txtSearch" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:ems="10" android:id="@+id/edtSearchProduct" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Search Product" android:id="@+id/btnSearch" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="" android:id="@+id/txtSearchResult" /> </LinearLayout> </RelativeLayout> 
 package com.androidaidl.androidaidl; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.androidaidl.androidaidllibrary.IRemoteProductService; import com.androidaidl.androidaidllibrary.Product; public class MainActivity extends Activity { private IRemoteProductService service; private RemoteServiceConnection serviceConnection; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); connectService(); Button addProduct = (Button)findViewById(R.id.btnAdd); addProduct.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { if (service != null) { String name = ((EditText) findViewById(R.id.edtName)).getText().toString(); int quatity = Integer.parseInt(((EditText) findViewById(R.id.edtQuantity)).getText().toString()); float cost = Float.parseFloat(((EditText) findViewById(R.id.edtCost)).getText().toString()); service.addProduct(name, quatity, cost); Toast.makeText(MainActivity.this, "Product added.", Toast.LENGTH_LONG) .show(); } else { Toast.makeText(MainActivity.this, "Service is not connected", Toast.LENGTH_LONG) .show(); } } catch (Exception e) { } } }); Button searchProduct = (Button)findViewById(R.id.btnSearch); searchProduct.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { if (service != null) { String name = ((EditText) findViewById(R.id.edtSearchProduct)).getText().toString(); Product product = service.getProduct(name); if(product != null) { ((TextView) findViewById(R.id.txtSearchResult)).setText(product.toString()); } else { Toast.makeText(MainActivity.this, "No product found with this name", Toast.LENGTH_LONG) .show(); } } else { Toast.makeText(MainActivity.this, "Service is not connected", Toast.LENGTH_LONG) .show(); } } catch(Exception e) { } } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } private void connectService() { serviceConnection = new RemoteServiceConnection(); Intent i = new Intent("com.androidaidl.androidaidlservice.ProductService"); i.setPackage("com.androidaidl.androidaidlservice"); boolean ret = bindService(i, serviceConnection, Context.BIND_AUTO_CREATE); } class RemoteServiceConnection implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder boundService) { service = IRemoteProductService.Stub.asInterface((IBinder) boundService); Toast.makeText(MainActivity.this, "Service connected", Toast.LENGTH_LONG) .show(); } public void onServiceDisconnected(ComponentName name) { service = null; Toast.makeText(MainActivity.this, "Service disconnected", Toast.LENGTH_LONG) .show(); } } } 

Выше в методе onCreate действия мы вызываем connectService который создает намерение для службы и вызывает bindService , передавая реализацию ServiceConnection которая вызывается при подключении службы. Когда служба подключена, связыватель возвращается как обратный вызов, который является реализацией интерфейса, который мы храним для использования для вызовов.

В пользовательском интерфейсе мы создаем поля для ввода значений для добавления продукта и вызываем addProduct для сохраненного сервисного объекта. Чтобы получить продукт, мы называем getProduct . Установите оба приложения (сервис и активность клиента), и вы сможете увидеть экран, а также добавлять и получать продукты, как показано ниже.

Вывод

Android упрощает взаимодействие между процессами. Он предоставляет инструменты и механизмы для удаления большого количества стандартного кода, который в противном случае был бы необходим.

AIDL предоставляет простой и понятный метод создания компонентов в Android для совместного использования функций между приложениями и службами.

Дайте мне знать, если у вас есть какие-либо вопросы или проблемы.