本文共 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 文件,可以更深入地理解数据流向的实现:
@Overridepublic 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); } }} 从代码中可以看出:
- in 和 inout 模式下,客户端传递的对象可以直接被服务端操作。
- out 模式下,服务端传递的对象是新创建的,无法直接从客户端获取内部数据。
注意事项
- 如果你在 Android 11 上进行试验,可能会发现怎么都 bind 不上 Service。这是因为 Android 11 更新了隐私策略,默认情况下安装包之间互相是不可见的。
- 确保在跨进程通信中,客户端和服务端在同一函数作用域内进行操作,否则数据流动特性可能丢失。
通过以上验证和结论,可以清晰地了解 AIDL 中非原语参数的数据流向特性,以及如何在不同方向标记下实现数据同步。
发表评论
最新留言
关于作者