Android Framework:深入探索 AIDL 数据流动
发布日期:2021-04-30 21:10:04 浏览次数:85 分类:精选文章

本文共 7926 字,大约阅读时间需要 26 分钟。

AIDL 中传递非原语参数的数据流向问题解析

在 Android 的跨进程通信中,AIDL(Android Intentservice Definition Language)通过 Binder 实现数据的跨进程传递。其中,非原语参数的传递涉及到数据的方向标记(in、out、inout),这些标记决定了数据在传递过程中的流向和访问方式。以下将详细解析客户端通过 AIDL 传递非原语参数对象后,服务端是否会跟随客户端修改的数据变化。


数据方向标记的理解

  • in:表示参数是从客户端流向服务端。在这种模式下,客户端可以通过 AIDL 方法将对象传递给服务端,服务端可以接收并操作该对象。

  • out:表示参数是从服务端流向客户端。在这种模式下,服务端可以通过 AIDL 方法将对象传递给客户端,但客户端无法直接修改该对象的内部数据。

  • inout:表示参数可以双向流动。客户端可以通过 AIDL 方法修改该对象的内部数据,服务端也可以接收并操作该对象。


  • 数据流向的验证

    为了验证数据流向的猜想,我们设计了一个简单的 AIDL 接口,并通过 Java代码验证不同数据方向标记的效果。

    JavaBean 接口定义:
    public class Person implements Parcelable {
    private String name;
    private String age;
    public Person() {
    }
    public Person(String name, String age) {
    this.name = name;
    this.age = age;
    }
    protected Person(Parcel in) {
    name = in.readString();
    age = in.readString();
    }
    }
    AIDL 接口定义:
    interface IYaYaInterface {
    void setPersonIn(in Person person);
    void setPersonOut(out Person person);
    void setPersonInOut(inout Person person);
    void changePerson();
    void personChanged();
    Person getPerson();
    }
    服务端实现:
    class MyYaYa extends IYaYaInterface.Stub {
    private Person mPerson;
    @Override
    public void setPersonIn(Person person) throws RemoteException {
    Log.d(TAG, "setPersonIn1: " + person);
    mPerson = person;
    mPerson.setPrice("666666");
    Log.d(TAG, "setPersonIn2: " + person);
    }
    @Override
    public void setPersonOut(Person person) throws RemoteException {
    Log.d(TAG, "setPersonOut1: " + person);
    mPerson = person;
    mPerson.setPrice("666666");
    Log.d(TAG, "setPersonOut2: " + person);
    }
    @Override
    public void setPersonInOut(Person person) throws RemoteException {
    Log.d(TAG, "setPersonInOut1: " + person);
    mPerson = person;
    mPerson.setPrice("666666");
    Log.d(TAG, "setPersonInOut2: " + person);
    }
    @Override
    public void changePerson() throws RemoteException {
    mPerson.setName("CCCCCCC");
    Log.d(TAG, "changePerson: " + mPerson);
    }
    @Override
    public void personChanged() throws RemoteException {
    Log.d(TAG, "personChanged: " + mPerson);
    }
    @Override
    public Person getPerson() throws RemoteException {
    return mPerson;
    }
    }
    客户端调用代码:
    findViewById(R.id.in).setOnClickListener(v -> {
    try {
    Person in = new Person("In", "1");
    Log.d(TAG, "in1: " + in);
    mService.setPersonIn(in);
    Log.d(TAG, "in2: " + in);
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    });
    findViewById(R.id.out).setOnClickListener(v -> {
    try {
    Person out = new Person("Out", "1");
    Log.d(TAG, "out1: " + out);
    mService.setPersonOut(out);
    mService.changePerson();
    Log.d(TAG, "out2: " + out);
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    });
    findViewById(R.id.inout).setOnClickListener(v -> {
    try {
    Person inOut = new Person("InOut", "1");
    Log.d(TAG, "inOut1: " + inOut);
    mService.setPersonInOut(inOut);
    Log.d(TAG, "inOut2: " + inOut);
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    });

    Log 验证

    通过运行代码,我们可以从 Log 中观察数据流向的效果:

  • 服务端 Log

    D/AIDLDebug: setPersonIn1: Person{name='In', age='1'}
    D/AIDLDebug: setPersonIn2: Person{name='In', age='666666'}
    D/AIDLDebug: setPersonOut1: Person{name='null', age='null'}
    D/AIDLDebug: setPersonOut2: Person{name='null', age='666666'}
    D/AIDLDebug: setPersonInOut1: Person{name='InOut', age='1'}
    D/AIDLDebug: setPersonInOut2: Person{name='InOut', age='666666'}
  • 客户端 Log

    D/AIDLDebug: in1: Person{name='In', age='1'}
    D/AIDLDebug: in2: Person{name='In', age='1'}
    D/AIDLDebug: out1: Person{name='Out', age='1'}
    D/AIDLDebug: out2: Person{name='null', age='666666'}
    D/AIDLDebug: inOut1: Person{name='InOut', age='1'}
    D/AIDLDebug: inOut2: Person{name='InOut', age='666666'}
  • 从 Log 中可以看出:

    • in 和 inout:客户端和服务端的 Log 完全一致,说明客户端的修改会同步到服务端。
    • out:服务端的 Log 中完全没有接收到客户端传递的属性,说明 out 模式无法同步客户端的修改。

    数据流动的生命周期

    数据流动的生命周期仅在函数作用域内有效。一旦离开函数作用域,流动特性就会失效。例如:

  • 客户端修改 in 参数后调用 personChanged()

    mService.setPersonIn(in);
    in.setPrice("3");
    mService.personChanged();
  • 服务端 Log

    D/AIDLDebug: setPersonIn1: Person{name='In', age='1'}
    D/AIDLDebug: setPersonIn2: Person{name='In', age='666666'}
    D/AIDLDebug: personChanged: Person{name='In', age='666666'}
  • 客户端 Log

    D/AIDLDebug: in1: Person{name='In', age='1'}
    D/AIDLDebug: in2: Person{name='In', age='1'}
  • 从 Log 中可以看出,客户端修改 in 参数后,服务端的 personChanged() 方法被调用,服务端的对象属性发生了变化,但客户端的对象属性没有跟随变化。这是因为 in 模式下,数据流向服务端,客户端无法直接修改服务端的对象。


    结论

    数据方向标记(in、out、inout)决定了跨进程通信中参数内部数据的流向:

    • in:数据从客户端流向服务端,客户端无法直接修改服务端的对象属性。
    • out:数据从服务端流向客户端,服务端无法从客户端获取对象属性。
    • inout:数据可以双向流动,客户端和服务端可以相互修改对象属性。

    此外,数据流动特性仅在函数作用域内有效。一旦离开函数作用域,流动特性就会失效。因此,在使用 AIDL 进行跨进程通信时,必须确保客户端和服务端在同一函数作用域内进行操作,否则可能导致数据无法同步。


    源码验证

    通过查看生成的 AIDL Java 文件,可以更深入地理解数据流向的实现:

    @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_setPersonIn: {
    data.enforceInterface(descriptor);
    com.yaya.服务端.Person _arg0;
    if ((0 != data.readInt())) {
    _arg0 = com.yaya.服务端.Person.CREATOR.createFromParcel(data);
    } else {
    _arg0 = null;
    }
    this.setPersonIn(_arg0);
    reply.writeNoException();
    return true;
    }
    case TRANSACTION_setPersonOut: {
    data.enforceInterface(descriptor);
    com.yaya.服务端.Person _arg0;
    _arg0 = new com.yaya.服务端.Person();
    this.setPersonOut(_arg0);
    if ((_arg0 != null)) {
    reply.writeInt(1);
    _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
    } else {
    reply.writeInt(0);
    }
    return true;
    }
    case TRANSACTION_setPersonInOut: {
    data.enforceInterface(descriptor);
    com.yaya.服务端.Person _arg0;
    if ((0 != data.readInt())) {
    _arg0 = com.yaya.服务端.Person.CREATOR.createFromParcel(data);
    } else {
    _arg0 = null;
    }
    this.setPersonInOut(_arg0);
    if ((_arg0 != null)) {
    reply.writeInt(1);
    _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
    } else {
    reply.writeInt(0);
    }
    return true;
    }
    case TRANSACTION_changePerson: {
    data.enforceInterface(descriptor);
    this.changePerson();
    reply.writeNoException();
    return true;
    }
    case TRANSACTION_personChanged: {
    data.enforceInterface(descriptor);
    this.personChanged();
    reply.writeNoException();
    return true;
    }
    case TRANSACTION_getPerson: {
    data.enforceInterface(descriptor);
    com.yaya.服务端.Person _result = this.getPerson();
    reply.writeNoException();
    if ((_result != null)) {
    reply.writeInt(1);
    _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
    } else {
    reply.writeInt(0);
    }
    return true;
    }
    default: {
    return super.onTransact(code, data, reply, flags);
    }
    }
    }

    从代码中可以看出:

    • ininout 模式下,客户端传递的对象可以直接被服务端操作。
    • out 模式下,服务端传递的对象是新创建的,无法直接从客户端获取内部数据。

    注意事项

    • 如果你在 Android 11 上进行试验,可能会发现怎么都 bind 不上 Service。这是因为 Android 11 更新了隐私策略,默认情况下安装包之间互相是不可见的。
    • 确保在跨进程通信中,客户端和服务端在同一函数作用域内进行操作,否则数据流动特性可能丢失。

    通过以上验证和结论,可以清晰地了解 AIDL 中非原语参数的数据流向特性,以及如何在不同方向标记下实现数据同步。

    上一篇:大学寒假这样过,过完惊艳所有人,不只是你的宿友,还有千千万万个程序员同行们!!!
    下一篇:代码天敌之体积

    发表评论

    最新留言

    做的很好,不错不错
    [***.243.131.199]2026年06月12日 21时28分55秒

    关于作者

        喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
    -- 愿君每日到此一游!

    推荐文章