通常情况下,Android的进程之间的内存并不能共享, 所以Android 的一个进程通常不能访问另一个进程的内存。那么,要实现IPC(跨进程通信)就要使用到一些看似特殊的方式了,总的来说就是Android的四大组件。但对于Activity, ContentProvider, BroadcastReceiver而言, 用作跨进程只是实现了单向的数据操作,而并不能进行方法调用(AIDL的接口调用)。而通过bind service, 使用AIDL可以进行相互之间跨进程的方法调用和数据传递。而通过AIDL的形式传递对象时,必须要让对象对应的类支持Parcelable接口。
Android进程间通信(二)
什么是Parcelable
在了解Parcelable协议之前,先解释一下Parcel。
官方文档
按照文档中的解释, Parcel是一种可以通过IBinder传递的消息(数据和对象引用)容器. 一个Parcel可以包含IPC通信时,另一端能够去扁平化的扁平化数据和一个实时的IBinder,这个IBinder将会导致在通信的另一端接收到一个与Parcel中的原始Ibinder相连接的IBinder代理。
注意:Parcel并不是一个通用的序列化机制。这个类是为高性能的IPC传输而设计的,就其本身而言,并不适合持久化存储, 任意改变其中数据的底层实现都将会导致数据的不可读。
Parcel的API是为了围绕解决读写不同类型的数据。主要包括6大类 Primitives(基类), Primitive Arrays, Parcelables, Bundles, Active Objects(Binder, ParcelFileDescriptor), Untyped Containers(容器类)。
- Parcelable
继承了此接口的类的示例可以从Parcel 写入和存储。实现此接口的类必须定义一个非空的静态变量 CREATOR, 该变量实现了Parcelable.Creator接口。
//writeToParcel(...)的flag标志位, 被写入的数据是其他函数的运行结果, 会阻止资源的释放
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
//writeToParcel(...)的flag标志位, 标示父对象将会管理通常从内部类复制的重复数据
public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;
//文件描述符标志
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
//描述Parcelable中特殊对象的种类, 通常为0, 但包含文件描述符时,应返回 CONTENTS_FILE_DESCRIPTOR
public int describeContents();
//在dest中扁平化这个对象
public void writeToParcel(Parcel dest, int flags);
//必须实现的静态变量CREATOR, 用于丛Parcel中创建Parcelable实例
public interface Creator {
//创建实例
public T createFromParcel(Parcel source);
//创建一组新的Parcelable类数组
public T[] newArray(int size);
}
public interface ClassLoaderCreator extends Creator {
//特殊的CREATOR,允许接收ClassLoader
public T createFromParcel(Parcel source, ClassLoader loader);
}
- PaecelableCompat
Pracelable的向下兼容类,为了更好的支持低版本, 以API13为分界线(support-v4-25.2.0), 创建一个兼容性更好的CREATOR, APi13以下 和之后的两个版本的主要差别就在于ClassLoader。
- Parcelable和Serializable的区别
Serializable是JDK提供的一种序列化方式,而Parcelable则是Android为解决IPc而提供的一种序列方式,它相对于Serializable而言,具有更高的性能,但实现更加麻烦, 而且不适合持久化存储(原因上文有提到, Parcel对data实现有严格要求,改变任意的实现都可能导致之前的存储不可读。 可在下例中找到具体解释)。
NewsEntity.ava
package th.how.bean;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Keep;
/**
* Created by me_touch on 2017/8/16.
*注解是因为使用了GreenDao, 可以忽略
*/
@Entity
public class NewsEntity{
@Id
int id;
String title;
@Keep
public NewsEntity(int id, String title){
this.id = id;
this.title = title;
}
@Generated(hash = 2121778047)
public NewsEntity() {
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
@Override
public String toString() {
return "id = " + id + ", title = " + title;
}
}
NewsEntityParcel.java
package th.how.bean;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
/**
* Created by me_touch on 2017/9/7.
* Parcel 实现类, 实现Parcelable接口, 读取和写入的顺序要保持一致,否则有可能导致不可测的结果
*/
public class NewsEntityParcel extends NewsEntity implements Parcelable{
//如果改变读写顺序,会导致原存储数据变为不可读状态
public NewsEntityParcel(Parcel in){
this.id = in.readInt();
this.title = in.readString();
}
public NewsEntityParcel(int id, String title){
this.id = id;
this.title = title;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(title);
}
public void readFromParcel(Parcel dest){
this.id = dest.readInt();
this.title = dest.readString();
}
//通过ParcelableCompat 创建CREATOR, 基于兼容性考虑
public static Parcelable.Creator CREATOR = ParcelableCompat.newCreator(
new ParcelableCompatCreatorCallbacks() {
@Override
public NewsEntityParcel createFromParcel(Parcel in, ClassLoader loader) {
return new NewsEntityParcel(in);
}
@Override
public NewsEntityParcel[] newArray(int size) {
return new NewsEntityParcel[size];
}
});
}
AIDL的概念及语法
官方文档
概念
根据文档所述,AIDL是用来简化将对象编组成跨越边界的对象这一繁杂过程的。AIDL文件的编写遵循Java简单语法。AIDL的接口通过1个或多个方法声明。其与普通的接口类就语法而言,区别主要在于以下几点
- AIDL 只支持方法,也即是说在AIDL中不能定义静态变量
- AIDL有自己的关键词。
- 在Java1.8中,接口可以包含实现的方法,而AIDL中不能。
- 不能使用权限修饰符,以及final, static 等
- 必须显式的使用import
关键词
oneway 通常情况下client通过AIDL的方式调用service方法时, client 会进入阻塞状态,等待service对应的方法执行完成。然而通过oneway关键词修饰的方法,就可以避免被调用时阻塞客户端。oneway可以用于修饰interface 和方法, 修饰interface时,相当于在所有声明的方法前加oneway关键词。注意 oneway 不能修饰有返回值的方法。
in, out, inout 用于标记非原语参数的数据走向, 注意所有原语参数默认并且只能为in。in表示数据从客户端流向服务端,而out表示数据从服务端流向客户端, inout则是双向的(客户端和服务端是相对的, 并不绝对)。
如果设置错误,有可能导致获得的对象不为空,但值却是空的,当然也不能在并没有必要的情况下,直接设置成inout,这会导致严重的开销。什么时候用in, 什么时候用out? 当仅需要把客户端的值传递给服务端时用in, 仅需要在服务端完成赋值,再传递给客户端时用out
传递对象
除了对象对应的类需要实现Parcelable方法外,还 需要在aidl文件中声明对应的类为Parcelable, 用以查找并识别该类实现了Parcelable方法。
一个例子
具体方式: 在module目录下,与java同级的目录下建立一个aidl目录,然后建立一个包名与需要传递的对象对应的类的包名相同的包,如NewsEntityParcel.aidl所示, 如果不需传递, 则忽略掉这一步。创建新的aidl文件以定义需要调用的方法,需要声明所在的包,如IMyAidlInterface.aidl, IClientInterface.aidl, 然后编译,生成对应的接口文件(build/generated/source/aidl)。
NewsEntityParcel.aidl
package th.how.bean;
parcelable NewsEntityParcel;
IMyAidlInterface.aidl
package th.how.ipc;
import th.how.bean.NewsEntityParcel;
import th.how.ipc.IClientInterface;
interface IMyAidlInterface {
NewsEntityParcel getData(int id, String title, in NewsEntityParcel parcel);
void register(IClientInterface callback);
void unRegister(IClientInterface callback);
}
IClientInterface.aidl
package th.how.ipc;
import th.how.bean.NewsEntityParcel;
interface IClientInterface {
void callbackClient(in NewsEntityParcel parcel);
}
分析一下AIDL文件经在编译后生成的文件, 以IMyAidlInterface.aidl为例
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: G:\\2SelfWork\\HowApp\\app\\src\\main\\aidl\\th\\how\\ipc\\IMyAidlInterface.aidl
*/
package th.how.ipc;
public interface IMyAidlInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements th.how.ipc.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = "th.how.ipc.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* 将IBinder对象转化为对应的IMyAidlInterface
* 方式:从IBinder中根据DESCRIPTOR查询对应的IMyAidlInterface对象,如果没有,则
* 通过代理生成对应的对象
*/
public static th.how.ipc.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof th.how.ipc.IMyAidlInterface))) {
return ((th.how.ipc.IMyAidlInterface)iin);
}
return new th.how.ipc.IMyAidlInterface.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;
}
//对应的getData(int id, String title, in NewsEntityParcel parcel)方法
case TRANSACTION_getData:
{
data.enforceInterface(DESCRIPTOR);
//获取三个参数的值
int _arg0;
_arg0 = data.readInt();
java.lang.String _arg1;
_arg1 = data.readString();
th.how.bean.NewsEntityParcel _arg2;
if ((0!=data.readInt())) {
_arg2 = th.how.bean.NewsEntityParcel.CREATOR.createFromParcel(data);
}
else {
_arg2 = null;
}
//在Stub对象中调用该方法
th.how.bean.NewsEntityParcel _result = this.getData(_arg0, _arg1, _arg2);
reply.writeNoException();
//在reply中写入返回值
if ((_result!=null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
case TRANSACTION_register:
{
data.enforceInterface(DESCRIPTOR);
th.how.ipc.IClientInterface _arg0;
_arg0 = th.how.ipc.IClientInterface.Stub.asInterface(data.readStrongBinder());
this.register(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_unRegister:
{
data.enforceInterface(DESCRIPTOR);
th.how.ipc.IClientInterface _arg0;
_arg0 = th.how.ipc.IClientInterface.Stub.asInterface(data.readStrongBinder());
this.unRegister(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements th.how.ipc.IMyAidlInterface
{
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 th.how.bean.NewsEntityParcel getData(int id, java.lang.String title, th.how.bean.NewsEntityParcel parcel) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
th.how.bean.NewsEntityParcel _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(id);
_data.writeString(title);
if ((parcel!=null)) {
_data.writeInt(1);
parcel.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_getData, _data, _reply, 0);
_reply.readException();
if ((0!=_reply.readInt())) {
_result = th.how.bean.NewsEntityParcel.CREATOR.createFromParcel(_reply);
}
else {
_result = null;
}
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void register(th.how.ipc.IClientInterface callback) 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.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_register, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void unRegister(th.how.ipc.IClientInterface callback) 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.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_unRegister, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_register = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_unRegister = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
public th.how.bean.NewsEntityParcel getData(int id, java.lang.String title, th.how.bean.NewsEntityParcel parcel) throws android.os.RemoteException;
public void register(th.how.ipc.IClientInterface callback) throws android.os.RemoteException;
public void unRegister(th.how.ipc.IClientInterface callback) throws android.os.RemoteException;
}
生成的 IMyAidlInterface接口继承自IIterface接口
package android.os;
//Binder接口的基类
public interface IInterface
{
//通过与这个接口的关联关系检索Binder对象,为了代理对象能够返回正确的值,必须使用该方法获取,而不是直接转型
public IBinder asBinder();
}
接口内部,定义了一个继承自Binder并且实现IMyAidlInterface接口的抽象类Stub, 实现了返回接口对象方法,IBinder对象方法以及onTransact方法, 以及内部类Proxy。
- onTransact(...)方法实现了对aidl文件中定义的方法的调用, 注释中解释了其中一个方法,其余类似。
- Proxy 在代理类中实现了aidl文件中定义的方法以及调用