

AIDL 数据深究
source link: https://blog.fxcdev.com/archives/235.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

今早群里在讨论 AIDL 的问题,我突然想到一个问题:客户端通过 AIDL 传递给服务端一个非原语参数对象,然后在客户端改变这个参数,服务端会跟着变化吗?
了解一点 Binder 原理的同学,应该都会觉得不可能,因为 Binder 是通过调用时将内存变化同步给被调用方,从而实现的跨进程通信。改变数据类的属性时,压根都没有调用 Binder,怎么可能同步。一开始我也是这么想的,不过深入研究后却发现了一点不一样东西。
看到传递非原语参数,应该第一时间就想到 in
,out
,inout
这三个标记,查了一下官方文档,描述很简单
所有非原语参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout(见下方示例)。原语默认为 in,不能是其他方向。
这句话里对于我们这个问题有用的线索就只有一个:数据走向。
那么让我们大胆的猜测一下,这个数据走向是否指的就是该参数内部的数据流向?如果是这样,那么数据走向应该是这样的:
in
:参数可以从客户端流向服务端out
:参数可以从服务端流向客户端inout
:参数可以在客户端和服务端互相流动
从客户端流向服务端比较好理解,就是传参,但是从服务端流向客户端到底是什么意思?
写一个 Demo 验证下:
Demo 代码
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();
}
Service:
class MyYaYa extends IYaYaInterface.Stub {
@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);
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();
}
});
从代码可以知道,我们分别定义了使用 in
,out
,inout
修饰的三个方法,然后在客户端分别调用了这三个方法,服务端则在收到对象后马上修改对象的属性。我们看一下运行起来的 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'}
可以看到 in
和 inout
服务端都是可以接收到完整的属性,而 out
则服务端完全接收不到任何属性。
这验证了一部分我们的猜想:in
和 inout
可以从客户端传递参数中的属性到服务端,而 out
则不能从客户端传递参数的属性到服务端。
接着验证数据从服务端流向客户端,我们在服务端中修改了参数的属性,看一下客户端的对象属性有没有跟随变化:
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 的对象属性完全没有发生变化,而 out 和 inout 都同步了服务端的修改。
至此,我们的猜想已经得到了验证,最后还剩下一个问题,就是 in out inout 的生命周期,也就是说在该函数作用域之外,同步是否还会生效?修改我们的代码:
验证生命周期
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);
in.setPrice("3");
mService.personChanged();
} 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();
}
});
有两点修改:
in
修饰符在setPersonIn()
之后,客户端自行修改了对象参数,然后调用personChanged()
在服务端打印对象out
修饰符在setPersonOut()
之后,调用changePerson()
改变服务端的对象属性,然后打印客户端的对象
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'}
D/AIDLDebug: setPersonOut1: Person{name='null', age='null'}
D/AIDLDebug: setPersonOut2: Person{name='null', age='666666'}
D/AIDLDebug: changePerson: Person{name='CCCCCCC', age='666666'}
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'}
可以看到,数据的流动不能始终保持,在离开了相应的函数作用域之后,流动就会失效。
方向标记规定了在跨进程通信中参数内部的数据流向:
in
:表示参数内部数据只能从客户端流向服务端out
:表示参数内部数据只能从服务端流向 客户端inout
:表示参数内部数据可以在客户端和服务端之间互相流动
而且,该流动特性只在被修饰的函数作用域内有效,一旦离开该作用域,流动特性就会失效。
通过源码印证下我们的结论,看一下生成的 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);
// 可以看到 _arg0 就是客户端传进来的参数
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;
// out 根本没有从客户端拿数据,而是 new 了一个新对象
_arg0 = new com.yaya.服务端.Person();
// 然后将新对象传递给服务端
this.setPersonOut(_arg0);
reply.writeNoException();
// 在这里将服务端的对象重新写到序列化中,可以返给客户端。如果在 setPersonOut 方法中改变了参数,会在这里传递给客户端
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);
// inout 在这里获取了客户端传递的参数
com.yaya.服务端.Person _arg0;
if ((0!=data.readInt())) {
_arg0 = com.yaya.服务端.Person.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
// 在这里传递给服务端
this.setPersonInOut(_arg0);
reply.writeNoException();
// 在这里回写给客户端
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);
}
}
}
参看源码中的注释,可以从代码层面印证我们的猜想,而且明确了为什么仅在函数作用域内才会流动。
如果你在 Android 11 上进行试验,可能会发现怎么都 bind 不上 Service:
ActivityManager: Unable to start service Intent { act=com.yaya.server cmp=com.yaya.server/.YaYaService } U=0: not found
这是因为 Android 11 更新了隐私策略,默认情况下安装包之间互相是不可见的。
本文由 TOner 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Dec 15, 2020 at 12:56 pm
Recommend
-
71
Android跨进程IPC通信AIDL
-
32
( 此花无日不春风 ) 其实IdentityServer4的小项目已经基本完结了,但是我总感觉还是有很多东西没有深入挖掘和研究的,这不,二群里有小...
-
7
深究strtok系列函数 2019-05-21 ...
-
14
一、跨进程通信为了演示方便,将Service与Activity处于不同的进程,可以在AndroidManifest.xml中,把service配置成android:process=":remote" ,也可以命名成其他的。 (1) IRemoteService.aidl:定义Server端提供的服务接口...
-
7
自定义binder架构的 client/ server组件 一、AIDL 1.1 Server端 RemoteService.java 本例是为了演示进程间的通信机制,故需要将Service与Activity处于不同的进程,需要在AndroidManifest.xml...
-
9
编者按:本文来自微信公众号 “锋科技”(ID:feng_keji) ,作者:锋科技,编辑:Witkey,36氪经授权发布。 说到近期讨论热烈的互联网大事,QQ一定榜上有名。
-
9
来源:内容由半导体行业观察(ID:icbank)编译自「biz-journal」,笔者:汤之上隆,谢谢。 2020年在全球范围内爆发的新冠肺炎直击汽车行业。汽车需求“蒸发”、全球各车企工厂相继停工。原以为市场需求、汽车生产都会在...
-
15
Android 11 AIDL 失效引出的新特性:软件包可见性 2021-06-21 前两天公司一同事做了一下 AIDL 的尝试,发现在 Android 11 的设备上无法运行,经过检索得知,在 Android 11 (Target API 30)的情况下,需要指定软件包的可见性。...
-
4
摘要:本节主要来讲解Android10.0 Binder中如何使用AIDL 阅读本文大约需要花费20分钟。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!
-
4
AIDL在Android应用程序中的重要作用 作者:Reathin 2023-11-06 08:22:34 AIDL在Android中的应用非常广泛,特别是在需要进行跨进程通信的情况下。它可以帮助开发人员轻松地定义和实现跨进程通信接口,提高应用程序...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK