700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Android四大组件之Service 远程服务解析 通过AIDL达到进程间通信交换基础数据

Android四大组件之Service 远程服务解析 通过AIDL达到进程间通信交换基础数据

时间:2023-02-27 10:07:33

相关推荐

Android四大组件之Service 远程服务解析 通过AIDL达到进程间通信交换基础数据

继上一篇介绍Service的文章后,这篇来具体分析下远程服务,具体代码请看/Mangosir/RemoteServiceMath

简介

远程服务:Android 系统与Windows系统的通信原则基本一致,进程就是安全策略的边界,不同的APP属于不同进程 Process,一个进程不能直接访问其他进程的资源。需要实现多进程间的通信,就要使用IPC(Inter Process Commnication)进程间通信技术。Android 系统的 IPC 机制是基于AIDL(AndroidInterfaceDefinition Language)接口定义语言定制进程间的通讯规则的。系统会基于AIDL 规则把信息进行序列化处理,然后发送到另一个进程当中,Android 系统把这种基于跨进程通信的服务称作 Remote Service 。

AIDL:是一种IDL语言,定义Android中两个进程间通信的规则。因为在Android中每个应用程序都是一个单独的JVM,就像两个独立的小岛,过着自己的生活,进行自己的操作,互不相干,虽然都是在地球上,但无法进行联系,这时候AIDL就像一座桥连接着两个岛,桥制定规则,规定人怎么来往,哪些人可以来往。在进程间就规定数据怎么进行传输;其底层采用的是Binder机制。

通信方式比较:其实进程间通信还可以使用BroadcastReceiver , Messenger 等,但是BroadcastReceiver占用资源较多,并且它优先级较低,如果在这个广播之前有系统级广播,那它就会延迟执行,对于那些及时性的通信要求高的应用显然不合适;而Messenger 通信虽然实现简单,但它是以队列的方式进行,显然对那些要求并发执行的应用也不合适,那AIDL优势也就出来了,可以传输复杂的数据量,也可以支持多进程并发情况的进程间通信。

远程服务应用:当今有很多企业都有多个独立的APP,如阿里巴巴旗下就天猫、淘宝、聚划算、支付宝等多个APP,这时候就有需要把Service服务放在一独立的后台进程当中,作为多个APP之间信息交换的桥梁。这样如用户信息,用户登录,身份验证等多个共用的模块都可以在Service服务中实现,以供不同的APP进行调用。

AIDL规则

那我们如何编写AIDL文件呢,其实AIDL语法和Java语法基本上差不多,只不过有点细微差别:

1.AIDL文件后缀名是.aidl,java文件是.java;

2.aidl默认支持的数据类型是:

* Java八种基本数据类型(byte/short/int/long/float/double/boolean/char)

* String 与CharSequence类型。

* List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是实现parcelable接口的

* Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是实现parcelable接口的,Map是不支持泛型的。

3.AIDL应用的目标文件即使与你正在编写的文件在同一个包下,也是需要导包的,但是java是不需要的;

4.AIDL中的定向 tag 表示了在跨进程通信中数据的流向,这个点放在下一篇(Android 远程服务 通过AIDL进行进程间复杂类型数据交换)详细叙述,

in 表示数据只能由客户端流向服务端,表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;

out 表示数据只能由服务端流向客户端,表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改,之后客户端将会同步变动;

inout 则表示数据可在服务端与客户端之间双向流通,表现为服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

5.AIDL文件大概分为两类,一类是定义parcelable对象,给其它AIDL文件使用AIDL支持的默认数据类型外的数据;一类是用来定义接口的。看到没,AIDL都是在定义规则,而没有具体实现,所以它为什么叫接口定义语言了。

使用AIDL基本步骤

声明文件:在服务端创建AIDL文件,用来声明Java Bean等传输的数据,传输调用的接口创建服务:在服务端创建远程Service,并继承接口实现类客户端绑定远程Service,并获得远程服务的代理对象,即获取远程Binder对象的接口的本地实现客户端通过接口调用远程Service提供的方法

使用样例

我们先做个简单的例子来体会下AIDL是什么东西(我这demo把远程服务端与客户端写在了一个app里)

声明一个AIDL文件,如图:

输入文件名,我这里是IMathInterface,然后就是这样

这是编译器自动帮我们新建的,可以发现AIDL文件所在包名跟我们主程序包名是一样的。

在这个文件里我定义了两个方法,代码如下:

package com.mangoer.remotemathservicedemo;//远程服务接口的定义interface IMathInterface {int add(int a,int b);void log(String tag);}

然后需要生成与之对应的java接口文件,点击AS顶部工具栏的Build,然后选择MakeProject,然后在如图位置就会生成对应的java文件

我们来看下这个文件

//继承IInterface接口,目的是将此接口与远程Binder对象关联public interface IMathInterface extends android.os.IInterface {//在接口中定义两个方法,没有具体实现,供客户端调用public int add(int a, int b) throws android.os.RemoteException;public void log(java.lang.String tag) throws android.os.RemoteException;/**Binder本地对象,即Server进程里面的Binder对象*/public static abstract class Stub extends android.os.Binder implements com.mangoer.remotemathservicedemo.IMathInterface {//Binder的唯一标识private static final java.lang.String DESCRIPTOR = "com.mangoer.remotemathservicedemo.IMathInterface";public Stub() {//调用这个方法,将描述符与Binder对象关联this.attachInterface(this, DESCRIPTOR);}/*** IBinder是一个接口,代表了跨进程传输能力** 这个方法用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象* 如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,因为Stub就是Binder的本地对象* 否则返回服务端的Binder代理,也就是内部类Proxy** 这就好比你在北京,A商城(Stub)也在北京,你想买A商城的苹果,就直接去本地A商城买东西* 但是如果你在西藏,那你只能去A商城在西藏的代理点(Proxy)去买它的苹果*/public static com.mangoer.remotemathservicedemo.IMathInterface asInterface(android.os.IBinder obj) {if ((obj==null)) {return null;}//通过描述符查找本地接口android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.mangoer.remotemathservicedemo.IMathInterface))) {//处于同一个进程return ((com.mangoer.remotemathservicedemo.IMathInterface)iin);}//跨进程,就构建IBinder这个接口的代理对象返回,通过代理对象调用远程服务方法return new com.mangoer.remotemathservicedemo.IMathInterface.Stub.Proxy(obj);}//返回与此接口关联的Binder对象,这里是Binder本地对象//因为Stub类继承Binder类,而Binder又实现了IBinder接口,所以直接返回自己@Overridepublic android.os.IBinder asBinder() {return this;}/** Parcel是Android系统应用程序间传递数据的容器,能够在两个进程中完成打包和拆包的工作 但Parcel不同于通用意义上的序列化* Parcel的设计目的是用于高性能IPC传输 不能持久化在存储设备上* 接收Parcel对象,并从中逐一读取每个参数,然后调用Service内部制定的方法,将结果写进另一个Parcel对象,* * onTransact运行在服务端中的Binder线程池中 客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法来处理,* 服务端接收到请求并且通过客户端携带的参数,执行完服务端的方法,返回结果*如果此方法返回false,那么客户端的请求就会失败** code :确定客户端请求的目标方法是什么* data : 如果目标方法有参数的话,就从data取出目标方法所需的参数* reply : 当目标方法执行完毕后,如果目标方法有返回值,就向reply中写入返回值* */@Overridepublic boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {switch (code) {case INTERFACE_TRANSACTION: {reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_add: {data.enforceInterface(DESCRIPTOR);int _arg0;//add方法第一个参数_arg0 = data.readInt();int _arg1;//add方法第二个参数_arg1 = data.readInt();//执行接口定义的方法,传入两个参数,执行完毕获取返回值,具体由Server实现具体逻辑int _result = this.add(_arg0, _arg1);reply.writeNoException();reply.writeInt(_result);return true;}case TRANSACTION_log: {data.enforceInterface(DESCRIPTOR);java.lang.String _arg0;_arg0 = data.readString();this.log(_arg0);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}//用来实现客户端的跨进程调用private static class Proxy implements com.mangoer.remotemathservicedemo.IMathInterface {//Binder代理对象,即Client里面的Binder对象,客户端绑定服务后传入的private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}//返回Binder代理对象@Override public android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}//以一定顺序将所有参数写入Parcel对象,以供Stub内部的onTransact()方法获取参数@Overridepublic int add(int a, int b) throws android.os.RemoteException {//输出,装载传递给服务端的数据android.os.Parcel _data = android.os.Parcel.obtain();//输入,装载服务端返回给客户端的数据android.os.Parcel _reply = android.os.Parcel.obtain();//返回数据int _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeInt(a);_data.writeInt(b);//transact方法运行在客户端,通过该方法发起远程过程调用(RPC)请求,同时当前线程挂起;//然后服务端的onTransact方法会被调用,直到RPC过程返回后,mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);//当前线程继续执行,并从_reply中取出RPC过程的返回结果,也就是返回_reply中的数据_reply.readException();_result = _reply.readInt();} finally {_reply.recycle();_data.recycle();}return _result;}@Overridepublic void log(java.lang.String tag) throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(tag);mRemote.transact(Stub.TRANSACTION_log, _data, _reply, 0);_reply.readException();} finally {_reply.recycle();_data.recycle();}}}static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_log = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);}}

你在AS里看到的可能比较乱,我这是整理后的

这个文件就是进程通信的关键了,注释写的非常清楚了,大概总结下:

IBinder是一个接口,代表了跨进程传输能力,负责数据传输,只要实现了这个接口,就能将这个类型的对象进行跨进程传递;这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换,有两个实现类Binder和BinderProxyIInterface代表的就是远程server对象具有什么能力,具体来说,就是aidl里面的接口Java层的Binder类代表的就是Binder本地对象,BinderProxy类是Binder类的一个内部类,它内部持有Binder引用,代表Binder代理对象;两个类都具备跨进程传输能力,在跨越进程的时候,Binder驱动会自动完成这两个对象的转换AIDL通过Stub类用来接收、处理、返回数据,Proxy代理类供客户端调用发送数据及接收返回值

Stub类里有一个很重要的方法就是asInterface,我们通常在onServiceConnecttion的回调里面,把它的参数传递给这个方法拿到一个远程的service的,它的参数是IBinder,这是驱动给我们的。

如果这个IBinder是Binder类型,说明是Binder本地对象,处于同一个进程,asInterface方法就返回Stub对象,Client直接用其访问Server;如果IBinder是BinderProxy类型,说明是Binder代理对象,处于不同进程,asInterface方法就通过IBinder实例化Proxy对象,Client在这个类里用BinderProxy访问Server

通常我们这是跨进程调用,在onServiceConnecttion的回调里面,你实际拿到的是Proxy对象,Proxy对象持有Binder代理对象,也就是这里面的mRemote,mRemote实际是BinderProxy;Client在调用远程服务提供的add方法,其实是调用Proxy类的add方法,这个方法将Client请求进行封装后通过BinderProxy类的transact方法发送到远程Server

说是发送到Server了,但是远不止这么简单,先看下BinderProxy类的transact方法

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");if (Binder.isTracingEnabled()) { Binder.getTransactionTracker().addTrace(); }return transactNative(code, data, reply, flags);}public native boolean transactNative(int code, Parcel data, Parcel reply,int flags) throws RemoteException;

transactNative是一个本地方法,这需要调用到native层了,经过一系列调用后,最终会调用到Binder驱动,然后Binder驱动会通知Server,调用Server进程的本地对象的onTransact方法,这就会走到Binder本地对象的onTransact方法,也就是Stub类的onTransact方法

这个方法里根据调用号(每个AIDL函数都有一个编号,在跨进程的时候,不会传递函数,而是传递编号指明调用哪个函数)调用相关函数,这里就调用add方法,然后将结果返回给驱动,驱动再将结果传递给Client

创建服务

新建MathService类继承Service

public class MathService extends Service {private String TAG = "MathService";/** 建立IMathInterface.Stub实例,并实现IMathInterface这个AIDL文件定义的几个远程服务接口*在onBind方法中将mBind返回给远程调用者* */private IMathInterface.Stub mBind = new IMathInterface.Stub() {@Overridepublic int add(int a, int b) throws RemoteException {Log.e(TAG,"add");return a+b;}@Overridepublic void log(String tag) throws RemoteException {log_e(tag);}};private void log_e(String tag) {while (true) {Log.e(tag,"math");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}@Overridepublic void onCreate() {super.onCreate();Log.e(TAG,"onCreate process id = " + Process.myPid());}@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.e(TAG,"onBind");return mBind;}@Overridepublic boolean onUnbind(Intent intent) {Log.e(TAG,"onUnbind");return false;}@Overridepublic void onDestroy() {super.onDestroy();Log.e(TAG,"onDestroy");}}

然后需要注册该服务

<service android:name=".MathService"android:process=":remote"><intent-filter><action android:name="com.mangoer.remotemathservicedemo.MathService" /></intent-filter></service>

可以看到我给服务加了一个属性android:process:

1.如果属性值是以一个冒号 : 开头的,则这个新的进程对于这个应用来说是私有的,只有该APP可以使用其它应用无法访问,进程名称为:App-packageName:remote。

2.如果属性值不是以冒号 : 开头的,则这个服务将运行在一个以这个名字命名的全局的进程中,当然前提是它有相应的权限。这将允许在不同应用中的各种组件可以共享这个进程,从而减少资源的占用;但是属性值至少要包含一个点号 .

这点从源码PackageParser.java解析AndroidManiefst.xml过程就明白进程名的命名要求

public class PackageParser {...private static String buildCompoundName(String pkg,CharSequence procSeq, String type, String[] outError) {String proc = procSeq.toString();char c = proc.charAt(0);if (pkg != null && c == ':') {if (proc.length() < 2) {//进程名至少要有2个字符return null;}String subName = proc.substring(1);//此时并不要求强求 字符'.'作为分割符号String nameError = validateName(subName, false, false);if (nameError != null) {return null;}return (pkg + proc).intern();}//此时必须字符'.'作为分割符号String nameError = validateName(proc, true, false);if (nameError != null && !"system".equals(proc)) {return null;}return proc.intern();}private static String validateName(String name, boolean requireSeparator,boolean requireFilename) {final int N = name.length();boolean hasSep = false;boolean front = true;for (int i=0; i<N; i++) {final char c = name.charAt(i);if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {front = false;continue;}if (!front) {if ((c >= '0' && c <= '9') || c == '_') {continue;}}//字符'.'作为分割符号if (c == '.') {hasSep = true;front = true;continue;}return "bad character '" + c + "'";}if (requireFilename && !FileUtils.isValidExtFilename(name)) {return "Invalid filename";}return hasSep || !requireSeparator? null : "must have at least one '.' separator";}}

我还加了一个intent-filter:这是为了隐式启动远程服务所用。

讲到intent-filter就得讲还有一个属性android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。手动设置false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。

现在要开始远程调用了,在布局里加几个按钮

绑定服务

然后在MainActivity里开始写,在绑定按钮里开始启动服务

@OnClick(R.id.bind)public void bind() {Log.e(TAG,"bind");//隐式启动服务,android5.0后要设置包名Intent serviceIntent = new Intent();serviceIntent.setAction("com.mangoer.remotemathservicedemo.MathService");serviceIntent.setPackage("com.mangoer.remotemathservicedemo");bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);}

因为不处于一个进程,需要隐式启动,而且在android5.0以前,我们只要设置Action就行了,但是之后需要把包名也一起设置进来,从源码可以看出如果启动service的intent的component和package都为空并且版本大于LOLLIPOP(5.0)的时候,直接抛出异常。

启动之前需要构建一个ServiceConnection

private IMathInterface mathInterface;private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mathInterface = IMathInterface.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {mathInterface = null;}};

在onServiceConnected里获取远程服务的实例,这时候我们看下日志

可以看出Mainactivity打印出来的进程id是6821,进程名称是包名,再看下远程服务的日志

进程id是7151,进程名是 包名:remote,这也就证明了应用程序与远程服务不处于同一个进程。可以看出远程服务回调了onCreate,onBind两个方法。

远程调用

然后点击计算按钮

@OnClick(R.id.math)public void math() {if (mathInterface == null) {Toast.makeText(this,"远程服务未绑定",Toast.LENGTH_LONG).show();return;}try {int result = mathInterface.add(3,2);Log.e(TAG,"result="+result);} catch (RemoteException e) {e.printStackTrace();}}

通过在onServiceConnected方法里拿到的远程服务实例,调用add方法,

在日志了看到主进程里打印了

11-09 14:52:48.5686821-6821/com.mangoer.remotemathservicedemo E/MainActivity: result=5

在远程服务进程打印了,也就是调用了add方法

11-09 14:52:48.567 7151-7171/com.mangoer.remotemathservicedemo:remoteE/MathService: add

这个操作是在客户端(MainActivity)里调用服务端(MathService)的add方法,并把两个int型数据传递给服务端,服务端计算完,把结果再返回给客户端(MainActivity),两个进程间就通过IMathInterface这个接口类进行数据交换。这就基本完成了两个进程间的简单通信。

这里传递的都是简单的数据类型,打包解包都是自动进行的,对于一些自定义的数据类型,需要实现Parcelable接口,将其转换成Parcel对象,使其穿越进程边界,这里我们下一篇继续(Android 远程服务 通过AIDL进行进程间复杂类型数据交换)。

使用AIDL实现远程服务调用总结起来就是:

Server端为了提供服务,就得提供一套接口,里面包含了各种功能的方法;为了让Client端能通过远程访问调用Server端方法,就通过Proxy模式(代理模式),将接口方法定义在抽象类,然后Server和Client继承抽象类,实现所有接口方法;只不过Server端的这些方法的有真正功能实现,Client端的这些方法只是对请求进行封装,然后通过Binder驱动发送给Server端

如果你看过一些系统源码的实现,比如ActivityManagerServer的源码,PackageManagerService源码等,你会发现实现AIDL有一个固定模式:肯定有个接口实现了IInterface,定义了一些方法表明Server具有哪些能力;一个跨进程传递对象必然实现了IBinder接口,如果是Binder本地对象,就肯定继承Binder,实现了IInterface接口;如果是Binder代理对象,必然实现了IInterface接口,持有BinderProxy引用(Binder和BinderProxy都实现了IBinder接口)

参考文章http://weishu.me//01/12/binder-index-for-newer/

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。