Android Service aidl使用及进阶
Posted ITRenj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Service aidl使用及进阶相关的知识,希望对你有一定的参考价值。
- 《Android Service基础》
- 《Android Service回调和配置》
- 《Android Service aidl使用及进阶》
- 《Android Service更多知识》
- 《Android 中的 IntentService 类详解》
- 《Android Service aidl分析》
- 《Android Service 流程分析》
AIDL:android 接口定义语言,可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 使用 AIDL 为简化此问题。
注意: 只有在需要不同应用的客户端通过 IPC 方式访问服务,并且希望在服务中进行多线程处理时,才有必要使用 AIDL。如果无需跨不同应用执行并发 IPC,则应通过实现 Binder 来创建接口;或者,如果想执行 IPC,但不需要处理多线程,请使用 Messenger 来实现接口。
使用AIDL
服务端定义
-
创建aidl文件
此文件定义带有方法签名的编程接口。
-
实现接口
Android SDK 工具会基于定义的 .aidl 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。代码中必须扩展 Stub 类并实现这些方法。
-
向客户端公开接口
实现 Service 并重写 onBind(),从而返回 Stub 类的实现。
创建 .aidl 文件
AIDL 使用一种简单语法,允许通过一个或多个方法(可接收参数和返回值)来声明接口。参数和返回值可为任意类型,甚至是 AIDL 生成的其他接口。(AIDL文件的创建和java接口的创建类似,或者就是按照创建java接口的方式(注意:不能像Java 8以上带非抽象方法),然后将文件的扩展名修改为 .aidl)。
AIDL支持的数据:
- Java 编程语言中的所有基本数据类型(如 int、long、char、boolean 等)
- String、CharSequence
- 自定义的Bean(查看以下 “在aidl中使用自定义对象时的步骤或者注意” 内容)
- List(List 中的所有元素必须是以上列表中支持的数据类型,或者声明的由 AIDL 生成的其他接口或 Parcelable 类型。可以选择将 List 用作“泛型”类(例如,List)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList。)
- Map(Map 中的所有元素必须是以上列表中支持的数据类型,或者声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap。)
在aidl中使用自定义对象时的步骤或者注意:
-
实现 Parcelable 接口
-
创建声明 Parcelable 类的 .aidl 文件(如下: BookBean.aidl)
package com.renj.service.bean; parcelable BookBean; // BoodBean 为自定义的对象
-
自定义的数据即使与aidl接口文件在相同的包内,也需要通过 import 导包。
定义服务接口时,请注意:
- 方法可带零个或多个参数,可以有返回值或无返回值
- 所有非基本数据类型的参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout。基本数据类型默认为 in,不能是其他方向。(应将方向限定为真正需要的方向,因为编组参数的开销较大。)
in、out 和 inout说明:
in、out、inout表示非基本数据类型的数据的走向标记,三个值都是针对服务端的(服务端接收客户端的输入和服务端输出给客户端),in表示只能输入;out表示只能输出;inout表示既能输入也能输出。
-
in:服务端只能接收输入,不能输出给客户端。当客户端传递BookBean给服务端时,服务端能获取到BookBean对象信息,但是服务端进行修改之后,客户端不会发生改变,还是原来的。
-
out:服务端不能接收输入,只能输出给客户端。当客户端传递BookBean给服务端时,服务端获取到的BookBean对象之后,并不能获取BookBean里面的字段信息(都是默认值),但是服务端修改了这些值后,客户端的信息会发生改变,变成服务端修改之后的值。
-
inout:服务端既能接收输入,也能输出给客户端。当客户端传递BookBean给服务端时,服务端能获取到BookBean对象信息;服务端修改了这些值后,客户端的信息也会发生改变,变成服务端修改之后的值。
// BoodBean.aidl package com.renj.service.bean; parcelable BookBean; // BoodBean 为自定义的对象 // IRemoteBook.aidl import com.renj.service.bean.BookBean; import java.util.List; interface IRemoteBookBinder void addBookIn(in BookBean bookBean); void addBookOut(out BookBean bookBean); void addBookInOut(inout BookBean bookBean); List<BookBean> getBookList();
实现接口
Android SDK 工具会基于定义的 .aidl 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 Stub 的内部抽象类(YourInterface.Stub),用于扩展 Binder 类并实现 AIDL 接口中的方法。代码中必须扩展 Stub 类并实现这些方法。
注意:Stub 还会定义几个辅助方法,其中最值得注意的是 asInterface(),该方法会接收 IBinder(通常是传递给客户端 onServiceConnected() 回调方法的参数),并返回 Stub 接口的实例。
public class IRemoteBookBinderImpl extends IRemoteBookBinder.Stub
private List<BookBean> bookBeans = new ArrayList<>();
@Override
public void addBookIn(BookBean bookBean) throws RemoteException
@Override
public void addBookOut(BookBean bookBean) throws RemoteException
@Override
public void addBookInOut(BookBean bookBean) throws RemoteException
@Override
public List<BookBean> getBookList() throws RemoteException
return bookBeans;
向客户端公开接口
在为服务实现接口后,您需要向客户端公开该接口,以便客户端进行绑定。如要为您的服务公开该接口,请扩展 Service 并实现 onBind(),从而返回实现生成的 Stub 的类实例
public class RemoteBookService extends Service
public static final String SERVICE_NAME = RemoteBookService.class.getSimpleName();
private IRemoteBookBinderImpl remoteBookBinder;
@Nullable
@Override
public IBinder onBind(Intent intent)
remoteBookBinder = new IRemoteBookBinderImpl();
return remoteBookBinder;
// AndroidManifest.xml 文件声明
<service
android:name=".remote.RemoteBookService"
android:exported="true"
android:label="远程绑定服务"
android:process="com.renj.remote.binder">
<intent-filter>
<action android:name="com.renj.remote.binder" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
现在,当客户端(如 Activity)调用 bindService()
以连接此服务时,客户端的 onServiceConnected()
回调会接收服务的 onBind()
方法所返回的 binder 实例。
客户端还必须拥有接口类的访问权限,因此如果客户端和服务在不同应用内,则客户端应用的 src/ 目录内必须包含 .aidl 文件(该文件会生成 android.os.Binder 接口,进而为客户端提供 AIDL 方法的访问权限)的副本。(将服务端定义的关于aidl的部分复制一份到客户端,同时保证相关类的包名和服务端保持一致)
当客户端在 onServiceConnected()
回调中收到 IBinder 时,它必须调用 YourServiceInterface.Stub.asInterface(service),以将返回的参数转换成 YourServiceInterface 类型。例如:
// 绑定服务连接对象
private IRemoteBookBinder iRemoteBookBinder;
private ServiceConnection bindConnection = new ServiceConnection()
@Override
public void onServiceConnected(ComponentName name, IBinder service)
iRemoteBookBinder = IRemoteBookBinder.Stub.asInterface(service);
@Override
public void onServiceDisconnected(ComponentName name)
iRemoteBookBinder = null;
;
// 绑定远程服务
Intent intent = new Intent();
intent.setAction("com.renj.remote.binder");
intent.setPackage("com.renj.service");
bindService(intent, bindConnection, Service.BIND_AUTO_CREATE);
客户端调用
通过以上操作,当我们在客户端(如 Activity)调用 bindService()
以连接到服务成功并且在onServiceConnected()
回调会接收服务的 onBind()
方法所返回的 binder 实例后,就可以通过该实例调用服务器方法了。
try
// addBookIn
Logger.i("-------- addBookIn() ------------------------");
int nextIntIn = RandomUtils.randomInt(10000);
BookBean bookBeanIn = new BookBean("书名-" + nextIntIn, "作者-" + nextIntIn,
(RandomUtils.randomInt(10000) + 10000) / 100d);
Logger.i(RemoteBinderService.SERVICE_NAME + " addBookIn() before info:" + bookBeanIn.toString());
iRemoteBookBinder.addBookIn(bookBeanIn);
Logger.i(RemoteBinderService.SERVICE_NAME + " addBookIn() after info:" + bookBeanIn.toString());
// addBookOut
Logger.i("-------- addBookOut() ------------------------");
int nextIntOut = RandomUtils.randomInt(10000);
BookBean bookBeanOut = new BookBean("书名-" + nextIntOut, "作者-" + nextIntOut,
(RandomUtils.randomInt(10000) + 10000) / 100d);
Logger.i(RemoteBinderService.SERVICE_NAME + " addBookOut() before info:" + bookBeanOut.toString());
iRemoteBookBinder.addBookOut(bookBeanOut);
Logger.i(RemoteBinderService.SERVICE_NAME + " addBookOut() after info:" + bookBeanOut.toString());
// addBookInOut
Logger.i("-------- addBookInOut() ------------------------");
int nextIntInOut = RandomUtils.randomInt(10000);
BookBean bookBeanInOut = new BookBean("书名-" + nextIntInOut, "作者-" + nextIntInOut,
(RandomUtils.randomInt(10000) + 10000) / 100d);
Logger.i(RemoteBinderService.SERVICE_NAME + " addBookInOut() before info:" + bookBeanInOut.toString());
iRemoteBookBinder.addBookInOut(bookBeanInOut);
Logger.i(RemoteBinderService.SERVICE_NAME + " addBookInOut() after info:" + bookBeanInOut.toString());
Logger.i("-------------------------------------------------");
catch (RemoteException e)
e.printStackTrace();
Binder池
通过以上的介绍,我们知道了通过aidl来调用远程服务的方法,就是定义aidl文件(类似接口),然后实现扩展YourInterface.Stub类,最后通过Service的onBind()方法返回给客户端,客户端获取到了定义的接口对象,就可以实现方法的调用了。通过上面我们能增加和获取BookBean信息了,但是现在我的需求增加了,我不止要获取BookBean信息,我现在增加一个增加和获取PersonBean信息的模块。怎么办了?好办,继续重复上面的步骤,再写一个aidl,创建一个Service返回。这样实现是没有任何问题的,但是随着我后面继续增加更多的模块,那么我们就需要继续的创建Service了,理论上是没有任何问题的。但是就会出现两个问题了
- 我们的Service虽然说是在后台运行的,在应用中对用户不可见,但是在Android中可以通过查看所有正在运行的程序查看到当前服务,一看很多不知道有什么作用的东西在运行,让人及其不爽
- 当然,相对于第一点,第二点就更加严重了。因为Service作为Android四大组件之一,他的创建、运行和销毁都是需要耗时和消耗资源的,当我们创建了很多的Service时,那么对手机的电池、内存和我们应用的流畅度都是非常不友好的,从而直接影响用户体验,这当然是不可忍受的。
我们在使用线程时,为了避免线程的不断创建与销毁,使用线程池。那么在Service中是不是也可以利用这种思想了,答案是可以的,我们可以通过Binder连接池来处理对多个远程的aidl接口进行调用。具体的思路就是:通过一个aidl接口,这个接口的功能就是客户端通过不同的参数来获取对应的IBinder对象,这样的话,我们只需要创建一个Service返回这个aidl接口,在客户端调用这个接口方法,通过传递不同的参数来获取不同的IBinder对象即可。(Service相当于Binder池,返回不同IBinder对象的aidl接口实现有点类似简单工厂模式)
-
定义返回不同IBinder的aidl文件 IBinderPoolBinder.aidl
// IBinderPoolBinder.aidl interface IBinderPoolBinder IBinder getBinder(int type);
-
扩展 IBinderPoolBinder.Stub IBinderPoolBinderImpl
// IBinderPoolBinder 实现类 public class BinderPoolBinderImpl extends IBinderPoolBinder.Stub public final static int BINDER_TYPE_BOOK = 0; public final static int BINDER_TYPE_PERSON = 1; public final static int BINDER_TYPE_STRING = 2; private RemoteBookBinderImpl remoteBookBinder; private RemotePersonBinderImpl remotePersonBinder; private RemoteStringBinderImpl remoteStringBinder; @Override public IBinder getBinder(int type) throws RemoteException switch (type) case BINDER_TYPE_BOOK: if (remoteBookBinder == null) remoteBookBinder = new RemoteBookBinderImpl(); return remoteBookBinder; case BINDER_TYPE_PERSON: if (remotePersonBinder == null) remotePersonBinder = new RemotePersonBinderImpl(); return remotePersonBinder; case BINDER_TYPE_STRING: if (remoteStringBinder == null) remoteStringBinder = new RemoteStringBinderImpl(); return remoteStringBinder; return null;
-
通过Service返回IBinderPoolBinder BinderPoolService
public class BinderPoolService extends Service private BinderPoolBinderImpl binderPoolBinder; @Nullable @Override public IBinder onBind(Intent intent) binderPoolBinder = new BinderPoolBinderImpl(); return binderPoolBinder;
-
客户端使用 BinderPoolManager
// 绑定池aidl private IBinderPoolBinder iBinderPoolBinder; // ServiceConnection 对象 private ServiceConnection bindConnection = new ServiceConnection() @Override public void onServiceConnected(ComponentName name, IBinder service) iBinderPoolBinder = IBinderPoolBinder.Stub.asInterface(service); @Override public void onServiceDisconnected(ComponentName name) ; // 绑定服务 Intent intent = new Intent(); intent.setAction("com.renj.remote.pool"); intent.setPackage("com.renj.service"); applicationContext.bindService(intent, bindConnection, Service.BIND_AUTO_CREATE); // 根据类型返回 IBinder if (iBinderPoolBinder != null) try IBinder iBinder = iBinderPoolBinder.getBinder(binderType); catch (RemoteException e) e.printStackTrace();
以上是关于Android Service aidl使用及进阶的主要内容,如果未能解决你的问题,请参考以下文章
Android进阶——借鉴FlutterEngine,实现子进程渲染视图
Android进阶——借鉴FlutterEngine,实现子进程渲染视图