Android Interprocess Communication (I)

Posted by ow-phil on Sat, 25 May 2019 22:58:48 +0200

Often, memory between Android processes is not shared, so one process in Android usually cannot access the memory of another process. So, in order to realize IPC (cross-process communication), it is necessary to use some seemingly special ways, in general, the four components of Android. But for Activity, ContentProvider, Broadcast Receiver, cross-process only implements one-way data operations, not method calls (AIDL interface calls). Through bind service, AIDL can be used to make method calls and data transfers across processes. When passing objects in the form of AIDL, the corresponding classes of objects must support the Parcelable interface.

What is Parcelable

_Before you understand the Parcelable protocol, explain Parcel first.

  Official documents

_According to the interpretation in the document, Parcel is a container of messages (data and object references) that can be delivered through IBinder. A Parcel can contain flattened data and a real-time IBinder that can be de-flattened when communicating with IPC. This IBinder will cause an IBinder agent connected to the original Ibinder in Parcel to be received at the other end of the communication.

Note: Parcel is not a universal serialization mechanism. This class is designed for high-performance IPC transmission. As far as it is concerned, it is not suitable for persistent storage. Any change in the underlying implementation of the data will result in unreadable data.

Parcel's API is designed to solve the problem of reading and writing different types of data. It mainly includes six categories: Primitives (base class), Primitive Arrays, Parcelables, Bundles, Active Objects (Binder, ParcelFile Descriptor), Untyped Containers (container class).

  • Parcelable

Examples of classes that inherit this interface can be written and stored from Parcel. The class that implements this interface must define a non-empty static variable, CREATOR, which implements the Parcelable.Creator interface.

//The flag flag bit of writeToParcel(...), which is the result of other functions, prevents the release of resources.
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;

//The flag flag flag of writeToParcel(...), indicating that the parent object will manage duplicate data typically copied from the internal class
public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;

//file descriptor flags
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;

//Describes the type of special objects in Parcelable, usually 0, but when you include a file descriptor, you should return CONTENTS_FILE_DESCRIPTOR
public int describeContents();

//Flattening this object in dest
public void writeToParcel(Parcel dest, int flags);

//The static variable CREATOR that must be implemented for creating Parcelable instances in cluster Parcel
public interface Creator<T> {
    
    //Create examples    
    public T createFromParcel(Parcel source);
        
    //Create a new array of Parcelable classes
    public T[] newArray(int size);
}


public interface ClassLoaderCreator<T> extends Creator<T> {
     
    //Special CREATOR, allowing ClassLoader to be received   
    public T createFromParcel(Parcel source, ClassLoader loader);
}
  • PaecelableCompat

_Pracelable's downward compatible classes, in order to better support the low version, take API13 as the dividing line (support-v4-25.2.0), create a more compatible CREATOR. The main difference between the two versions below APi13 and later is ClassLoader.

Take common news for example:

  • Differences between Parcelable and Serilizable

_Serializable is a serialization method provided by JDK, while Parcelable is a serialization method provided by Android to solve IPc. Compared with Serializable, it has higher performance, but it is more difficult to implement, and it is not suitable for persistent storage. (As mentioned above, Parcel has strict requirements for data implementation. Changing any implementation may lead to previous ones. Storage is unreadable. Specific explanations can be found in the following examples.

NewsEntity.ava

package th.how.bean;

import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Keep;

/**
 * Created by me_touch on 2017/8/16.
 *Annotations are negligible because GreenDao is used
 */

@Entity
public class NewsEntity{

    @Id
    int id;
    String title;

    @Keep
    public NewsEntity(int id, String title){
        this.id = id;
        this.title = title;
    }

    @Generated(hash = 2121778047)
    public NewsEntity() {
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    @Override
    public String toString() {
        return "id = " + id + ", title = " + title;
    }
}

NewsEntityParcel.java

package th.how.bean;

import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;

/**
 * Created by me_touch on 2017/9/7.
 * Parcel Implement classes, implement Parcelable interfaces, read and write in the same order, otherwise it may lead to unpredictable results
 */

public class NewsEntityParcel extends NewsEntity implements Parcelable{

    //If the order of reading and writing is changed, the original stored data will become unreadable.
    public NewsEntityParcel(Parcel in){
        this.id = in.readInt();
        this.title = in.readString();
    }

    public NewsEntityParcel(int id, String title){
        this.id = id;
        this.title = title;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(title);
    }
    
    public void readFromParcel(Parcel dest){
        this.id = dest.readInt();
        this.title = dest.readString();
    }

    //Create CREATOR through Parcelable Compat, based on compatibility considerations
    public static Parcelable.Creator<NewsEntityParcel> CREATOR = ParcelableCompat.newCreator(
            new ParcelableCompatCreatorCallbacks<NewsEntityParcel>() {
        @Override
        public NewsEntityParcel createFromParcel(Parcel in, ClassLoader loader) {
            return new NewsEntityParcel(in);
        }

        @Override
        public NewsEntityParcel[] newArray(int size) {
            return new NewsEntityParcel[size];
        }
    });
}

The Concept and Grammar of AIDL

  Official documents

concept

According to the document, AIDL is used to simplify the complicated procedure of weaving objects across borders. AIDL files are written in Java simple grammar. The interface of AIDL is declared by one or more methods. In terms of grammar, it differs from ordinary interface classes mainly in the following points

  1. AIDL only supports methods, that is, static variables cannot be defined in AIDL
  2. AIDL has its own keywords.
  3. In Java 1.8, interfaces can contain implementations, but not in AIDL.
  4. You cannot use permission modifiers, and final, static, etc.
  5. import must be explicitly used

Key word

  • Usually when a client calls a service method through AIDL, the client will enter a blocking state and wait for the execution of the corresponding service method. However, through the one way keyword modification method, we can avoid blocking the client when it is called. One way can be used to modify the interface and methods, which is equivalent to adding one way keywords before all declared methods. Note that oneway does not modify methods with return values.

  • in, out, inout is used to mark the data direction of non-primitive parameters. Note that all primitive parameters are default and can only be in. In means that data flows from the client to the server, while out means that data flows from the server to the client, and inout is bidirectional (the client and the server are relative and not absolute).
    If you set it incorrectly, it may cause the acquired object not to be empty, but the value is indeed empty, of course, it can not be set directly to inout without necessary, which will lead to serious overhead.

  • When to use in and when to use out? In is used when only the client's value needs to be passed to the server, and out is used when only the server's value needs to be assigned and then passed to the client.

Transfer objects

_In addition to the class corresponding to the object needs to implement the Parcelable method, we also need to declare the corresponding class as Parcelable in the aidl file to find and identify the class that implements the Parcelable method.

An example

Specific way: In the module directory, create an Aidl directory in the same level as the java directory, and then create a package with the same package name as the class corresponding to the object to be delivered, as shown in NewsEntityParcel.aidl. If you don't need to pass, ignore this step. To create a new Aidl file to define the method to be invoked, you need to declare the package you are calling, such as IMyAidlInterface.aidl, IClientInterface.aidl, then compile and generate the corresponding interface file (build/generated/source/aidl).

NewsEntityParcel.aidl

package th.how.bean;
parcelable NewsEntityParcel;

IMyAidlInterface.aidl

package th.how.ipc;
import th.how.bean.NewsEntityParcel;
import th.how.ipc.IClientInterface;
interface IMyAidlInterface {
    NewsEntityParcel getData(int id, String title, in NewsEntityParcel parcel);
    void register(IClientInterface callback);
    void unRegister(IClientInterface callback);
}

IClientInterface.aidl

package th.how.ipc;
import th.how.bean.NewsEntityParcel;

interface IClientInterface {
    void callbackClient(in NewsEntityParcel parcel);
}

Analysis of AIDL files generated after compilation, take IMyAidlInterface.aidl as an example

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: G:\\2SelfWork\\HowApp\\app\\src\\main\\aidl\\th\\how\\ipc\\IMyAidlInterface.aidl
 */
package th.how.ipc;
public interface IMyAidlInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements th.how.ipc.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = "th.how.ipc.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Converting IBinder objects into corresponding IMyAidlInterface
 * Way: Query the corresponding IMyAidlInterface object from IBinder based on DESCRIPTOR, if not
 * Generating corresponding objects through proxy
 */
public static th.how.ipc.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof th.how.ipc.IMyAidlInterface))) {
return ((th.how.ipc.IMyAidlInterface)iin);
}
return new th.how.ipc.IMyAidlInterface.Stub.Proxy(obj);
}
@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
{
switch (code)
{
case INTERFACE_TRANSACTION: //Query Interface Description
{
reply.writeString(DESCRIPTOR);
return true;
}
//The corresponding getData (int id, String title, in NewsEntityParcel package) method
case TRANSACTION_getData:
{
data.enforceInterface(DESCRIPTOR);
//Get the values of three parameters
int _arg0;
_arg0 = data.readInt();
java.lang.String _arg1;
_arg1 = data.readString();
th.how.bean.NewsEntityParcel _arg2;
if ((0!=data.readInt())) {
_arg2 = th.how.bean.NewsEntityParcel.CREATOR.createFromParcel(data);
}
else {
_arg2 = null;
}
//Calling this method in a tub object
th.how.bean.NewsEntityParcel _result = this.getData(_arg0, _arg1, _arg2);
reply.writeNoException();
//Write the return value in reply
if ((_result!=null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
case TRANSACTION_register:
{
data.enforceInterface(DESCRIPTOR);
th.how.ipc.IClientInterface _arg0;
_arg0 = th.how.ipc.IClientInterface.Stub.asInterface(data.readStrongBinder());
this.register(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_unRegister:
{
data.enforceInterface(DESCRIPTOR);
th.how.ipc.IClientInterface _arg0;
_arg0 = th.how.ipc.IClientInterface.Stub.asInterface(data.readStrongBinder());
this.unRegister(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements th.how.ipc.IMyAidlInterface
{
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 th.how.bean.NewsEntityParcel getData(int id, java.lang.String title, th.how.bean.NewsEntityParcel parcel) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
th.how.bean.NewsEntityParcel _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(id);
_data.writeString(title);
if ((parcel!=null)) {
_data.writeInt(1);
parcel.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_getData, _data, _reply, 0);
_reply.readException();
if ((0!=_reply.readInt())) {
_result = th.how.bean.NewsEntityParcel.CREATOR.createFromParcel(_reply);
}
else {
_result = null;
}
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void register(th.how.ipc.IClientInterface callback) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_register, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void unRegister(th.how.ipc.IClientInterface callback) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_unRegister, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_register = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_unRegister = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
public th.how.bean.NewsEntityParcel getData(int id, java.lang.String title, th.how.bean.NewsEntityParcel parcel) throws android.os.RemoteException;
public void register(th.how.ipc.IClientInterface callback) throws android.os.RemoteException;
public void unRegister(th.how.ipc.IClientInterface callback) throws android.os.RemoteException;
}

The generated IMyAidlInterface interface inherits from the IIterface interface

package android.os;

//Base Class of Binder Interface
public interface IInterface
{
    
    //Binder objects are retrieved by association with this interface. In order for the proxy object to return the correct value, this method must be used instead of directly transforming.
    public IBinder asBinder();
}

Within the interface, an abstract class Stub, which inherits from Binder and implements IMyAidlInterface interface, is defined. It implements the return interface object method, IBinder object method, onTransact method, and the internal class Proxy.

  • The onTransact(...) method implements a call to the method defined in the aidl file, one of which is explained in the commentary, and the others are similar.
  • Proxy implements methods defined in aidl file and calls in proxy class

Topics: Android Java JDK