Android的IPC机制--实现AIDL的最简单例子(下)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq475703980/article/details/79823501

AIDL用法详解及源码分析

上一篇博客主要讲了如何使用AIDL, 这一篇我们将进入AIDL的身体,去分析源码。

上一章已经提到过了,AIDL也是基于Binder通信的, 其实Android的绝大部分跨进程通信都是依赖Binder来完成的(还有极少数是基于socket)。通过这一章的阅读,我们将基本掌握Binder通信的套路。

补充:oneway关键字

上一章的介绍中, 有一个关键字没有介绍,就是oneway, 本章将结合代码解释下它,首先看一下官方文档的解释:

The oneway keyword modifies the behavior of remote calls. When used, a remote
call does not block; it simply sends the transaction data and immediately returns. 
The implementation of the interface eventually receives this as a 
regular call from the Binder thread pool as a normal remote call. If oneway is
 used with a local call, there is no impact and the call is still synchronous.

大概翻译下:”oneway”修饰词是来形容远端服务的调用。当使用它的时候,远端服务不会阻塞,它只是发送数据并立即返回。接口的实现最终会收到一个来自远端Binder线程池的正确的回调。如果”oneway”被使用在本地调用,那么对调用没有任何影响,调用的方式还是同步的。

看起来还是有点不明白, 那就说大白话吧:使用oneway关键字的AIDL接口的所有方法调用都非阻塞式调用。什么意思呢?其实AIDL通信, 由于跨进程了,客户端进程发出请求后, 客户端进程的当前线程会挂起, 等到服务端返回后,才继续执行后面的代码。如果方法前使用了oneway关键字, 则线程不用挂起啦, 继续往下执行。这下明白了把。至于说为什么当前进程会挂起, 我们下面的源码分析会有所介绍

但是要注意,使用oneway关键字的这个方法的返回值为必须为void类型。否则会有编译异常提示:oneway method ‘xxx’ cannot return a value。

oneway关键字也可以直接用于定义方法的AIDL接口文件上, 但是, 这就要求所有方法返回值类型都为void才行, 否则也会报错。

如果没有跨进程,当前进程调用加了oneway关键字的方法, 也是没有任何影响的。

一、整理代码

我们知道, AIDL文件写好后, eclipse会在gen目录下, 根据AIDL文件,自动生成对应的java文件, AndroidStudio生成的目录在:app->build->generated->source->aidl->debug, 我们找到这个文件,下面分别贴一下 原始代码 和整理格式以后的代码:

原始代码 (扫一眼就行了,不用细看,重点在后面)

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\AndroidDevelopArt\\My_Chapter_2_aidl_client\\src\\com\\example\\my_chapter_2\\aidl\\BookManager.aidl
 */
package com.example.my_chapter_2.aidl;
//接口定义语言, 当然要定义接口啦

public interface BookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.my_chapter_2.aidl.BookManager
{
private static final java.lang.String DESCRIPTOR = "com.example.my_chapter_2.aidl.BookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.example.my_chapter_2.aidl.BookManager interface,
 * generating a proxy if needed.
 */
public static com.example.my_chapter_2.aidl.BookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.my_chapter_2.aidl.BookManager))) {
return ((com.example.my_chapter_2.aidl.BookManager)iin);
}
return new com.example.my_chapter_2.aidl.BookManager.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;
}
case TRANSACTION_getBooks:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.my_chapter_2.aidl.Book> _result = this.getBooks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.example.my_chapter_2.aidl.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.my_chapter_2.aidl.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.my_chapter_2.aidl.BookManager
{
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 java.util.List<com.example.my_chapter_2.aidl.Book> getBooks() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.my_chapter_2.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.my_chapter_2.aidl.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定

@Override public void addBook(com.example.my_chapter_2.aidl.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
//所有的返回值前都不需要加任何东西,不管是什么数据类型

public java.util.List<com.example.my_chapter_2.aidl.Book> getBooks() throws android.os.RemoteException;
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定

public void addBook(com.example.my_chapter_2.aidl.Book book) throws android.os.RemoteException;
}

嗯。。。原始代码。。。好像有点乱, 而且原始代码里, 对于类的引用, 全部是在代码里使用的全路径名,那我们整理一下格式, 把比较冗长的路径名去掉, 改为import类,整理为我们常见的代码格式:

整理后的代码

package com.example.my_chapter_2.aidl;

import java.util.List;
import com.example.my_chapter_2.aidl.Book;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;

public interface BookManager extends IInterface {

/***********************第一部分 start: Stub类***************************************/

public static abstract class Stub extends Binder implements
        BookManager {
    private static final String DESCRIPTOR = "com.example.my_chapter_2.aidl.BookManager";

    /** Construct the stub at attach it to the interface. */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an BookManager interface, generating a
     * proxy if needed.
     */
    public static BookManager asInterface(
            android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof BookManager))) {
            return ((BookManager) iin);
        }
        return new BookManager.Stub.Proxy(obj);
    }

    @Override
    public android.os.IBinder asBinder() {
        return this;
    }

    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {

        switch (code) {
        case INTERFACE_TRANSACTION: {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_getBooks: {
            data.enforceInterface(DESCRIPTOR);
            List<Book> _result = this.getBooks();
            reply.writeNoException();
            reply.writeTypedList(_result);
            return true;
        }
        case TRANSACTION_addBook: {
            data.enforceInterface(DESCRIPTOR);
            Book _arg0;
            if ((0 != data.readInt())) {
                _arg0 = Book.CREATOR.createFromParcel(data);
            } else {
                _arg0 = null;
            }
            this.addBook(_arg0);
            reply.writeNoException();
            return true;
        }
        }
        return super.onTransact(code, data, reply, flags);
    }

  static final int TRANSACTION_getBooks = (IBinder.FIRST_CALL_TRANSACTION + 0);
   static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
    /***********************第一部分 end: Stub类***************************************/



    /***********************第二部分 start: Proxy类***************************************/

    private static class Proxy implements
            BookManager {
        private IBinder mRemote;

        Proxy(IBinder remote) {
            mRemote = remote;
        }

        @Override
        public IBinder asBinder() {
            return mRemote;
        }

        public String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        // 所有的返回值前都不需要加任何东西,不管是什么数据类型

        @Override
        public List<Book> getBooks() RemoteException {
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            List<Book> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
                _reply.readException();
                _result = _reply
                        .createTypedArrayList(Book.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        // 传参时除了Java基本类型以及String,CharSequence之外的类型
        // 都需要在前面加上定向tag,具体加什么量需而定

        @Override
        public void addBook(Book book) throws RemoteException {
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                if ((book != null)) {
                    _data.writeInt(1);
                    book.writeToParcel(_data, 0);
                } else {
                    _data.writeInt(0);
                }
                mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    }


}

/***********************第二部分 end: Proxy类***************************************/


/***********************第三部分 start: 定义的方法接口***************************************/

public List<Book> getBooks() throws RemoteException;

public void addBook(Book book) throws RemoteException;

/***********************第三部分 end: 定义的方法接口***************************************/
}

经过整理以后的代码, 看起来就比较清晰了, 这就是我们AIDL文件中定义的接口:BookManager, 可以看到这个类继承了IInterface这个接口。然后内部代码大致分为三部分:

  • 第一部分:是一个内部类Stub,继承了 Binder 并实现了我们定义的BookManager接口
  • 第二部分:是Stub的一个内部类Proxy, 实现了我们定义的BookManager接口
  • 第三部分:就是我们在AIDL文件中定义的方法,由于可能跨进程, 所以都跑出了RemoteException异常

    结构很清楚了, 第三部分就是我们定义的方法, 实现了我们定义的接口BookManager, 自然就会实现我们的方法, 所以重点放到实现了BookManager接口的类上, 也就是 前两部分:Stub类和Proxy类。

    我们分了一、二、三部分, 只是为了让结构比较清晰,并且是AIDL代码原本可的顺序, 我们分析是, 倒着分析回去。 接着看第二部分的代码, Proxy这个类

二、初步分析Stub和Proxy这两个类

如果把跨进程通信比作打电话,打10086查话费的话, Proxy类就相当于我们的手机(客户端),Stub类就是10086(服务端)。手机(客户端)发出信号给10086(服务端), 然后我们(客户端)就需要需要等打通10086,然后10086帮我们查到话费,然后告诉我们(客户端)。 在打电话的这个过程中, 我们一直在等待,也干不了其他什么事,知道告诉我们话费是多少后,挂掉电话。 在Binder通信过程也是类似的,客户端发起跨进程通信请求后,当前线程会挂起, 知道等待服务端返回我们,然后客户端的这个线程才会继续往下执行。下面就开始看代码分析。

三、Proxy类–Binder通信的客户端

在跨进程通信,也就是Binder通信的过程中,Proxy就是我们的客户端,也就是Binder在本地的一个代理,这也是为什么它叫Proxy。我们看第二部分代码, Proxy内部有一个 IBinder对象:

private IBinder mRemote;
Proxy(IBinder remote) {
    mRemote = remote;
}

这个对象时在构造函数里初始化的,这个什么时候调用构造函数,我们后面会分析,接着看

当我们调用 getBooks()这个方法时, 我们进去看一下:

@Override
        public List<Book> getBooks() RemoteException {
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            List<Book> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
                _reply.readException();
                _result = _reply
                        .createTypedArrayList(Book.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

首先看到, 我们从Parcel池里面取了两个Parcel对象出来, 通过在_data里写入了我们的DESCRIPTOR,把这个值作为token。这个DESCRIPTOR其实就是我们的类名, 用来作为这个Binder类的唯一标识。 然后是 _reply实用类保存服务端返回给我们数据的。

特别注意这一行代码:

mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);

从这里开始, 就是真正发起了Binder通信请求, 我们的请求发出去以后,当前线程会挂起(如果没用oneway关键字), 然后会一直等待直到服务端返回,然后把数据写入 _reply中返回回去。

四、Stub类–Binder通信的服务端

Stub才是一个真正的Binder类, 它继承自Binder并实现了我们的BookManager接口,首先看它里面的第一个变量:

private static final String DESCRIPTOR = "com.example.my_chapter_2.aidl.BookManager";

DESCRIPTOR就是我们的全路径名, 它用来唯一标识一个Binder类, Binder必备属性,然后是构造方法:

public Stub() {
        this.attachInterface(this, DESCRIPTOR);
}

构造方法很简单,就是一个简单的初始化, this表示的是一个IInterface接口,所有通过Binder传输的接口都必须继承IInterface接口。然后是asInterface方法:

 public static BookManager asInterface(
            android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof BookManager))) {
            return ((BookManager) iin);
        }
        return new BookManager.Stub.Proxy(obj);
    }

这个发发发主要是判断是否在一个进程中,不在一个进程的话, 就在最后返回一个new BookManager.Stub.Proxy(obj)。上一篇博客中我们降到了AIDL的使用, 在客户端绑定服务端成功后, 会返回一个IBinder对象,然后我们会用到这个asInterface接口获取到一个本地的代理, 这里实际上最后得到的就是一个Proxy类, 它是服务端在客户端的代理对象。

下面就是Stub这个Binder类的核心方法了,onTransact() :

public boolean onTransact(int code, Parcel data,Parcel reply, int flags)
            throws RemoteException {
        switch (code) {
        case INTERFACE_TRANSACTION: {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_getBooks: {
            data.enforceInterface(DESCRIPTOR);
            ListBook> _result = this.getBooks();
            reply.writeNoException();
            reply.writeTypedList(_result);
            return true;
        }
        case TRANSACTION_addBook: {
            data.enforceInterface(DESCRIPTOR);
            Book _arg0;
            if ((0 != data.readInt())) {
                _arg0 = Book.CREATOR
                        .createFromParcel(data);
            } else {
                _arg0 = null;
            }
            this.addBook(_arg0);
            reply.writeNoException();
            return true;
        }
        }
        return super.onTransact(code, data, reply, flags);
    }

当客户端调用方法的请求过来以后, 可以看到, 我们对传来的方法有一个code, 通过这个code, 我们就知道 客户端想调用哪一个方法, 假如是调用了getBooks的话, 我们进入这一个case,
首先服务端会真正的去调用this.getBooks()方法, 然后把返回数据 _result写入 reply这个Parcel对象,返回给客户端, 然后客户端就可以收到数据啦~~

还有最后一部分, 标识每个方法的int值:

 static final int TRANSACTION_getBooks = (IBinder.FIRST_CALL_TRANSACTION + 0);
 static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);

可以很清晰的看到, 每个方法对应的int值的属性变量的命名规则为: TRANSACTION_+方法名, 这一点很重要, 从这里我们也可以得出,在AIDL中, 不能使用重载方法, 即方法名相同, 方法参数不同的方法。这一点和java规则不同。因为如果在AIDL文件中定义了方法名相同的方法, 那么这里的int值就会生成两个相同的变量名,对于服务端来说, 就会产生混淆,我服务端到底应该调用哪一个方法呢?

到这里, AIDL的使用就介绍得差不多了, Binder的应用层的流程我们也差不多熟悉了, 主要就是通过Binder的transact()实现通信。至于再往下冗长的c++代码,这个以后再分析吧。

五、AIDL总结

我们简单总结下:

  • AIDL通信生成的源码主要有三个类型: 一个BookManager接口,一个Binder类型的Stub类, 一个 Proxy类。
  • BookManager接口的作用就是定义方法接口, 其他两个类都通过继承这个接口,从而有相同的方法
  • Binder通信主要通过 transact 方法完成
  • 如果没有使用oneway关键字,客户端发出请求后, 当前线程会挂起, 知道服务端处理完毕并返回
  • 每一个Binder都有一个DESCRIPTOR, 用来唯一标识这个Binder类, 通常是全路径名(包名+类名)
  • 每个方法都有一个int值来进行标识, 这样,跨进程后,服务端才能通过int值来知道,客户端到底是想调用哪一个方法, int值的生成规则是:TRANSACTION_+方法名
  • AIDL中不能使用重载方法, 理由见上一条
六、延伸–通过AIDL看使用Binder跨进程通信的常见套路
Android源码中常见的Binder通信的场景

通过本文及前一篇博客的了解, 其实我们可以发现, AIDL的作用其实就是帮我们自动生成java代码, 从而简化了应用开发者的操作。 其实我们自己也可以直接按照这个格式写出java代码, 一样可以通信。而在Android系统里, 处处可以见到这中模式的跨进程java代码,代码机构和AIDL生成的java文件及其相似, 因为Binder通信, 基本上就是一个套路。

我们再回顾一遍,gen目录下生成的java文件, 主要包含三部分: 我们定义的接口BookManager, 然后是继承Binder类的Stub类, 然后是代理类Proxy。

那么在系统中,哪些时候回用到Binder通信呢, 请看下面的代码:

ActivityManager mA  ctivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

很熟悉吧, 我们经常通过getSystemService()这个方法区获取一些Manager, 其实就是通过Binder通信, 去各种服务进程中获取我们想要的信息, 这就会建立本地代理,和服务进程进行通信。

再比如我们启动一个Activity 那也是需要跨进程的, 需要把这个Activity放到记录到我们的任务栈, 就涉及到ActivityManagerService, ActivityManagerService是运行在System_server进程中, 所以我们在源码中可以看见这么几个类

IActivityManager接口
    ActivityManagerService   binder的服务端,实现了IActivityManager
    ActivityManagerProxy     binder的客户端, 实现了IActivityManager

这里只是一个简单的介绍,有兴趣的朋友可以自己查阅下相关资料,或者可以看看我的另一篇博客一张图看懂Activity启动流程 , 里面有一些相关的资料。

后续还会继续介绍下AIDL的高级用法。

很晚了, 睡觉去了, 喜欢的朋友麻烦点个赞吧~~

展开阅读全文

没有更多推荐了,返回首页