Android 进程间通信(一)

通常情况下,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<T> {
    
    //创建实例    
    public T createFromParcel(Parcel source);
        
    //创建一组新的Parcelable类数组
    public T[] newArray(int size);
}


public interface ClassLoaderCreator<T> extends Creator<T> {
     
    //特殊的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<NewsEntityParcel> CREATOR = ParcelableCompat.newCreator(
            new ParcelableCompatCreatorCallbacks<NewsEntityParcel>() {
        @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个或多个方法声明。其与普通的接口类就语法而言,区别主要在于以下几点

  1. AIDL 只支持方法,也即是说在AIDL中不能定义静态变量
  2. AIDL有自己的关键词。
  3. 在Java1.8中,接口可以包含实现的方法,而AIDL中不能。
  4. 不能使用权限修饰符,以及final, static 等
  5. 必须显式的使用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文件中定义的方法以及调用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350