android 加入AIDL进行底层通讯,Android接口定义语言aidl通信简单理解, 简单客户端和服务端demo,ipc,Serializable和Parcelable区别

android 加入AIDL进行底层通讯直接将aidl文件复制到main目录中,在sync和make project

生成的文件在 android 目录中,在java中在邮特殊符号的文件夹中能看到;

java文件将目录下的包直接复制到项目的java 目录中;进行调用;

AIDL重要的是通讯必须是报名是相同的;

Android接口定义语言aidl通信简单理解​AIDL:Android Interface Definition Language,即Android接口定义语言。

Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。

为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC(Remote Procedure Call Protocol)--远程过程调用协议)的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、BroadcastReceiver和ContentProvider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。

ipc:IPC是Inter-Process Communication的缩写,含义就是进程间通信或者跨进程通信

Serializable和Parcelable区别:Serializable(可串行化的)是Java中的序列化接口,其使用起来简单但是开销很大,在序列化和反序列化过程中需要大量的I/O操作。

而Parcelable(打包的)是Android中的序列化方式,因此更适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高。

两种AIDL文件:在我的理解里,所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。

注:所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用。

AIDL 实例

下面以实例代码演示一个 AIDL 的编写。

1.创建 AIDL

①创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化

package net.sxkeji.shixinandroiddemo2.bean;

import android.os.Parcel;

import android.os.Parcelable;

public class Person implements Parcelable {

private String mName;

public Person(String name) {

mName = name;

}

protected Person(Parcel in) {

mName = in.readString();

}

public static final Creator CREATOR = new Creator() {

@Override

public Person createFromParcel(Parcel in) {

return new Person(in);

}

@Override

public Person[] newArray(int size) {

return new Person[size];

}

};

@Override

public int describeContents() {

return 0;

}

@Override

public void writeToParcel(Parcel dest, int flags) {

dest.writeString(mName);

}

@Override

public String toString() {

return "Person{" +

"mName='" + mName + '\'' +

'}';

}

}

实现 Parcelable 接口是为了后序跨进程通信时使用。

关于 Parcelable 可以看我的这篇文章 Android 进阶6:两种序列化方式 Serializable 和 Parcelable。

注意 实体类所在的包名。

②新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件

在 main 文件夹下新建 aidl 文件夹,使用的包名要和 java 文件夹的包名一致:

先创建实体类的映射 aidl 文件,Person.aidl:

// Person.aidl

package net.sxkeji.shixinandroiddemo2.bean;

//还要和声明的实体类在一个包里

parcelable Person;

在其中声明映射的实体类名称与类型

注意,这个 Person.aidl 的包名要和实体类包名一致。

然后创建接口 aidl 文件,IMyAidl.aidl:

// IMyAidl.aidl

package net.sxkeji.shixinandroiddemo2;

// Declare any non-default types here with import statements

import net.sxkeji.shixinandroiddemo2.bean.Person;

interface IMyAidl {

/**

* 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)

*/

void addPerson(in Person person);

List getPersonList();

}

在接口 aidl 文件中定义将来要在跨进程进行的操作,上面的接口中定义了两个操作:

addPerson: 添加 Person

getPersonList:获取 Person 列表

需要注意的是:

非基本类型的数据需要导入,比如上面的 Person,需要导入它的全路径。

这里的 Person 我理解的是 Person.aidl,然后通过 Person.aidl 又找到真正的实体 Person 类。

方法参数中,除了基本数据类型,其他类型的参数都需要标上方向类型

in(输入), out(输出), inout(输入输出)

③Make Project ,生成 Binder 的 Java 文件

AIDL 真正的强大之处就在这里,通过简单的定义 aidl 接口,然后编译,就会为我们生成复杂的 Java 文件。

点击 Build -> Make Project,然后等待构建完成。

然后就会在 build/generated/source/aidl/你的 flavor/ 下生成一个 Java 文件:

现在我们有了跨进程 Client 和 Server 的通信媒介,接着就可以编写客户端和服务端代码了。

我们先跑通整个过程,这个文件的内容下篇文章介绍。

2.编写服务端代码

创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法;然后在 onBind() 中返回

创建将来要运行在另一个进程的 Service,在其中实现了 AIDL 接口中定义的方法:

public class MyAidlService extends Service {

private final String TAG = this.getClass().getSimpleName();

private ArrayList mPersons;

/**

* 创建生成的本地 Binder 对象,实现 AIDL 制定的方法

*/

private IBinder mIBinder = new IMyAidl.Stub() {

@Override

public void addPerson(Person person) throws RemoteException {

mPersons.add(person);

}

@Override

public List getPersonList() throws RemoteException {

return mPersons;

}

};

/**

* 客户端与服务端绑定时的回调,返回 mIBinder 后客户端就可以通过它远程调用服务端的方法,即实现了通讯

* @param intent

* @return

*/

@Nullable

@Override

public IBinder onBind(Intent intent) {

mPersons = new ArrayList<>();

LogUtils.d(TAG, "MyAidlService onBind");

return mIBinder;

}

}

上面的代码中,创建的对象是一个 IMyAidl.Stub() ,它是一个 Binder,具体为什么是它我们下篇文章介绍。

别忘记在 Manifest 文件中声明:

android:name="net.sxkeji.shixinandroiddemo2.service.MyAidlService"

android:enabled="true"

android:exported="true"

android:process=":aidl"/>

服务端实现了接口,在 onBind() 中返回这个 Binder,客户端拿到就可以操作数据了。

3.编写客户端代码

这里我们以一个 Activity 为客户端。

①实现 ServiceConnection 接口,在其中拿到 AIDL 类

private IMyAidl mAidl;

private ServiceConnection mConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

//连接后拿到 Binder,转换成 AIDL,在不同进程会返回个代理

mAidl = IMyAidl.Stub.asInterface(service);

}

@Override

public void onServiceDisconnected(ComponentName name) {

mAidl = null;

}

};

在 Activity 中创建一个服务连接对象,在其中调用 IMyAidl.Stub.asInterface() 方法将 Binder 转为 AIDL 类。

②接着绑定服务

Intent intent1 = new Intent(getApplicationContext(), MyAidlService.class);

bindService(intent1, mConnection, BIND_AUTO_CREATE);

要执行 IPC,必须使用 bindService() 将应用绑定到服务上。

注意:

5.0 以后要求显式调用 Service,所以我们无法通过 action 或者 filter 的形式调用 Service,具体内容可以看这篇文章 Android 进阶:Service 的一些细节。

③拿到 AIDL 类后,就可以调用 AIDL 类中定义好的操作,进行跨进程请求

@OnClick(R.id.btn_add_person)

public void addPerson() {

Random random = new Random();

Person person = new Person("shixin" + random.nextInt(10));

try {

mAidl.addPerson(person);

List personList = mAidl.getPersonList();

mTvResult.setText(personList.toString());

} catch (RemoteException e) {

e.printStackTrace();

}

}

总结

这篇文章介绍了 AIDL 的简单编写流程,其中也踩过一些坑,比如文件所在包的路径不统一,绑定服务收不到回调等问题。

到最后虽然跨进程通信成功,但是我们还是有很多疑问的,比如:

AIDL 生成的文件内容?

什么是 Binder?

为什么要这么写?

知其然还要知其所以然,这一切都要从 Binder 讲起,且听下一回合介绍。

代码地址

Thanks

《Android 开发艺术探索》

https://developer.android.com/guide/components/aidl.html

http://www.jianshu.com/p/b9b15252b3d6

http://rainbow702.iteye.com/blog/1149790

简单客户端和服务端demo:编写服务端代码

通过上面几步,我们已经完成了AIDL及其相关文件的全部内容,那么我们究竟应该如何利用这些东西来进行跨进程通信呢?其实,在我们写完AIDL文件并 clean 或者 rebuild 项目之后,编译器会根据AIDL文件为我们生成一个与AIDL文件同名的 .java 文件,这个 .java 文件才是与我们的跨进程通信密切相关的东西。事实上,基本的操作流程就是:在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。

接下来我直接贴上我写的服务端代码:

/**

* 服务端的AIDLService.java

*

* Created by lypeer on 2016/7/17.

*/

public class AIDLService extends Service {

public final String TAG = this.getClass().getSimpleName();

//包含Book对象的list

private List mBooks = new ArrayList<>();

//由AIDL文件生成的BookManager

private final BookManager.Stub mBookManager = new BookManager.Stub() {

@Override

public List getBooks() throws RemoteException {

synchronized (this) {

Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());

if (mBooks != null) {

return mBooks;

}

return new ArrayList<>();

}

}

@Override

public void addBook(Book book) throws RemoteException {

synchronized (this) {

if (mBooks == null) {

mBooks = new ArrayList<>();

}

if (book == null) {

Log.e(TAG, "Book is null in In");

book = new Book();

}

//尝试修改book的参数,主要是为了观察其到客户端的反馈

book.setPrice(2333);

if (!mBooks.contains(book)) {

mBooks.add(book);

}

//打印mBooks列表,观察客户端传过来的值

Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());

}

}

};

@Override

public void onCreate() {

super.onCreate();

Book book = new Book();

book.setName("Android开发艺术探索");

book.setPrice(28);

mBooks.add(book);

}

@Nullable

@Override

public IBinder onBind(Intent intent) {

Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));

return mBookManager;

}

}

整体的代码结构很清晰,大致可以分为三块:第一块是初始化。在 onCreate() 方法里面我进行了一些数据的初始化操作。第二块是重写 BookManager.Stub 中的方法。在这里面提供AIDL里面定义的方法接口的具体实现逻辑。第三块是重写 onBind() 方法。在里面返回写好的 BookManager.Stub 。

接下来在 Manefest 文件里面注册这个我们写好的 Service ,这个不写的话我们前面做的工作都是无用功:

android:name=".service.AIDLService"

android:exported="true">

到这里我们的服务端代码就编写完毕了,如果你对里面的一些地方感觉有些陌生或者根本不知所云的话,说明你对 Service 相关的知识已经有些遗忘了,建议再去看看这两篇博文:Android中的Service:默默的奉献者 (1),Android中的Service:Binder,Messenger,AIDL(2) 。

4.5,编写客户端代码

前面说过,在客户端我们要完成的工作主要是调用服务端的方法,但是在那之前,我们首先要连接上服务端,完整的客户端代码是这样的:

/**

* 客户端的AIDLActivity.java

* 由于测试机的无用debug信息太多,故log都是用的e

*

* Created by lypeer on 2016/7/17.

*/

public class AIDLActivity extends AppCompatActivity {

//由AIDL文件生成的Java类

private BookManager mBookManager = null;

//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中

private boolean mBound = false;

//包含Book对象的list

private List mBooks;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_aidl);

}

/**

* 按钮的点击事件,点击之后调用服务端的addBookIn方法

*

* @param view

*/

public void addBook(View view) {

//如果与服务端的连接处于未连接状态,则尝试连接

if (!mBound) {

attemptToBindService();

Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();

return;

}

if (mBookManager == null) return;

Book book = new Book();

book.setName("APP研发录In");

book.setPrice(30);

try {

mBookManager.addBook(book);

Log.e(getLocalClassName(), book.toString());

} catch (RemoteException e) {

e.printStackTrace();

}

}

/**

* 尝试与服务端建立连接

*/

private void attemptToBindService() {

Intent intent = new Intent();

intent.setAction("com.lypeer.aidl");

intent.setPackage("com.lypeer.ipcserver");

bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

}

@Override

protected void onStart() {

super.onStart();

if (!mBound) {

attemptToBindService();

}

}

@Override

protected void onStop() {

super.onStop();

if (mBound) {

unbindService(mServiceConnection);

mBound = false;

}

}

private ServiceConnection mServiceConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

Log.e(getLocalClassName(), "service connected");

mBookManager = BookManager.Stub.asInterface(service);

mBound = true;

if (mBookManager != null) {

try {

mBooks = mBookManager.getBooks();

Log.e(getLocalClassName(), mBooks.toString());

} catch (RemoteException e) {

e.printStackTrace();

}

}

}

@Override

public void onServiceDisconnected(ComponentName name) {

Log.e(getLocalClassName(), "service disconnected");

mBound = false;

}

};

}

同样很清晰,首先建立连接,然后在 ServiceConnection 里面获取 BookManager 对象,接着通过它来调用服务端的方法。

4.6,开始通信吧!

通过上面的步骤,我们已经完成了所有的前期工作,接下来就可以通过AIDL来进行跨进程通信了!将两个app同时运行在同一台手机上,然后调用客户端的 addBook() 方法,我们会看到服务端的 logcat 信息是这样的:

//服务端的 log 信息,我把无用的信息头去掉了,然后给它编了个号

1,on bind,intent = Intent { act=com.lypeer.aidl pkg=com.lypeer.ipcserver }

2,invoking getBooks() method , now the list is : [name : Android开发艺术探索 , price : 28]

3,invoking addBooks() method , now the list is : [name : Android开发艺术探索 , price : 28, name : APP研发录In , price : 2333]

客户端的信息是这样的:

//客户端的 log 信息

1,service connected

2,[name : Android开发艺术探索 , price : 28]

3,name : APP研发录In , price : 30