目录
android 加入AIDL进行底层通讯
Android接口定义语言aidl通信简单理解
Serializable和Parcelable区别:
简单客户端和服务端demo:
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 文件中声明:
服务端实现了接口,在 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 ,这个不写的话我们前面做的工作都是无用功:
到这里我们的服务端代码就编写完毕了,如果你对里面的一些地方感觉有些陌生或者根本不知所云的话,说明你对 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