AIDL
AIDL概述
AIDL全称是Android Interface Definition Language,即Android接口定义语言。学习一门计算机技术语言,就需要了解该语言的基本知识:
设计AIDL的目的是什么
设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
AIDL语法
-
文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。.aidl文件大致可以分为两类,一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的,后面在数据类型中有对parcelable的介绍。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。两种.aidl文件例子如下:
// 文件名称 Book.aidl //这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用 package com.test.ipcdemo; //注意parcelable是小写 parcelable Book;
// 文件名称 BookManager.aidl package com.test.ipcdemo; //导入所需要使用的非默认支持数据类型的包, //特别需要注意,无论Book.aidl是否和BookManager.aidl 在同一个目录下,都需要导入包。 import com.test.ipcdemo.Book; interface BookManager { //传参时除了Java基本类型以及String,CharSequence之外的类型 //都需要在前面加上定向tag,具体加什么量需而定 oneway setBookPrice(in Book book , int price) oneway setBookName(in Book book , String name) Book addBookIn(in Book book); Book addBookOut(out Book book); Book addBookInout(inout Book book); }
-
数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,即使目标文件与当前正在编写的 .aidl 文件在同一个包下,在使用之前必须导包,非默认类型通常是我们自定义的类。默认支持的数据类型包括:
Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char
String 类型
CharSequence类型。
List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。List可以使用泛型。
Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。
所有Parceable接口的实现类,因为跨进程传输对象时,本质上是序列化与反序列化的过程,自定义的类都需要实现Parceable接口。
AIDL接口,所有的AIDL接口本身也可以作为可支持的数据类型; -
实现Parceable接口的.aidl文件
以上述Book.aidl为例,编写序列化后的Book.java文件,重点如下:
包名要与.aidl文件一致package com.test.ipcdemo;
引入Parcelable需要的包
import android.os.Parcel; import android.os.Parcelable; 实现Parcelable
实现Parcelable
public class Book implements Parcelable{
实现Book中定义的方法
public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; }
实现构造函数
public Book() { } protected Book(Parcel in) { name = in.readString(); price = in.readInt(); }
实现Creator
public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } };
实现writeToParcel方法
@Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(price); }
因为在BookManager的方法中,用了out tag修饰参数Book,所以必须实现readFromParcel
/** * 参数是一个Parcel,用它来存储与传输数据 * @param dest */ public void readFromParcel(Parcel dest) { //注意,此处的读值顺序应当是和writeToParcel()方法中一致的 name = dest.readString(); price = dest.readInt(); }
-
定向TAG:
- in:Service端可以拿到客户端的原始数据,但是无法将修改后的数据返回给Client端
- out:Service端无法拿到客户端的原始数据,但是可以将修改后的数据返回给Client端
- inout:Service端可以拿到客户端的原始数据,并且可以将修改后的数据返回给Client端
- oneway
oneway可以用来修饰在interface之前,这样会造成interface内所有的方法都隐式地带上oneway;
oneway也可以修饰在interface里的各个方法之前。
被oneway修饰了的方法不可以有返回值,也不可以有带out或inout的参数。- 本地调用(同步调用):如果 oneway 用于本地调用,则不会有任何影响,调用仍是同步调用。
- 远程调用(异步调用):使用oneway时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用进行接收。
- oneway不能修饰有返回值的方法
- 如果远程调用中有耗时操作,最好使用oneway修饰,防止Client端出现ANR
- 采用oneway修饰调用方法,Service端通过Binder.getCallingPid方法获得客户端进程号始终是0,此时可以通过Binder.getCallingUid方法获得uid号,来区别客户端应用。
如何使用AIDL
-
编写AIDL文件:在项目文件目录app/src/main/下,新建一个aidl文件夹,与java文件夹平级,在aidl文件夹下
新建.aidl文件 -
编写AIDL文件:编写非默认支持数据类型的.aidl文件对应的.java文件,.java文件所在包路径要与.aidl文件相同。
-
编写AIDL文件:在客户端和服务端中都有我们需要用到的 .aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。通常我们会将 .aidl 文件和 .java 文件封装到一个静态jar包中,客户端和服务端将这个jar包编译到各自的应用中去,就实现了客户端和服务端中都有同样路径的.aidl 文件和其中涉及到的 .java 文件。
-
Service端实现远程调用办法:在Service中,定义.aidl的stub对象,并实现其中方法
public class MyService extends Service { private static final String TAG = "MyService"; private List<Book> mBooks; public MyService() { } @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate: "); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand: "); return super.onStartCommand(intent, flags, startId); } public BookManager.Stub mBookManager = new BookManager.Stub() { @Override public void addBook(Book book) throws RemoteException { Log.e(TAG, "addBook: "); book = new Book(); book.setName("111"); book.setPrice(222); mBooks.add(book); Log.e(TAG, "addBook: " + mBooks.toString()); } }; @Override public IBinder onBind(Intent intent) { return mBookManager; } }
-
Client端获得远程Service端的binder对象:在bindService绑定服务后,onServiceConnected回调中,以Ibinder参数通过Stub.asInterface方法获得Service端的代理binder对象
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private BookManager mBookManager = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d(TAG, "onClick: "); } }); } @Override protected void onResume() { super.onResume(); Intent intent = new Intent(); intent.setAction("com.example.ipcserver.MyService"); intent.setPackage("com.example.ipcserver"); boolean b = bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); Log.e(TAG, "onStart: " + b ); Log.e(TAG, "onStart: "+ "链接成功" ); } @Override protected void onStart() { super.onStart(); } @Override protected void onStop() { super.onStop(); unbindService(mServiceConnection); Log.e(TAG, "onStop: " + "解除链接" ); } private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e(TAG, "onServiceConnected: " + "ClientSuccess"); mBookManager = BookManager.Stub.asInterface(service); if (mBookManager != null) { addBook(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.e(TAG, "onServiceDisconnected: ClientError"); } }; public void addBook() { Book book = new Book(); book.setName("333"); book.setPrice(444); try { mBookManager.addBook(book); } catch (RemoteException e) { e.printStackTrace(); } } }
-
Client端通过Service端的binder对象,调用远程Service端中的方法:获得Service端的binder对象后,直接使用binder对象调用远程方法:
if (mBookManager != null) { addBook(); }
java代码分析
Sdk中会有名为“aidl”的可执行文件,在编译工程时,aidl会将.aidl文件编译成对应的.java文件,Client端和Service端调用的就是这些由.aidl生成的.java文件,通过分析.java文件可以对AIDL有更深入的了解。在.java文件中主要实现了Stub,分析Stub要点如下:
-
构造方法:构造方法中调用了binder中的attachInterface(IInterface owner, String descriptor)方法,其中DESCRIPTOR可以看做是进程的唯一标识,IInterface参数则把Stub自己传了进去,这个在后面asInterface()方法中会用到。
public Stub() { this.attachInterface(this, DESCRIPTOR); }
-
asInterface(android.os.IBinder obj):由于我们分析的是跨进程通信,所以obj.queryLocalInterface(DESCRIPTOR)返回“null”,最终返回Stub的Proxy方法。如果不是跨进程通信,则obj.queryLocalInterface(DESCRIPTOR)返回不为“null”,返回Stub本身
public static com.test.ipcdemo.BookManager asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.test.ipcdemo.BookManager)) { return ((com.test.ipcdemo.BookManager)iin); } return new com.test.ipcdemo.BookManager.Stub.Proxy(obj); }
-
Stub的内部类Proxy类:获得全局对象mRemote, addBookIn代码分析如下:代理类实现了功能方法的接口,这样就保证了和Stub类具有了相同的功能,只有这样才能代理Stub行使相同的功能。主要看里面方法的实现,这这个例子中,实现了addBookIn方法,把参数封装到了Parcel 中通过binder传递给了远程的服务。可以看出绑定远程服务后,要调用远程服务的方法是通过执行这个代理类中的对应方法,该方法再通过 mRemote.transact(Stub.TRANSACTION_addBookIn, _data, _reply, 0)把数据传递到远程服务,等远程服务执行完后再通过代理 _result = _reply.readInt();获取到结果返回给我们的程序的。其中Stub.TRANSACTION_addBookIn是这个方法的唯一标识,是告诉远程进程我要调用哪个方法。
总结起来这个代理类就是负责本地进程和远程进程之间的数据封装和传递(调用binder传递数据)以及解析结果的。
addBookOut、addBookInout解析与addBookIn相同。private static class Proxy implements com.test.ipcdemo.BookManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } } private static class Proxy implements com.test.ipcdemo.BookManager { @Override public com.test.ipcdemo.Book addBookIn(com.test.ipcdemo.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); com.test.ipcdemo.Book _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION__addBookIn, _data, _reply, 0); _reply.readException(); if ((0!=_reply.readInt())) { _result = com.test.ipcdemo.Book.CREATOR.createFromParcel(_reply); } else { _result = null; } } finally { _reply.recycle(); _data.recycle(); } return _result; }
-
Stub中的onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags):这个方法的参数和上面提到的代理发送数据 mRemote.transact(Stub.TRANSACTION_addBookIn, _data, _reply, 0)的参数一一对应,code指对应的要调用的add方法的唯一标识,data里面存放着要执行的add方法需要的参数,reply用来存储add方法执行完后要返回的结果。在onTransact函数中,会根据方法标识Stub.TRANSACTION_XXX来进行Service端的操作。在下面onTransact的三个方法中,可以看到Service中的方法是何时被调用到的,以及tag为in、out和inout时,对data和reply不同的处理方法。
case TRANSACTION_addBookIn: { data.enforceInterface(DESCRIPTOR); //很容易看出来,_arg0就是输入的book对象 com.test.ipcdemo.Book _arg0; //从输入的_data流中读取book数据,并将其赋值给_arg0 if ((0 != data.readInt())) { _arg0 = com.test.ipcdemo.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } //在这里才是真正的开始执行实际的逻辑,调用服务端写好的实现 _arg0 = this.addBookIn(_arg0); //执行完方法之后就结束了,没有针对_reply流的操作,所以客户端不会同步服务端的变化 reply.writeNoException(); return true; }
问题
-
在进行AIDL的build中会出现异常,导致打包不成功,添加如下配置:
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
-
在app项目中是无法使用makejar工具的,在Android Library下可以
-
makejar的使用:
task makeJar(type: Copy) { delete 'build/outputs/datamining_sdk.jar' from('build/intermediates/packaged-classes/debug/') into('build/outputs/') include('classes.jar') rename ('classes.jar', 'datamining_sdk.jar') } makeJar.dependsOn(build)
安卓11连接AIDL
在client端AndroidManifest.xml 添加queries包 开放的包
<queries>
<package android:name="com.android.myservice" />
</queries>