跨进程通信 - AIDL 使用 & 浅析

一、前言

相信小伙伴们一听到跨进程通信就会想到 AIDL、Binder、客户端、服务端、等等傻傻分不清楚、本篇会先讲解关于 AIDL 的相关内容,下一篇讲解关于 Binder 及其他的相关知识。

二、AIDL 的使用

(由于 AIDL 涉及到客户端及服务端,为了方便理解,因此我对项目分包处理,方便理解)
1. 新建一个bean 类,实现 Parcelable 序列化接口,Parcelable 与 Serializable 两个不清楚的可以看我文章:

public class Person implements Parcelable {

    private String name;
    private int grade;

    public Person(String name, int grade) {
        this.name = name;
        this.grade = grade;
    }

    protected Person(Parcel in) {
        this.name = in.readString();
        this.grade = in.readInt();
    }

    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(name);
        dest.writeInt(grade);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", grade=" + grade +
                '}';
    }
}

此时目录结构为:

目录结构
2. 接着新建一个AIDL文件:

创建AIDL文件
新建AIDL
此时提示我们新建我们的AIDL文件名为:IMyAidlInterface,我们先点 Finish 确认,完成之后的代码为:

// IMyAidlInterface.aidl
package com.example.jackeyservice;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

然后我们再修改文件名为:Person.AIDL,同时修改代码为:

package com.example.jackeyservice;

parcelable Person;

3. 新建AIDL 文件,此时文件名为:JackAidl,在这一步直接在创建界面命名即可:代码如下

package com.example.jackeyservice;

import com.example.jackeyservice.Person;

interface JackAidl {
    void addPerson(in Person person);
    ListgetPersonList();
}

4. 完成了以上步骤后,Rebuild 一下整个项目(再次提醒,注意检查代码中的包名,否则会Rebuild失败),完成后的目录结构如下:

项目目录
可以看到生成了一个和我们新建的 JackAidl 相同名字的接口文件,后面会重点讲解。
5. 在 jackeyService 包下新建 JackeyLocalService 文件:

public class JackeyLocalService extends Service {

    private String TAG = "service";

    private List personList = new ArrayList<>();

    private Binder jackBinder = new JackAidl.Stub() {

        @Override
        public void addPerson(Person person) throws RemoteException {
            personList.add(person);
        }

        @Override
        public List getPersonList() throws RemoteException {
            return personList;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind Service Success");
        return jackBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate Service Success");
    }
}

同时在 manifest 文件中注册我们的服务:


6. 修改 MainActivity 文件,里面只有一个按钮,用来添加数据:

public class MainActivity extends AppCompatActivity {

    private JackAidl jackAidl;

    private List personList;

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

        initView();
        //启动服务
        bindService();
    }

    private void initView() {
        findViewById(R.id.btn_bind).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    //添加数据
                    jackAidl.addPerson(new Person("aa", 1));
                    personList = jackAidl.getPersonList();
                    Log.e("---", personList.toString());
                } catch (Exception e) {
                    Log.e("---", e.toString());
                }
            }
        });
    }

    private void bindService() {
        Intent intent = new Intent(this, JackeyLocalService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }


    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e("--client--", "onServiceConnected: success");
            jackAidl = JackAidl.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e("--client--", "onServiceDisconnected: success");
            jackAidl = null;
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

最后看一下整个项目的目录:

项目目录
运行一下效果如下:
运行效果

看到这个效果说明大功告成,接下来来分析下各部分代码。

AIDL 解析

1. AIDL 接口
在第一步新建的 Person 这个 bean 类 实现了 Parcelable 序列化接口,新建的 Person.AIDL 是这个 bean 类在 AIDL 中的声明,JackAidl.aidl 是我们定义的一个接口,里面有两个方法 getPersonList和 addPerson,其中 getPersonList 用于从远程服务端获取用户列表,addPerson用于往用户列表里添加一本书。这里需要注意的是,尽管 Person 类已经和 JackAidl 位于相同的包中,但是在 JackAidl 中仍然要导入 Person 类,这就是 AIDL 的特殊之处。

在 AIDL 中并不是所有的数据类型都可以使用,支持的数据类型如下:

  • Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char 。
  • String 和 CharSequence 。
  • List:只支持 ArrayList ,且里面每个元素的类型都必须能够被 AIDL 支持。
  • Map:只支持 HashMap,且里面每个 Key 和 Value 的类型都必须能够被 AIDL 支持。
  • Parcelabel:所有实现了 Parcelabel 接口的对象。
  • AIDL:所有的 AIDL 接口本身也可以在 AIDL 文件中使用。

需要注意的是,其中 Parcelabel 和 AIDL 对象都必须显式的添加进来,不管它们是否和当前这个 AIDL 文件位于同一个包内。另一个需要注意的是,如果 AIDL 文件中用到了自定义的 Parcelabel 对象,那么必须新建一个和它同名的 AIDL 文件。比如在上面的 JackAidl.aidl 中用到了 Person.java 这个类,所以必须创建 Person.aidl,Person.aidlPerson.java 类在 AIDL 中的声明

package com.example.jackeyservice;

parcelable Person;

AIDL中每个实现了 Parcelabel 接口的类都要按如上方式创建相应的 AIDL 文件并声明那个类为 parcelabel(注意是小写)。此外,AIDL中除了基本数据类型,其他类型的参数都必须标上方向:

  • in:输入型参数
  • out:输出型参数
  • inout:输入输出型参数

要根据实际需要去指定参数方向,不能一概使用 out 或 inout,因为在底层实现是有开销的。
2. AIDL 生成的 Java 类文件

public interface JackAidl extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.example.jackeyservice.JackAidl {
        //用于判断服务端 & 客户端是否是同一进程的标志(Binder的唯一标识,一般用当前Binder的类名表示)
        private static final java.lang.String DESCRIPTOR = "com.example.jackeyservice.JackAidl";

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

        /**
         * 将 Binder 对象转换成客户端所需要AIDL接口对象
         */
        public static com.example.jackeyservice.JackAidl asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            //如果是同一进程则返回当前对象
            if (((iin != null) && (iin instanceof com.example.jackeyservice.JackAidl))) {
                return ((com.example.jackeyservice.JackAidl) iin);
            }
            //不同进程则 new 出 Proxy 对象
            return new com.example.jackeyservice.JackAidl.Stub.Proxy(obj);
        }

        //返回当前 Binder 对象
        @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 {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_addPerson: {
                    data.enforceInterface(descriptor);
                    com.example.jackeyservice.Person _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.example.jackeyservice.Person.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addPerson(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getPersonList: {
                    data.enforceInterface(descriptor);
                    java.util.List _result = this.getPersonList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.example.jackeyservice.JackAidl {
            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 void addPerson(com.example.jackeyservice.Person person) 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 ((person != null)) {
                        _data.writeInt(1);
                        person.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                     //客户端通过 transact 调用到 服务端的对应方法
                    mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.util.List getPersonList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                     //客户端通过 transact 调用到 服务端的对应方法
                    mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.example.jackeyservice.Person.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        //声明了两个整型的 id 分别用于标识这两个方法,这两个 id 用于标识在 transact 过程中客户端所请求的到底是哪个方法
        static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    //添加用户
    public void addPerson(com.example.jackeyservice.Person person) throws android.os.RemoteException;
    //获取用户
    public java.util.List getPersonList() throws android.os.RemoteException;
}

JackAidl 继承了 IInterface ,本身也是一个接口。所有可以在 Binder 中传输的接口都必须继承 IInterface 接口。这个类刚开始看起来逻辑混乱,但是实际上还是很清晰的,结构也很简单,通过它我们可以清楚地了解到 Binder 的工作机制。

1. 我们先看到最后,声明了前面创建的Jackey.aidl 文件里的两个方法,同时它还声明了两个整型的 id 分别用于标识这两个方法,这两个 id 用于标识在 transact 过程中客户端所请求的到底是哪个方法。
2. 声明一个内部类 Stub,这个 Stub 就是一个 Binder 类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的 transact 过程,而当两者位于不同进程时,方法调用需要走 transact 过程,这个逻辑由 Stub 的内部代理类 Proxy 来完成。
因此,我们应该认识到,这个接口的核心实现就是它的内部类 Stub 和 Stub 的内代理类 Proxy,下面详细介绍针对这两个类的每个方法的含义。

asInterface(android.os.IBinder obj)

用于将服务端的 Binder 对象转换成客户端所需的 AIDl 接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于统一进程,那么此方法返回的就是服务端的 Stub 对象本身,否则返回的是系统封装后的 Stub.proxy 对象。

asBinder

此方法用于返回当前 Binder 对象。

Proxy#getPersonList

这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:

  1. 首先创建该方法所需要的输入型 Parcel 对象 _data、
  2. 输出型 Parcel 对象 _reply
  3. 返回值队形 List;
  4. 然后把该方法的参数信息写入 _data 中(如果有参数的话);
  5. 接着调用 transact 方法来发起 RPC(远程过程调用)请求,
  6. 同时当前线程挂起;
  7. 然后服务端的 onTransact 方法会被调用,直到 RPC 过程返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程的返回结果;
  8. 最后返回 _reply 中的数据。
Proxy#addPerson
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

这个方法运行在服务器中的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过 code 可以确定客户所请求的目标方法是什么,接着从 data 中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向 reply 中写入返回值(如果目标方法有返回值的话),onTransact 方法的执行过程就是这样的。需要注意的是,如果此方法返回 false ,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。
以下是 Binder 的工作机制:

binder.png
通过上面的分析和图解,大家应该已经了解了 Binder 的工作机制,但是需要注意的是:
首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在 UI 线程中发起此远程请求;其次,由于服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。

Binder 机制

从上述的分析过程来看,我们完全可以不提供 AIDL 文件即可实现 BInder,之所以提供 AIDL 文件,是为了方便系统为我们生成代码。系统根据 AIDL 文件生成 Java 文件的格式是固定的,我们可以抛开 AIDL 文件直接写一个 Binder 出来,二者的工作原理是完全一样的,所以说 AIDL 文件并不是实现 Binder的必需品,AIDL 文件的本质是系统为我们提供的一种快速实现 Binder 的工具。

你可能感兴趣的:(跨进程通信 - AIDL 使用 & 浅析)