AIDL用法与分析

目录

  1. AIDL用法
    服务端
    客户端
  2. AIDL分析
    2.1 AIDL文件结构
    2.2 一些概念
    2.3 AIDL的UML图
    2.4 AIDL工作过程
    2.5 AIDL注意事项
    2.6 定向 tag:in、out、inout
    2.7 一些想法

1. AIDL用法

  • 下面是一个 Book 的存取例子,客户端与服务端分别为两个 App
  • 服务端 Service 实现 addBook() 和 getBook() 的功能
  • 客户端保存用户输入的 Book 的 name 和 price 属性值,并传送到服务端进程进行保存

服务端

第一:定义AIDL接口

  • Book.java 只是一个实现了 parcelable 的普通 bean 类,有 name 和 price 两个属性
  • BookManager.aidl 定义了两个接口方法,addBook() 和 getBook()

第二:实现服务端Service

public class BookService extends Service {

    private List<Book> mBooks;

    //定义本地Binder对象
    private BookManager.Stub mBinder = new BookManager.Stub() {
        @Override
        public void addBook(Book book) throws RemoteException {
            add(book);
        }

        @Override
        public Book getBook(int index) throws RemoteException {
            return get(index);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBooks = new ArrayList<>();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //返回该Binder
        return mBinder;
    }

    public void add(Book book) {
        mBooks.add(book);
    }

    public Book get(int index) {
        if (index < 0 || index >= mBooks.size()) {
            return null;
        }
        return mBooks.get(index);
    }
}

客户端

第一:将服务端 aidl 文件夹及其下所有文件原封不动复制一份到客户端代码下

第二:实现客户端Activity

public class MainActivity extends AppCompatActivity {

    private EditText index;
    private EditText name;
    private EditText price;


    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //接收服务端返回的Binder
            mBinder = BookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private BookManager mBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        index = (EditText) findViewById(R.id.index);
        name = (EditText) findViewById(R.id.name);
        price = (EditText) findViewById(R.id.price);
    }

    public void register(View view) {
        Intent intent = new Intent();
        intent.setAction("com.qgstudio.server.BookService");
        intent.setPackage("com.qgstudio.server");
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    public void unregister(View view) {
        unbindService(conn);
    }

    public void getBook(View view) {
        try {
            int i = Integer.parseInt(index.getText().toString());
            Book book = mBinder.getBook(i);
            
            name.setText(book.getName());
            price.setText(book.getPrice()+"");

        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void setBook(View view) {
        try {
            Book book = new Book();
            
            String n = name.getText().toString();
            int p = Integer.parseInt(price.getText().toString());
            
            book.setName(n);
            book.setPrice(p);

            mBinder.addBook(book);

        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

2. AIDL分析

2.1 AIDL文件结构

这里就不看源码了,我们先来看看 AIDL 为我们生成的 Binder 实现类的文件结构

  • 很简单,就是一个 Stub 类,还有一个内部类 Stub.Proxy 类
  • 这里的 Stub 继承自 Binder 还实现了 BookManager 接口
  • 而 Stub.Proxy 就只实现了 BookManager 接口,但它有一个成员变量 mRemote(是 BinderProxy )

2.2 一些概念

现在我们来捋一捋该文件的一些类的概念及类之间的关系:

  • IBinder接口:代表了一种跨进程传输的能力,在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据
  • IInterface接口:代表 Server 所提供的功能或者说是服务,在 Server 中定义的功能接口都需要扩展这个接口
  • Stub:AIDL 中自动生成的类,继承自 Binder,同时实现了 IInterface 的接口,代表了可以即可以在进程间传递,同时又可以提供对应的服务的对象(角色:Binder 本地对象)
  • Stub.Proxy:Stub 类的一个内部类,拥有一个 mRemote 成员变量,代表 BinderProxy。在 Stub.asInterface() 中首先会调用 IBinder.queryLocalInterface(DESCRIPTOR) 去查找本地Binder对象,如果没有说明系统传递进来的 IBinder 是一个 BinderProxy 对象(因为 Binder.queryLocalInterface() 返回本地 Binder 而 BInderProxy.queryLocalInterface() 直接返回 null)。此时会创建一个 Stub.Proxy 对象并且将它的 mRemote 初始化为该 BinderProxy,然后返回给调用者

2.3 AIDL的UML图

对应到之前 Binder 所讲的通信模型:

  • Stub 就是服务端进程提供的 Binder 本地对象
  • mRemote 就是在客户端的代理 BinderProxy

这里会发现还多了一个 Stub.Proxy 类:

  • 当你查看 Stub.Proxy 类的 addBook() 和 getBook() 方法会发现它调用的其实就是 mRemote 的 transact(code,data,reply,flags) 方法
  • mRemote 实际上是一个 BinderProxy, BinderProxy 是 Binder 的内部类,它并没有实现我们定义的 aidl 接口,但是定义了一个 transact() 方法,mRemote 用 code 这个值来区分到底调用的是 addBook() 还是 getBook()
  • 对于用户来说是不想写这么麻烦的代码的,那最好的方式就是将这个 BinderProxy 再包装一层而且还要是实现 aidl 接口的,具体细节就留给 Stub.Proxy 去实现,用户不用管了

2.4 AIDL工作过程

连接过程

  • Client 调用 bindService(),系统回调 Server 进程中 Service 的 onBind(),返回给 Client 进程一个对应的 BinderProxy, 这个是在 onServiceConnected() 回调方法中传递到 Client 进程的
  • Client 调用 Stub.asInterface() 方法将该 BinderProxy 包装成我们定义的 BookManager 接口实现类

请求过程

  • Client 将参数序列化到 _data 流中,通过 transact() 方法将它们传递给服务端,请求服务端调用指定方法,接着当前线程被挂起
  • 系统回调 Server 的 onTransact() 方法从 _data 流中反序列化出参数,根据方法 ID 调用对应的本地方法,并将需要回传的数据序列化到 _reply 流,传回客户端
  • Client 被唤醒,接收 _reply 数据流,并从中反序列出服务端传回来的结果,返回给函数调用者

2.5 AIDL注意事项

  1. 支持的数据类型
    • 基本类型,包括 int,long,boolean,float,double,String
    • Parcelable类型:所有实现了 Parcelable 接口的对象
    • List:只支持 ArrayList,里面每个元素都必须被 AIDL 支持
    • Map:只支持 HashMap,里面的每个元素都必须被 AIDL 支持,包括 key 和 value
    • 其他 AIDL 生成的接口
  • AIDL 文件中若使用到了除基本类型外的其他对象类型,必须新建一个和它同名的 AIDL 文件(且两者包名要相同),并在其中声明它为 Parcelable 类型
  • AIDL 文件中使用到的自定义对象必须显示的 import 进来,即使 .java 文件和 .adil 文件就在同一个包下
  1. 文件摆放问题
  • 建议是把所有和 AIDL 相关的类和文件,包括 .java 和 .aidl,全部放到同一个包中,这样就可以把整个包原封不动的复制到客户端中。为了能够使用 aidl 文件夹下的 .java 文件,需要在 app 的 build.gradle 下加上:
android {
    //...
    sourceSets {
        main {
            java.srcDirs += 'src/main/aidl'
        }
    }
}
  1. 定向 tag 问题
  • AIDL 中除了基本类型,其他类型的参数必须标上定向 tag 的方向(一般选择 in 就行)
  • AS 默认生成的 Parcelable 类对象只支持 in 的定向 tag。若要能够使用 out 或 inout,还要手动实现 readFromParcel() 方法(这个方法没有在 Parcelable 接口里):
//定义为 out 和 inout 的参数对象在服务端被改变后,
//客户端的该对象也会被改变,这与此方法相关
public void readFromParcel(Parcel dest) {
    //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
    //对象在服务端的变化反作用与客户端
    name = dest.readString();
    price = dest.readInt();
}
  1. 客户端将对象传递给服务端,在服务端反序列化是调用了 CREATOR 的 createFromParcel() 方法,此方法中是先 new 了一个新对象,接着从反序列化流中读出属性数据再赋值给该新对象的属性,也就是说客户端传递的对象和服务端获得的对象并不是同一对象

  2. 客户端调用 AIDL 方法时当前线程会被挂起,若服务端执行比较耗时,会导致 ANR。此时应该开启另一个线程来调用这些方法

  3. 服务端的 AIDL 方法是运行在 Binder 线程池中的,本身可以执行耗时操作(不应该在里面再启动线程)并且需要处理线程同步问题

  4. 若客户端传给服务端的是一个 AIDL 类型的 Listener(或叫 Binder),则形成双向 IPC,服务端可回调客户端方法,但也是执行在客户端的 Binder 线程池中,也就是说客户端中被回调的方法若要更新 UI,需要使用 Handle 切换到主线程

2.6 定向 tag:in、out、inout

AIDL 中定向 tag 表示数据的流向,in 表示数据只能由客户端流向服务端,out 表示数据只能由服务端流向客户端,inout 表示数据能在两端之间传递。这里的数据指的是客户端在调用远程方法时传入的参数对象

  • in:客户端只会将参数对象入到 _data流中,但不会从 _reply 流中读出该对象在服务端的改变结果,因此该对象在服务端的变化不会影响到客户端
  • out:客户端不会将参数对象写到 _data 流中,在服务端会自己创建一个新的参数对象,并且会将对该对象的改变结果传回给客户端,客户端从 _reply 流中读出结果,并改变原参数对象
  • inout:客户端会将参数对象写到 _data 流中,服务端接收该对象,并且会将对该对象的改变结果传回给客户端,客户端也会从 _reply 流中读出结果,并改变原参数对象

2.7 一些想法

  • IPC 过程就是 Binder 驱动为 Client 和 Server 建立了两条通信通道 _data 和 _reply,双方将需要传递的参数序列化到 _data 流中,并且从 _reply 流中反序列化读取出结果
  • 在 Java 层传递对象最方便,而底层是 C++ 写的,数据要流过底层只能采取序列化方式。在高层的通信上采用了代理模式,给用户造成了 Binder 对象能够在进程间传递的假象

参考文章

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

推荐阅读更多精彩内容