时间:2021-07-09 08:26:22 | 栏目:Android代码 | 点击:次
简介
AIDL:Android Interface Definition Language,即Android接口定义语言,用于生成Android不同进程间进行进程通信(IPC)的代码,一般情况下一个进程是无法访问另一个进程的内存的。如果某些情况下仍然需要跨进程访问内存数据,这时候Android系统就要将其对象分解成能够识别的原数据,编写这一组操作的代码是一项繁琐的工作,但是AIDL对底层进行了抽象的封装,简化了跨进程操作。
AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。
在Android中跨进程操作的方式不止一种,四大组件中ContentProvider天生就是为跨进程操作而存在的,但是ContentProvider所谓的跨进程操作数据,这些数据不一定是存放在内存中的,如通讯录数据时存放在Sqlite数据库中的。AIDL支持的跨进程操作的数据是要存放在内存中的,AIDL底层实际上也是使用的Binder进行的跨进程操作,后续另起一篇博文继续介绍Binder的跨进程机制。
使用场景
只有不同应用之间需要进行IPC,并且想要在Service中处理多线程时,这种场景才有必要使用AIDL。如果仅仅需要跨进程但是不是跨应用,这时候应该通过Binder进行数据交互;另外如果仅仅是需要跨进程IPC,但是不需要处理多线程,这时候应该通过Messenger类进行数据交互。
定义AIDL接口
在Android Studio中使用AIDL的项目的目录结构跟eclipse中有很大差异,下图是使用AIDL的项目的目录结构。
在Android Studio中只需要在某个Module中使用右键菜单中new就会显示创建AIDL文件的菜单,当新建成功后AIDL文件位于工程的同java同一级的aidl目录文件夹下面。在 .aidl 文件中存放的就是AIDL接口。
定义.aidl文件
.aidl文件名称必须同接口名称保持一致,必须使用Java语言的语法定义AIDL文件。AIDL使用简单语法,通过可带参数和返回值的一个或多个方法来声明接口。参数和返回值可以是任意类型,甚至可以是其他 AIDL 生成的接口。每个.aidl文件都必须定义单个接口,并且只需包含接口声明和方法签名,也意味着在.aidl文件中接口名称和方法名称都不可以使用权限修饰符。
默认情况下,AIDL 支持下列数据类型:
定义AIDL接口时需要注意如下:
如下是定义是IRemoteService.aidl:
package com.sunny.server; import com.sunny.server.bean.User; interface IRemoteService { void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); String getName(); void setListData(in List<String> inList,out List<String> outList); void setMapData(in Map map); void setUser(in User user); }
下面是自定义的JavaBean类型User.aidl
package com.sunny.server.bean; parcelable User;
User类在使用的时候必须实现Parcelable接口,代码这里就不再贴出来了。
Service实现AIDL接口
在定义的AIDL接口编译后实际上会生成一个跟.aidl同名的Java类文件,里面包含了所有的AIDL文件中声明的方法,并且包含了一个默认的实现类Stub,该类是抽象类,继承了Binder类实现了AIDL接口。在Stub类中有两个方法一个是asInterface()方法,该方法返回的是AIDL文件生成的接口,另外一个方法是asBinder(),该方法返回的是一个IBinder类型的实例。
asInterface()和asBinder()方法非常有用,asInterface()方法可以用于客户端的IPC方法调用,另外一个方法可以用于在服务端返回Binder实例,并在服务端实现响应的接口方法。
上面介绍过在定义非基本数据类型的时候必须定义数据走向,声明in或out或者inout,在AIDL生成的Java文件中就可以看出来究竟了,这里可以参看setListData()方法的生成实现。
@Override public void setListData(java.util.List<java.lang.String> inList, java.util.List<java.lang.String> outList) 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.writeStringList(inList); mRemote.transact(Stub.TRANSACTION_setListData, _data, _reply, 0); _reply.readException(); _reply.readStringList(outList); } finally { _reply.recycle(); _data.recycle(); } }
如果声明数据时是in,在生成相对应的方法的时候调用的实际上是Parcel的writeXXX方法,如果声明的是out,在实现上面采用的是readXXX,所以在定义的时候一定要明确调用逻辑。
接下来看一下服务端中MyService类的实现。
public class MyService extends Service { private static final String TAG = "AIDL_Server"; @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } private IRemoteService.Stub mBinder = new IRemoteService.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { Log.d(TAG, "anInt:" + anInt + " aLong:" + aLong + " aBoolean:" + aBoolean + " aFloat:" + aFloat + " aDouble:" + aDouble + " aString:" + aString); } @Override public String getName() throws RemoteException { return "admin"; } @Override public void setListData(List<String> inList, List<String> outList) throws RemoteException { Log.d(TAG, "inList:" + inList.toString()); setOutList(outList); } @Override public void setMapData(Map map) throws RemoteException { Log.d(TAG, "map:" + map.toString()); } @Override public void setUser(User user) throws RemoteException { Log.d(TAG, "user:" + user.toString()); } }; private void setOutList(List<String> list) { list.add("out_01"); list.add("out_02"); list.add("out_03"); } }
在MyService类中,除了getName是一个有返回值的方法,其余的方法都是void类型的,另外在数据走向方面,除了setListData方法的第二个参数outList是输出类型的参数,其余的参数都是输入类型参数,所以这里将其它参数直接打印出来了。
调用IPC方法
在客户端想要调用Android的AIDL中定义的IPC方法,可以通过如下步骤实现:
如下是客户端Activity中代码的实现:
public class MainActivity extends AppCompatActivity { private static final String TAG = "AIDL_Client"; private MyConnection conn; private IRemoteService service; private List<String> outList=new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void startBind(View v) { Intent intent = new Intent(); conn=new MyConnection(); intent.setAction("com.sunny.server.service.MyService"); bindService(intent, conn, Context.BIND_AUTO_CREATE); } public void startExecute(View v) { try { service.basicTypes(1, 10000L, true, 1.5f, 300.3, "Hello World"); Log.d(TAG, "getName:" + service.getName()); List<String> inList = new ArrayList<String>(); inList.add("inList01"); inList.add("inList02"); service.setListData(inList, outList); Log.d(TAG, "outList:" + outList.toString()); Map<String, String> map = new HashMap<String, String>(); map.put("key01", "value01"); map.put("key02", "value02"); service.setMapData(map); User user = new User(); user.setId(1001); user.setName("admin"); service.setUser(user); } catch (RemoteException e) { e.printStackTrace(); } } private class MyConnection implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder binder) { service = IRemoteService.Stub.asInterface(binder); } public void onServiceDisconnected(ComponentName name) { } } @Override protected void onDestroy() { super.onDestroy(); unbindService(conn); } }
其它
上述示例只是为了介绍AIDL如何跨进程通信的,所以在客户端接收到的数据直接就在主线程中处理了。但是实际上客户端调用服务端的远程方法,被调用的方法运行在服务端的Binder线程池中的,同时客户端线程会被挂起,这时候如果服务端方法执行比较耗时,就会导致客户端长时间阻塞在这里,如果客户端方法位于UI线程中,可能会引起ANR。在实际开发的时候注意,客户端进行IPC通信的时候尽量放在子线程中。由于服务端的方法本身就是运行在服务端的Binder线程池中,所以即使服务端需要执行大量耗时的工作也不需要开启新的线程去执行。
另外一定要注意的就是安全性,默认情况下远程服务任何人都可以连接,这应该不是我们所需要的,所以还需要考虑一下权限验证。一般情况下有两种处理方法,第一种是通过自定义权限的方法,我们在服务端Service方法的onBinder()方法中添加权限验证,如果权限验证不通过直接返回null。另外一种就是在服务端的onTransact()方法中做验证,也是做权限验证,如果不通过直接返回false。除了上面讲的权限验证之外,可以通过getCallingPid()和getCallingUid()拿到客户端应用的Pid和Uid进行校验。