preface

Haven’t written learning experience for a long time, recently read the Android multi-process related knowledge, it is time to summarize, but also convenient for their own review later, I mainly around the following points:

  • Why use IPC
  • The difference between the two serializations
  • Simple understanding of Binder
  • What are the ways to implement multiple processes

The need for IPC

If you want to use IPC, you must have multiple processes. Why do you want to use multiple processes? Here are two points:

  • To prevent OOM, consider increasing the memory used by the application. The memory allocated to an application is limited. In order to increase the memory of the application, we put some modules in the process separately, so that the system will allocate memory to these modules separately, reducing the probability of oom application.
  • Sometimes, we want to obtain some data in other applications, such as contact information, which cannot be directly obtained in other modules, so we need IPC. In fact, ContentProvier is also a multi-process communication mode, but some details are shielded, so we can’t perceive it.

serialization

Serialization is not a new concept in Android. It is available in Java. There are two ways of serialization: Serializable and Parcelable interfaces.

1. The Serializable interface

Java serialization is to convert Serializable objects to a character sequence and save it in a file. If necessary, the character sequence can be converted to objects to restore the original structure, that is, deserialization. In this way, since we are character sequences, we can easily transfer such as intent, multi-process, or even network communication.

ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("worm.out"));
        oos1.writeObject("Worm storage By FileOutputStream "); oos1.writeObject(w); / / must all referenced objects are serialized Data (in this case, after all this class), or throw a Java IO. NotSerializableException: this exception oos1. Close (); FileInputStream ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("worm.out"));
        String s1 = (String)ois1.readObject();
        Worm w1 = (Worm)ois1.readObject();
        ois1.close();
        System.out.println("After deserialization operation 1");
        System.out.println(s1);
        System.out.println("w1:"+w1);
Copy the code
public class Data implements Serializable {
    private static final long serialVersionUID = 7247714666080613254L;
    public int n;
    public Data(int n) {
        this.n = n;
    }
    public String toString() {returnInteger.toString(n); }}Copy the code

Serializable object Serializable object Serializable object Serializable object Serializable object Serializable object Serializable object Serializable object

Before serialization operation W =: A (853): B (119): C (802): D (788):e(199):f(881) Worm Storage By FileOutputStream after deserialization operation 1 w1::a(853):b(119):c(802):d(788):e(199):f(881)Copy the code

So just to show you, serialVersionUID, this thing that we see all the time, what it does, since it’s in the system for a reason, it’s very important, we’ll write it when we serialize it, we’ll compare it when we deserialize it, If consistent, the deserialization operation succeeds; otherwise, an exception is thrown.

Parcelable

In Android, a new serialization approach is the use of Parcelable, which is easy to use with IntEnts and binders.

public class FilterEntity implements Parcelable{
    private String name;
    private String chooseStr;
    private boolean hasSelected;

    public FilterEntity(String name, String chooseStr, boolean hasSelected) {
        this.name = name;
        this.chooseStr = chooseStr;
        this.hasSelected = hasSelected;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getChooseStr() {
        return chooseStr;
    }

    public void setChooseStr(String chooseStr) {
        this.chooseStr = chooseStr;
    }

    public boolean isHasSelected() {
        return hasSelected;
    }

    public void setHasSelected(boolean hasSelected) {
        this.hasSelected = hasSelected;
    }

    @Override
    public String toString() {
        return "FilterEntity{" +
                "name='" + name + '\'' + ", chooseStr='" + chooseStr + '\'' + ", hasSelected=" + hasSelected + '}'; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); dest.writeString(this.chooseStr); dest.writeByte(this.hasSelected ? (byte) 1 : (byte) 0); } public FilterEntity() { } protected FilterEntity(Parcel in) { this.name = in.readString(); this.chooseStr = in.readString(); this.hasSelected = in.readByte() ! = 0; } public static final Creator
      
        CREATOR = new Creator
       
        () { @Override public FilterEntity createFromParcel(Parcel source) { return new FilterEntity(source); } @Override public FilterEntity[] newArray(int size) { return new FilterEntity[size]; }}; }
       
      Copy the code

A comparison of two serializations

  • Serializable is a Java serialization method, which is easy to use but expensive. A large number of read and write operations are required during serialization and deserialization.
  • Pacelable is unique to Android, so it’s definitely the first choice on Android. It’s a little more complicated to use, but it’s very efficient.
  • Parcelable serializes objects into memory, while Serializable serializes objects into storage devices and network communications.

Binder

A Binder is a very complex concept, and we’re going to focus on the upper level applications. A Binder is a bridge between processes, a remote service

public class BookManagerService extends Service {

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
//    private CopyOnWriteArrayList<IOnNewBookArriveListen> mListenList = new CopyOnWriteArrayList<IOnNewBookArriveListen>();
    private RemoteCallbackList<IOnNewBookArriveListen> mListenList = new RemoteCallbackList<>();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.d("wangchao0000"."getBookList: ");
            returnmBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @override public Boolean onTransact(int code, Parcel data, Parcel Reply, int flags) throws RemoteException { Like package validationreturnsuper.onTransact(code, data, reply, flags); }}; @Override public voidonCreate() {
        super.onCreate();
        Log.d("wangchao0000"."onCreate: ");

//        mBookList.add(new Book(1, "Exploring the arts."));
//        mBookList.add(new Book(2, "Flutter advanced"));

        ExecutorService executorService = Executors.newScheduledThreadPool(1);
        ((ScheduledExecutorService) executorService).scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Log.d("wangchao0000"."run: ");
                int bookId = mBookList.size() + 1;
                notifyNewBookArrived(new Book(bookId, "new book--"+ bookId)); } },0, 5, TimeUnit.SECONDS); }}Copy the code

The Manifest file is as follows:

    <service android:name=".service.BookManagerService"
            android:process=":remote"
            android:enabled="true"
            android:exported="true"
            />
        <activity android:name=".aidl.BookMnagerActivity"/>
Copy the code

BookManagerService then runs on the remote service. The next step is to see how the client communicates with the remote server. Let’s create an AIDL file and declare several methods in this interface file.

interface IBookManager {
   List<Book> getBookList();
   void addBook(in Book book);
}
Copy the code

Binder class is automatically generated by the system according to the AIDL file. Binder class is our core class, and the client and server can communicate with each other using it. It’s exciting to think about it. Code first:

/* * This file is auto-generated. DO NOT MODIFY. * Original file: /Users/wangchao/code/TestProject/app/src/main/aidl/com/example/wangchao/testproject/IBookManager.aidl */ package com.example.wangchao.testproject; // Declare any non-default types here with import statements public interface IBookManager extends android.os.IInterface  { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.wangchao.testproject.IBookManager { private static final java.lang.String DESCRIPTOR ="com.example.wangchao.testproject.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.example.wangchao.testproject.IBookManager interface,
 * generating a proxy if needed.
 */
public static com.example.wangchao.testproject.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if(((iin! =null)&&(iin instanceof com.example.wangchao.testproject.IBookManager))) {return ((com.example.wangchao.testproject.IBookManager)iin);
}
return new com.example.wangchao.testproject.IBookManager.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:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.wangchao.testproject.data.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.example.wangchao.testproject.data.Book _arg0;
if((0! =data.readInt())) { _arg0 = com.example.wangchao.testproject.data.Book.CREATOR.createFromParcel(data); }else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true; }}return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.wangchao.testproject.IBookManager
{
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 java.util.List<com.example.wangchao.testproject.data.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.wangchao.testproject.data.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.wangchao.testproject.data.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.example.wangchao.testproject.data.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if((book! =null)) { _data.writeInt(1); book.writeToParcel(_data, 0); }else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

public java.util.List<com.example.wangchao.testproject.data.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.wangchao.testproject.data.Book book) throws android.os.RemoteException;

}
Copy the code

This class inherits from IInterface. Once it inherits from IInterface, the system considers it AIDL:

/**
 * Base class for Binder interfaces.  When defining a new interface,
 * you must derive it from IInterface.
 */
public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder();
}
Copy the code

There’s just a method called asBinder that returns binder objects. Take a look:

public java.util.List<com.example.wangchao.testproject.data.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.wangchao.testproject.data.Book book) throws android.os.RemoteException;
Copy the code

If you look closely, these are the two methods defined in the AIDL file. They are declared here and must be used in this class. It then defines several integer variables that tell the server which method the client is currently calling, otherwise the server would not know. The stub is a familiar concept that can be found in services:

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.d("wangchao0000"."getBookList: ");
            returnmBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @override public Boolean onTransact(int code, Parcel data, Parcel Reply, int flags) throws RemoteException { Like package validationreturnsuper.onTransact(code, data, reply, flags); }};Copy the code

Right? I was right. And then we’ll analyze it. It’s an inner class, it’s a Binder class, it implements the IInterface interface. There are several important methods in this class:

  • asInterface
  • onTransact
  • asBinder
  • Proxy, which is an inner class

Let’s analyze their implementation logic separately:

asInterface

Let’s first look at the asInterface method, which gets the server object:

/**
 * Cast an IBinder object into an com.example.wangchao.testproject.IBookManager interface,
 * generating a proxy if needed.
 */
public static com.example.wangchao.testproject.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if(((iin! =null)&&(iin instanceof com.example.wangchao.testproject.IBookManager))) {return ((com.example.wangchao.testproject.IBookManager)iin);
}
return new com.example.wangchao.testproject.IBookManager.Stub.Proxy(obj);
}
Copy the code

It is a static method called on the client side, and the comments are clear: if the client and server are in the same process, return the Service object directly, otherwise return Proxy. In fact, since you’re writing this class, you’re definitely not in a process anymore, so proxy comes in handy.

Proxy

Proxy is a stub internal class that implements the AIDL interface. The Proxy object is returned to the client, and the client calls the method inside it:

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = IBookManager.Stub.asInterface(service);
            try {

//                Log.d("BookMnagerActivity"."query book list: -------" + Arrays.asList(list));
//                list.add(new Book(3, "Web advanced"));
//                Log.d("BookMnagerActivity"."query book list: -------" + Arrays.asList(bookManager.getBookList()));

                bookManager.registerListen(onNewBookArriveListen);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
Copy the code

The bookManager is the object of the Proxy, which you can access through a breakpoint. Let’s pick one way to analyze it:


private static class Proxy implements com.example.wangchao.testproject.IBookManager
{
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 java.util.List<com.example.wangchao.testproject.data.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.wangchao.testproject.data.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.wangchao.testproject.data.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return_result; }}Copy the code

When the client calls the getBookList method, it executes here, declaring _data, _reply, and _result. The call process is as follows:

  • Write the int identifier to _data so that the server can recognize it.
  • Call transact of the remote service, at which point the client process is suspended until the server completes the onTransact method and returns the result.
  • When the result is returned, it wakes up and returns the data to the client at the point of call.

onTransact

As mentioned earlier, the client invokes the server-side Transact method, which belongs to Binder’s method and is in the Binder thread pool

mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
Copy the code

Let’s see what the server side does.

@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:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.wangchao.testproject.data.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
Copy the code

In this method, it’s going to determine which method to call based on the code, which is the same id that we declared earlier, oh, nice, it’s as simple as that:

  • Find the method to call according to the code
  • Call getBookList on the server
  • Write the result to reply and wake up the client

Proxy and onTransact are two processes that respond to each other. It’s important to note here,

  • The current thread is suspended until the server process returns the result. If the remote method performs a time-consuming operation, the ANR will result, so the thread that initiated the request cannot be the UI thread.
  • The Binder method runs in thread pools, where thread synchronization is used.

Binder is a tool that you can use to create a Binder class, but you don’t have to write your own AIDL class.

Several ways of IPC in Android

  • Bundle
  • File sharing
  • Messenger
  • AIDL
  • ContentPrider

Bundle

As long as the object is already serialized, it can be placed in the Bundle for transmission.

File sharing

This is very simple, multiple processes or should read and write to the same file, we write the serialized object, another process read from the file, and then deserialize, can get the data smoothly.

Messenger AIDL

AIDL has been analyzed with Binder above. If you don’t understand it, you can learn how to use AIDL. Messenger encapsulates AIDL, which is nothing to talk about.

ContentPrider

It’s one of the four main components of Android. Yes, it has IPC attributes, such as retrieving data from the contacts module.

Well, I have written a lot. The main understanding principle is not the focus of this article as to how to use it.

conclusion

  • Serialization and deserialization simply convert an object into a sequence of characters and store it in a file, and then read and write the file for easy transfer.
  • Binder. The methods in Binder are server-side defined methods. We, as clients, want to call server-side methods to implement some functions.
  • Do not bind in the UI thread, because we do not know what the remote process is doing