Android Framework: In-depth exploration of AIDL data flow

I was discussing the issue of AIDL in the group today. I suddenly thought of a question: The client passes a non-primitive parameter object to the server through AIDL, and then changes this parameter on the client. Will the server change accordingly?

Students who know a little about the principle of Binder should find it impossible, because Binder synchronizes the memory changes to the callee when calling, so as to achieve cross-process communication. When changing the properties of the data class, Binder is not called at all, how can it be synchronized. I thought the same at the beginning, but after further research, I found something different.

Official document

Seeing the transfer of non-primitive parameters, it should be the first thing to think inof out, inoutthese three tags, check the official documents, the description is very simple

All non-primitive parameters need a direction mark indicating the direction of the data. Such tags can be in, out, or inout (see example below). The primitive is in by default and cannot be in other directions.

In this sentence, there is only one useful clue to our question: the data trend .

So let us boldly guess whether this data direction refers to the data flow direction inside the parameter? If so, then the data trend should look like this:

  • in: Parameters can flow from the client to the server
  • out: Parameters can flow from the server to the client
  • inout: Parameters can flow between the client and server

It is easier to understand the flow from the client to the server, which is to pass parameters , but what does it mean to flow from the server to the client?

Write a Demo to verify:

Demo code

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;
  }
}

Client:

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();
  }
});

Can know from the code, we define the use in, out, inoutthree methods modified, then the client calls each of these three methods, the server immediately modify the properties of an object after receiving the object. Let's take a look at the running Log and observe the flow of data .

Log verification

Server 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'}

We can see inand inoutserver are can receive full property , while outthe server is completely not receive any property .

This verifies that the part of our conjecture: inand inoutcan pass parameters in the property from the client to the server, but outcan not attribute parameters passed from the client to the server.

Then verify that the data flows from the server to the client. We modify the attributes of the parameters in the server to see if the client's object attributes follow the changes:

Client

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 confirms our conjecture that the object properties of in have not changed at all, and both out and inout have synchronized the server-side modification.

At this point, our conjecture has been verified, and the last remaining question is the life cycle of in out inout, that is to say , will synchronization still take effect outside the scope of the function ? Modify our code:

Validation life cycle

Code modification

Client

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();
  }
});

There are two modifications:

  1. inModifier setPersonIn()Then, the client itself modify the object parameters, and then call personChanged()on the server to be printed
  2. outModifier setPersonOut()after the call changePerson()to change the server object properties, and then print the client object

Log verification

Server

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'}

Client

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'}

It can be seen that the flow of data cannot always be maintained, and the flow will become invalid after leaving the corresponding function scope .

in conclusion

The direction mark specifies the data flow direction within the parameters in cross-process communication :

  • in: Indicates that the internal data of the parameter can only flow from the client to the server
  • out: Indicates that the internal data of the parameter can only flow from the server to the client
  • inout: Indicates that the internal data of the parameter can flow between the client and the server

Furthermore, only the flow characteristics of the modified function scope valid for, upon leaving the scope, the flow characteristics will fail.

Source code:

To confirm our conclusion through the source code, take a look at the generated AIDL java file:

@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);
    }
  }
}

See the source of the comment , we can prove our guess from the code level, but also clear why only function scope will flow in.

note:

If you are experimenting on Android 11, you may find that bind can't connect to Service:

ActivityManager: Unable to start service Intent { act=com.yaya.server cmp=com.yaya.server/.YaYaService } U=0: not found

This is because Android 11 has updated the privacy policy, and installation packages are invisible to each other by default.

Author

Nanba 10,000

Guess you like

Origin blog.csdn.net/Androiddddd/article/details/111345700