为了使得一个程序能够在同一时间里处理许多用户的要求。即使用户可能发出一个要求,也肯能导致一个操作系统中多个进程的运行(PS:听音乐,看地图)。而且多个进程间需要相互交换、传递信息,IPC方法提供了这种可能。IPC方法包括管道(PIPE)、消息排队、旗语、共用内存以及套接字(Socket)。
Android中的IPC方式有Bundle、文件共享、Messager、AIDL、ContentProvider和Socket。
这次我们学习的是Android中的AIDL。
概述
===========
AIDL(Android接口描述语言)是一个IDL语言,它可以生成一段代码,可以是一个在Android设备上运行的两个进程使用内部通信进程进行交互。在Android上,一个进程通常无法访问另一个进程的内存。所以说,如果你想在一个进程中(例如在一个Activity中)访问另一个进程中(例如service)某个对象的方法,你就可以使用AIDL来生成这样的代码来伪装传递各种参数。
Calls made from the local process are executed in the same thread that is making the call. If this is your main UI thread, that thread continues to execute in the AIDL interface. If it is another thread, that is the one that executes your code in the service. Thus, if only local threads are accessing the service, you can completely control which threads are executing in it (but if that is the case, then you shouldn't be using AIDL at all, but should instead create the interface by implementing a Binder). 如果调用发生在本地进程的同一线程中。如果是UI线程,那么AIDL接口调用继续在该线程。如果是其他线程,那么服务代码也在该线程执行。因此如果使用本地线程访问服务,那么服务调用线程是完全可以控制的。(但是这种情况就没有必要使用AIDL,可以用实现一个继承Binder的接口)。
Calls from a remote process are dispatched from a thread pool the platform maintains inside of your own process. You must be prepared for incoming calls from unknown threads, with multiple calls happening at the same time. In other words, an implementation of an AIDL interface must be completely thread-safe. 远程服务调用会在一个线程池中维护着client端的调用,client必须准备好接受未知线程即将返回的结果,并且多个调用可能同时发生。换而言之,一个AIDL接口的实现必须是完全线程安全的。
代码语言:javascript复制The oneway keyword modifies the behavior of remote calls. When used, a remote call does not block; it simply sends the transaction data and immediately returns. The implementation of the interface eventually receives this as a regular call from the Binder thread pool as a normal remote call. If oneway is used with a local call, there is no impact and the call is still synchronous. "oneway"修饰词是来形容远端服务的调用。当使用它的时候,远端服务不会阻塞,它只是发送数据并立即返回。接口的实现最终会收到一个来自远端Binder线程池的正确的回调。如果"oneway"被使用在本地调用,那么对调用没有任何影响,调用的方式还是同步的。
//使用oneway关键字的AIDL接口的所有方法调用都非阻塞式调用,并且方法函数的返回值为void类型。
//否则会有编译异常提示:oneway method 'xxx' cannot return a value
语法
AIDL它和Java基本上类似,只是有一些细微的差别(PS:可能Google为了方便Android程序猿使用)。AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。重要的是必须导入所有非内置类型,哪怕是这些类型是在与接口相同的包中。
下边说说AIDL的一些特点:
- 通常引引用方式传递的其他AIDL生成的接口,必须要import 语句声明。
- Java编程语言的主要类型 (int, boolean等) —不需要 import 语句。
- 在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件中支持哪些数据类型呢? 如下所示: 1、基本数据类型(int,long,char,boolean,float,double,byte,short八种基本类型); 2、String和CharSequence; 3、List:只支持ArrayList,里面每个元素都必须能够被AIDL支持; 4、Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value; 5、Parcelable:所有实现了Parcelable接口的对象; 6、AIDL:所有的AIDL接口本身也可以在AIDL文件中使用; 以上6中数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内。
需要注意的地方: AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout; (PS:假若传递一个Book对象且没有加指向tag时,则会抛出
aidl.exe E 4928 5836 type_namespace.cpp:130] 'Book' can be an out type, so you must declare it as in, out or inout.
异常)
- in表示输入型参数(Server可以获取到Client传递过去的数据,但是不能对Client端的数据进行修改)
- out表示输出型参数(Server获取不到Client传递过去的数据,但是能对Client端的数据进行修改)
- inout表示输入输出型参数(Server可以获取到Client传递过去的数据,但是能对Client端的数据进行修改)。 更多tag相关的内容:AIDL源码解析in、out和inout
使用AIDL实现IPC
实现步骤 (官网AIDL样例)
代码语言:javascript复制// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service, to do evil things with it. */
int getPid();
/** Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
这是官网创建的一个简单的AIDL文件。
这次我们自己声明一个包含非默认支持类型的AIDL文件。
AIDL要跨进程通信,其所携带的数据也需要跨进程传输。所以我们首先需要自定自己想要传输的数据类必须其必须实现Parcelable接口从而可以被序列化。
为什么需要序列化呢,为什么不适用Serializable,不知道的同学可以看下这篇文章: Serializable和Parcelable的再次回忆
所以我们先创建需要传输的数据所对应的aidl文件,然后再相同目录下创建对应的Java类文件。这里可能有些同学会疑惑,不是直接创建Java类么。AIDL文件有两种类型,一种是我们上边定义的接口,而另外一种就是非常规类型的数据对象文件。即:如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。详细的使用我们看下边例子:
创建一个Book.aidl文件
在Android Studio的项目中先创建对应的aidl包,然后右击选择创建aidl文件,so easy。
代码语言:javascript复制// Book.aidl
package com.tzx.aidldemo.aidl;
// Declare any non-default types here with import statements
//所有注释掉的内容都是Android Studio帮你写的,但是我们不需要。
//我们创建的是aidl数据对象,所以我们只需写出parcelable 后面跟对象名。
//parcelabe前的字母‘p’是小写的哦~
parcelable Book;
//interface Book {
//
// /**
// * Demonstrates some basic types that you can use as parameters
// * and return values in AIDL.
// */
// void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
// double aDouble, String aString);
//}
在Book.aidl的包下创建Book.java类文件
代码语言:javascript复制public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
//从序列化后的对象中创建原始对象
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
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];
}
};
//返回当前对象的内容描述。如果含有文件描述符,返回1,否则返回0,几乎所有情况都返回0
@Override
public int describeContents() {
return 0;
}
//将当前对象写入序列化结构中,其flags标识有两种(1|0)。
//为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况下都为0.
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
@Override
public String toString() {
return "[bookId=" bookId ",bookName='" bookName "']";
}
}
在Android Studio中如果先创建Java类文件,然后创建AIDL文件则会提示命名重复,但顺序反过来就可以。
创建aidl接口文件IBookManager.aidl
代码语言:javascript复制// IBookManager.aidl
package com.tzx.aidldemo.aidl;
//通常引用方式传递自定义对象,必须要import语句声明
import com.tzx.aidldemo.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
这样所有aidl相关的文件就定义完了,我们可以写客户端和服务端了么。然而实际结果表明我们还是无法在客户端或服务费使用aidl类。在这里说一下其实aidl方式只不过是为我们提供模板自动创建aidl对应的Java类文件,只有生成了对应的Java文件之后我们才可以在客户端或服务端使用。Android studio中make一下当前的project就会在项目的app/build/source/aidl/包名/debug这个目录下生成对应的aidl类文件(PS:只有aidl接口文件才会生成java类文件)。
make的时候可能提示找不到对应的Book.java文件,我们可以在build.gradle文件中的android{}标签里面添加:
代码语言:javascript复制sourceSets{
main{
aidl.srcDirs = ['src/main/java']
}
}
这种情况只适合aidl类文件和对应的java类文件在同一个包下。
好了,现在所有的aidl文件都有了,我们开始写我们的服务交互了!!
服务端:
Service服务
代码语言:javascript复制/**
* Created by tanzhenxing
* Date: 2016/10/17.
* Description:远程服务
*/
public class BookManagerService extends Service {
//支持并发读写
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
//服务端定义Binder类(IBookManager.Stub)
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
并在Manifest文件中声明,将它放在一个新的进程中,这样方便我们演示跨进程通信。
代码语言:javascript复制<service android:name=".BookManagerService"
android:process=":server"/>
客户端:
代码语言:javascript复制/**
* Created by tanzhenxing
* Date: 2016/10/17.
* Description:主界面
*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText bookNameTV;
private Button bookAddTV;
private Button bookCountTV;
private TextView bookInfoTV;
private Intent bookManagerIntent;
private boolean mBound = false;
private IBookManager bookManager;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//客户端获取代理对象
bookManager = IBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onStart() {
super.onStart();
bookManagerIntent = new Intent(this, BookManagerService.class);
bindService(bookManagerIntent, mConnection, Context.BIND_AUTO_CREATE);
mBound = true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
private void initView() {
bookNameTV = (EditText) findViewById(R.id.book_name);
bookAddTV = (Button) findViewById(R.id.book_add);
bookCountTV = (Button) findViewById(R.id.book_count);
bookInfoTV = (TextView) findViewById(R.id.book_info);
}
private void initListener() {
bookAddTV.setOnClickListener(this);
bookCountTV.setOnClickListener(this);
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
mBound = false;
unbindService(mConnection);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.book_add:
addBook();
break;
case R.id.book_count:
getBookList();
break;
}
}
private void addBook() {
if (bookManager != null && !TextUtils.isEmpty(bookNameTV.getText().toString())) {
Book book = new Book((int) System.currentTimeMillis(), bookNameTV.getText().toString());
try {
bookManager.addBook(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
public void getBookList() {
try {
if (bookManager != null) {
List<Book> list = bookManager.getBookList();
if (list != null && list.size() > 0) {
StringBuilder builder = new StringBuilder();
for (Book book : list) {
builder.append(book.toString());
builder.append('n');
}
bookInfoTV.setText(builder.toString());
} else {
bookInfoTV.setText("Empty~!");
}
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
运行结果
AIDLDEMO
解析aidl生成的java类
代码语言:javascript复制public interface IBookManager extends android.os.IInterface {
//根据aidl文件中定义的方法,进行接口声明
public java.util.List<com.tzx.aidldemo.aidl.Book> getBookList()
throws android.os.RemoteException;
//根据aidl文件中定义的方法,进行接口声明
public void addBook(com.tzx.aidldemo.aidl.Book book)
throws android.os.RemoteException;
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.tzx.aidldemo.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.tzx.aidldemo.aidl.IBookManager";
//定义方法执行code,与客户端同步
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION
0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION
1);
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.tzx.aidldemo.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.tzx.aidldemo.aidl.IBookManager asInterface(
android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) &&
(iin instanceof com.tzx.aidldemo.aidl.IBookManager))) {
return ((com.tzx.aidldemo.aidl.IBookManager) iin);
}
//生成代理对象
return new com.tzx.aidldemo.aidl.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public 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_getBookList: {
data.enforceInterface(DESCRIPTOR);
//调用服务端getBookList()
java.util.List<com.tzx.aidldemo.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.tzx.aidldemo.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.tzx.aidldemo.aidl.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//调用服务端addBook()
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.tzx.aidldemo.aidl.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.tzx.aidldemo.aidl.Book> getBookList()
throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.tzx.aidldemo.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//调用远程服务addBook()
mRemote.transact(Stub.TRANSACTION_getBookList, _data,
_reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.tzx.aidldemo.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.tzx.aidldemo.aidl.Book book)
throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//调用远程服务addBook
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
}
}
用关系图表示比较清楚些。。
IBookManager.java
每个文件结构我们都解析完了,那么aidl到底是怎么实现通信的呢,要让我们自己写一套类似于aidl的那么应该怎么去设计呢?
我们仿aidl画一幅结构图:
AIDL
根据上面这个图,我们就可以写出自己的aidl。
代码语言:javascript复制//方法接口
public interface IBookManager {
final int CAHAGE_MSG = 1;
Book change(Book book);
}
//实现方法的代理类
public class Proxy implements IBookManager{
private static IBinder mRemote;
public static Proxy asInterface(IBinder service) {
mRemote = service;
return new Proxy();
}
public Book change(Book book) {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInt(1);
book.writeToParcel(data, 0);
mRemote.transact(IBookManager.CAHAGE_MSG, data, reply, 0);
reply.readException();
if(0 != reply.readInt()) {
return Book.CREATOR.createFromParcel(reply);
}
} catch (RemoteException e) {
e.printStackTrace();
}finally {
data.recycle();
reply.recycle();
}
return null;
}
}
//Binder远端实现类
public class Stub extends Binder implements IBookManager {
@Override
public Book change(Book book) {
book.bookName = "Server";
return book;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case IBookManager.CAHAGE_MSG:
Book book = null;
if (0 != data.readInt()) {
book = Book.CREATOR.createFromParcel(data);
}
book = change(book);
reply.writeNoException();
reply.writeInt(1);
book.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
reply.writeParcelable(book, 0);
return true;
}
return super.onTransact(code, data, reply, flags);
}
}