Android: simple understanding and use of inter process communication (IPC) in Android learning notes

Posted by jhlove on Tue, 11 Jan 2022 02:01:26 +0100

Interprocess communication mode

1. Background

Before we explain Binder, let's learn some basic knowledge of Linux

1.1. Process space division




IPC is inter process communication. Android is based on Linux. For security reasons, different processes cannot operate on each other's data, which is called "process isolation".

1.2. Process isolation & cross process communication (IPC)

Process isolation

  • In order to ensure security independence, one process cannot directly operate or access another process, that is, Android processes are independent and isolated from each other

Cross process communication (IPC)

  • That is, data interaction and communication are required between processes

Basic principles of cross process communication

Android applications and system services run in different processes for security, stability and memory management, but applications and system services need to communicate and share data.

advantage

  • Security: each process runs separately, which can ensure the isolation of the application layer from the system layer.
  • Stability: if one process crashes, it will not cause other processes to crash.
  • Memory allocation: if a process is not needed, it can be removed from memory and the corresponding memory can be recycled.

1.3. Basic concept: serialization method

Principle and difference of serializable & Parcelable

1.3.1. Serializable interface

Serializable is a serialization interface provided by Java, which is an empty interface.

  • serialVersionUID is used to assist in the serialization and deserialization process. In principle, the serialized data contains
  • The serialVersionUID must be the same as the serialVersionUID of the current class to be serialized normally.
  • Static member variables belong to classes and not objects, so they will not participate in the serialization process;
  • Secondly, the member variables marked with the transient keyword do not participate in the serialization process.

Override the following two methods to override the system default serialization and deserialization processes

private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}

1.3.2 SParcelable interface

The unique serialization method in Android is more efficient than Serializable and occupies less memory, but it is a little troublesome to use.

public class User implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;

    public Book book;

    public User() {
    }

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    public int describeContents() {
        return 0;//Returns the content description of the current object, including the file descriptor. Returns 1, otherwise 0
    }

    public void writeToParcel(Parcel out, int flags) {//Writes the current object to the serial number structure
        out.writeInt(userId);
        out.writeString(userName);
        out.writeInt(isMale ? 1 : 0);
        out.writeParcelable(book, 0);
    }

    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        public User[] newArray(int size) {
            return new User[size];
        }
    };

    private User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readInt() == 1;
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }

    @Override
    public String toString() {
        return String.format(
                "User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
                userId, userName, isMale, book);
    }

}

  • The serialization function is completed by the writeToParcel method, and finally by a series of write methods in the Parcel.
  • The deserialization function is completed by CREATOR, which indicates how to create serial number objects and arrays, and completes the deserialization process through a series of read methods of Parcel.
  • The content description function is completed by the describeContents method. It returns 0 in almost all cases. It returns 1 only when there is a file descriptor for the current object.

contrast

  • Serializable is a serialization interface in Java, which is simple but expensive. Serialization and deserialization require a lot of IO operations.
  • Parseable is a serialization method in Android, which is troublesome but efficient.

2. Type of communication mode

Interprocess communication (IPC) mode

  • Using Bundle
  • Using file sharing
  • Using Messenger
  • Use AIDL
  • Using the COntentProvider
  • Using Socket

3. Using Bundle

We all know that the three major components in Android, Activity, Service and Receiver, all support the transfer of Bundle data in Intent, and Bundle implements the Parcelable interface, so it can be easily transmitted between different processes.

When we start the Activity, Service and Receiver of another process in one process, we can attach the information we need to transmit to the remote process in the Bundle and send it through intent. Note here that the data we transmit must be serializable.

Let's take an example of inter process communication using Bundle:

private void startWithIntent(){
    Intent intent = new Intent();
    //Specify the package name (the full package name must be written, otherwise an error will be reported) and address (activity name) of the program to be opened
    intent.setComponent(new ComponentName("PackageName", 
                        "PackageName.intentIpcActivity"));
    //Passing data through the budle can carry serialized data
    Bundle bundle = new Bundle();
    bundle.putInt("intextra", 0);
    bundle.putString("stringextra", "test data");
    intent.putExtras(bundle);
    try{
        startActivity(intent);
    }catch(Exception e){
        ToastUtils.showMessage("Corresponding file not found");
    }
}

It is easy to use Bundle for interprocess communication. We should also note that this way of interprocess communication can only be simple data transmission in one direction, and its use has certain limitations.

4. Using file sharing

Sharing files is also a good way to communicate between processes. Two processes exchange data by reading / writing the same file

  • For example, process A writes data to the FILE file, and process B can obtain the data by reading the FILE.
  • In this way, in addition to exchanging simple text information, we can also serialize an object into the file system, and another process can recover the object through deserialization.

For example, A creates A thread in the process to write data

new Thread(new Runnable(){
    @Override
    public void run(){
        User user = new User(1, "user", false);
        File cachedFile = new File(CACHE_FILE_PATH);
        ObjectOutputStream objectOutputStream = null;
        try{
            objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
            objectOutputStream.writeObject(user);
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            objectOutputStream.close();
        }
    }
}).start();

Create a thread in process B to read data

new Thread(new Runnable(){
    @Override
    public void run(){
        User user = null;
        File cachedFile = new File(CACHE_FILE_PATH);
        if (cachedFile.exists()){
            ObjectInputStream objectInputStream = null;
            try{
                objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
                user = objectInputStream.readObject(user);
            } catch(IOException e){
                e.printStackTrace();
            }finally{
                objectInputStream.close();
            }
        }

        try{
            objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
            objectOutputStream.writeObject(user);
        }catch (IOException e){
            e,printStackTrace();
        }finally{
            objectOutputStream.close();
        }
    }

  • Sharing data through file sharing has no specific requirements on the file format. For example, it can be a text file or an XML file, as long as the data format is agreed by the reading and writing parties.
  • Although this method is convenient for interprocess communication, it also has limitations, such as concurrent read / write, which will lead to serious problems, such as incomplete read data or not up-to-date read data.
  • Therefore, the way of file sharing is suitable for communication between processes that do not require high data synchronization, and the problem of concurrent read / write should be properly handled.
  • SharedPreferences is a special case. Although it is also a kind of file, the system has a cache of SharedPreferences file in memory. Therefore, in the multi-threaded mode, the read / write of the system becomes unreliable. High concurrent read-write SharedPreferences may lose data. Therefore, it is not recommended to use SharedPreferences in multi process communication.

5. Using Messenger

5.1 what is Messenger?

Messenger is a lightweight IPC scheme. Its underlying implementation is AIDL, which can transfer messenger objects in different processes and put the data we need to transfer in messenger.

  • It only processes one request at a time. There is no need to consider thread synchronization on the server, and there is no concurrent execution on the server.
  • The bottom implementation is AIDL, which is encapsulated. The Messenger server processes the client's requests in a serial manner, and there is no concurrent execution.
  • The advantage is that we can avoid defining ourselves Aidl file, using messenger defined in advance in the system aidl

Messenger is usually used together with Message and Handler. The Handler is encapsulated in messenger through messenger Send (Message) finally calls Handler sendMessage()

5.1.1 brief description of Messenger source code

Messenger has two constructors:

  • Take Handler as parameter
  • Take Binder as the parameter
private final IMessenger mTarget;
public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);    //Is it very similar to the previous AIDL
}
  • 1. Messenger construction method 1 (create clientMessenger)
    getIMessenger of Handler was called
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

  • 2. Messenger construction method 2 (get serviceMessenger)
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
  • 3. Look at the handler getIMessenger () source code: getIMessenger method obtains MessengerImpl
final IMessenger getIMessenger() {
    synchronized (mQueue) {
        if (mMessenger != null) {
            return mMessenger;
        }
        mMessenger = new MessengerImpl();
        return mMessenger;
    }
}

This IMessanger should also be a class generated by AIDL. Take a look at the source code. Sure enough, it is:

public interface IMessenger extends android.os.IInterface {
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements
            android.os.IMessenger {
        private static final java.lang.String DESCRIPTOR = "android.os.IMessenger";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static android.os.IMessenger asInterface(...}

        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 {...}

        private static class Proxy implements android.os.IMessenger {...}

    public void send(android.os.Message msg)
            throws android.os.RemoteException;
}

IMessenger is a cross process interface generated by AIDL, which defines a message sending method:

    public void send(android.os.Message msg)
            throws android.os.RemoteException;

MessengerImpl in the Handler implements the send method, which uses the Handler to send messages:

public void send(Message message) throws RemoteException {
    mTarget.send(message);
}

Therefore, there is the following code in the Handler

  • getIMessenger method to get MessengerImpl
  • Messenger impl inherits from imessenger Stub, and implements the send method, which finally calls handler sendMessage

summary

  • 1, Messenger holds a reference to IMessenger. In the constructor, you can get the final IMessenger implementation in the form of Handler or Binder, and then call its send() method.
  • 2. Messenger is actually a simplified version of AIDL. It encapsulates all the interfaces. We just need to create a Handler in one process and deliver it to messenger. Messenger helps us deliver messages across processes to another process, and our Handler in another process is processing messages.

5.2 use steps

There are the following steps to implement a Messenger, which is divided into server and client:

5.2.1. Server:

1. Create a Service to handle the connection request of the client

2. Create a Handler and use it to create a Messager object

3. Return the Binder at the bottom of the Messager object in the onBind of the Service

5.2.2 client

1. Bind the Server of this Server

2. Create a Messager object with the IBinder object returned by the server, and send a Message to the server through this Messager object

3. If the server needs to respond to the client, it needs to create a Handler and a new Messager, and pass it to the server through the replyTo parameter of Messa. The server can respond to the client through this replyTo parameter

5.3 use cases


1,AndroidManifest.xml add

        <service
            android:name=".MyService"
            android:process=":wangrui"></service>

2,activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_marginTop="50dp"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_ipc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="IPC connect"/>

    <Button
        android:id="@+id/btn_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:layout_gravity="center"
        android:text="IPC signal communication"/>

</LinearLayout>

3,MyBean.java

public class MyBean implements Parcelable {
    private String name;

    public MyBean(){

    }

    protected MyBean(Parcel in) {
        name = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
    }

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

    public static final Creator<MyBean> CREATOR = new Creator<MyBean>() {
        @Override
        public MyBean createFromParcel(Parcel in) {
            return new MyBean(in);
        }

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

    public String getName() {
        return name;
    }

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

4,MyService.java

1. Create a Service to handle the connection request of the client

2. Create a Handler and use it to create a Messager object

3. Return the Binder at the bottom of the Messager object in the onBind of the Service

public class MyService extends Service {

    private Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            //Client → server
            Bundle bundle = msg.getData();
            bundle.setClassLoader(MyBean.class.getClassLoader());
            MyBean myBean = bundle.getParcelable("message");
            Toast.makeText(MyService.this,myBean.getName(),Toast.LENGTH_SHORT).show();

            //Server → client
            try {
                Messenger clientMessenger = msg.replyTo;
                myBean = new MyBean();
                myBean.setName("Pikachu used 100000 volts to Wang Rui");
                bundle = new Bundle();
                bundle.putParcelable("message",myBean);
                Message message = new Message();
                message.setData(bundle);
                message.replyTo = clientMessenger;
                clientMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

    private Messenger messenger = new Messenger(handler);

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}

5,MainActivity.java

1. Bind the Server of this Server

2. Create a Messager object with the IBinder object returned by the server

3. To respond to the client, you need to create a Handler and a new Messager, and pass it to the server through the replyTo parameter of Messa. The server can respond to the client through this replyTo parameter

public class MainActivity extends AppCompatActivity {
    private Button btnIPC;
    private Button btnSend;
    private Messenger messengerProxy;
    private Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Bundle bundle = msg.getData();
            bundle.setClassLoader(MyBean.class.getClassLoader());
            MyBean myBean = bundle.getParcelable("message");
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this,myBean.getName(),Toast.LENGTH_SHORT).show();
                }
            },3000);
        }
    };

    private Messenger clientMessenger = new Messenger(handler);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnIPC = findViewById(R.id.btn_ipc);
        btnIPC.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,MyService.class);
                bindService(intent, new ServiceConnection() {
                    @Override
                    public void onServiceConnected(ComponentName name, IBinder iBinder) {
                        messengerProxy = new Messenger(iBinder);
                        Toast.makeText(MainActivity.this,"Connection succeeded",Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onServiceDisconnected(ComponentName name) {

                    }
                }, Context.BIND_AUTO_CREATE);
            }
        });

        btnSend = findViewById(R.id.btn_send);
        btnSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyBean myBean = new MyBean();
                myBean.setName("Wang Rui used the elf ball on Pikachu");
                try {
                    Message message = new Message();
                    message.replyTo = clientMessenger;
                    Bundle bundle = new Bundle();
                    bundle.putParcelable("message",myBean);
                    message.setData(bundle);
                    messengerProxy.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

5.4. Characteristic analysis

features:

  • Messenger internal message processing is implemented by Handler, so it processes the messages sent by the client in a serial manner
  • If a large number of messages are sent to the server, the server can only process them one by one. If the concurrency is large, Messenger is not suitable
  • Moreover, the main function of Messenger is to deliver messages. In many cases, we need to call the server method across processes, which Messenger can't do.

be careful:
  
The client and server send messages by getting each other's Messenger. Only the client uses bindService onServiceConnected and the server uses Message Replyto to get each other's Messenger. There is a Hanlder in Messenger that processes messages in the queue in a serial manner. There is no concurrent execution, so we don't need to consider thread synchronization.

6. Use AIDL

6.1 what is AIDL?

AIDL (Android Interface Definition Language) is an IDL language, which is used to generate code for interprocess communication (IPC) between two processes on Android devices. If you want to call the operation of an object in another process (such as Service) in one process (such as Activity), you can use AIDL to generate serializable parameters.

AIDL is a lightweight implementation of IPC, using a syntax familiar to Java developers. Android also provides a tool to automatically create a Stub (class architecture, class skeleton). When we need to communicate between applications, we need to follow the following steps:

  • Define an AIDL interface
  • Implement the corresponding Stub for the remote Service
  • "Expose" services to client applications

"Only when you allow different clients to access your service and need to deal with multithreading problems, you must use AIDL". In other cases, you can choose other methods, such as using Messager, or cross process communication. It can be seen that AIDL handles multithreading and multi client concurrent access. The Messager is single threaded.

6.2 use of AIDL

Data types supported by AIDL files

use

  • 1. Server
    • First, create a Service to listen to the connection request of the client,
    • Then create an AIDL file and declare the interface exposed to the client in the AIDL file,
    • Finally, implement the AIDL interface in the Service.
  • 2. Client
    • First bind the Service of the server,
    • After successful binding, convert the Binder object returned by the server into the type of AIDL interface and call the corresponding methods in AIDL.

6.3 precautions

1. If a custom Parcelable type is used in the ALDL file, you must create a new ALDL file with the same name and declare it as a Parcelable type.

2. In AIDL, in addition to the basic data type, other type parameters must be marked with direction: in, out or inout.

3. Only methods are supported in AIDL interface, and declaration of static constants is not supported.

4. In order to facilitate AIDL development, it is recommended to put all AIDL related classes and files in the same package. The advantage is that when the client is another application, we can directly copy the whole package to the client project.

5. The package structure of AIDL should be consistent between the server and the client, otherwise an error will occur.

6. The listener on the client side and the listener on the server side are not the same object. RemoteCallbackList is an interface specially provided by the system to delete cross process listeners. RemoteCallbackList is generic and supports the management of any AIDL interface, because all AIDL interfaces inherit from Android os. Iinterface interface.

7. It should be noted that when the AIDL client initiates an RPC process, the client thread will hang. If the UI thread initiates an RPC process, if the server processing events is too long, it will lead to ANR.

6.4 use cases

AIDL is easy to understand
1. New AIDL interface file

// RemoteService.aidl
package com.example.mystudyapplication3;

interface IRemoteService {

    int getUserId();

}

2. Create remote service

public class RemoteService extends Service {

    private int mId = -1;

    private Binder binder = new IRemoteService.Stub() {

        @Override
        public int getUserId() throws RemoteException {
            return mId;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mId = 1256;
        return binder;
    }
}

3. Declare remote services

<service
    android:name=".RemoteService"
    android:process=":aidl" />

4. Bind remote service

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "wzq";

    IRemoteService iRemoteService;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iRemoteService = IRemoteService.Stub.asInterface(service);
            try {
                Log.d(TAG, String.valueOf(iRemoteService.getUserId()));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iRemoteService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService(new Intent(MainActivity.this, RemoteService.class), mConnection, Context.BIND_AUTO_CREATE);
    }
}

7. Using ContentProvider

7.1 definition and use of ContentProvider

All the knowledge about ContentProvider is here!

Content provider is one of the four components in Android. In order to exchange data between applications, Android provides content provider

  • ContentProvider is an API for data exchange between different applications. Once an application exposes its own data operation interface through ContentProvider, no matter whether the application is started or not, other applications can operate the data in the interface through the interface, including adding, deleting, modifying, querying, etc.
  • ContentProvider is divided into system and user-defined, that is, data such as contacts and pictures.

Steps for developing a ContentProvider:

  • 1. Define your own ContentProvider class, which inherits the ContentProvider base class;
  • 2. At androidmanifest Registering this ContentProvider in XML is similar to Activity registration. When registering, bind a domain name to the ContentProvider;
  • 3. After we register the ContentProvider, other applications can access the data exposed by the ContentProvider.

The ContentProvider only exposes data that can be operated by other applications. Other applications need to operate the data exposed by the ContentProvider through the contentrepliver. Context provides the getContentResolver() method to obtain the ContentProvider object. After obtaining it, you can add, delete, modify and query the exposed data.

To manipulate data using ContentResolver:

  • 1. Call getContentResolver() of Activity to get the ContentResolver object
  • 2. Operate the database according to the insert(), delete(), update(), and query() methods of the called ContentResolver.

8. Comparison of advantages and disadvantages of different IPC

reference resources

1,[learning notes] exploration of Android development Art: IPC mechanism
2,Android foundation - Android interprocess communication mode
3,Messenger enables two-way communication across processes
4,Android interview through this article, there is no offer you can't get!

Topics: Java Android