Android IPC overview

This paper use code: : https://github.com/poecook/IPCDemo

Open the process

The Android :process attribute is specified in the AndroidManifest as follows:

 <service
    android:process=":remote"
    android:name=".ServerService">
Copy the code

Problems with multiple processes

Four components of an Application are in different processes, which means that they have independent running space, VIRTUAL machine and Application, which will bring the following problems:

  • Singleton mode, static variable failure.
  • Application is created multiple times.
  • Thread synchronization failure
  • SharePreferences reliability deteriorates.

The relationship between processes and threads

  • Threads and processes are contained and contained relationships.
  • Threads are the CPU’s smallest unit of scheduling.
  • A process can contain multiple threads and refers to a unit of execution

Android IPC Implementation (AIDL)

There are many different IPC methods for Android, but the most powerful is undoubtedly AIDL. Let’s look at how to use AIDL for inter-process communication.

Overall process

The service side

  • Create a Service and wait for the client to send a connection request.
  • Create an AIDL file in which the interface to be exposed for use by the client is declared.
  • Implement this interface in a Service.

The client

  • Bind the Service to the server
  • The returned binder object is converted to the type of the AIDL interface.
  • Use methods in AIDL

The specific implementation

We simulate the behavior of storing headphones in a store, including adding headphones, checking what headphones are available, and notifying them when they are available.

AIDL interface created

Supported data types

The following six types of data are supported

  1. Basic data types
  2. String CharSequence
  3. Parcelable
  4. Aidl document
  5. List(ArrayList only supported)
  6. Map (HashMap only)

Notes for creating AIDL

  • Where ADIL and Parcelable must display import regardless of whether they are in a package or not
  • If an AIDL uses a parceable object, you need to create an AIDL file with the same name and declare the object in the file.
  • In addition to the basic data type, other type parameters must specify the direction. In, out, or inout.
  • Aidl does not support static constants. //Headphones.java
package com.weshape3d.ipcdemo;

import android.os.Parcel;
import android.os.Parcelable;

public class Headphones implements Parcelable {
    public int id;
    public String brand;
    public String name;
    public String price;

    protected Headphones(Parcel in) {
        id = in.readInt();
        brand = in.readString();
        name = in.readString();
        price = in.readString();
    }

    public Headphones(int id, String brand, String name, String price) {
        this.id = id;
        this.brand = brand;
        this.name = name;
        this.price = price;
    }

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

        @Override
        public Headphones[] newArray(int size) {
            returnnew Headphones[size]; }}; @Override public intdescribeContents() {
        return0; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(id); parcel.writeString(brand); parcel.writeString(name); parcel.writeString(price); }}Copy the code

//Headphones.aidl

package com.weshape3d.ipcdemo;

 parcelable Headphones;

Copy the code

// HeadphonesManager.aidl

package com.weshape3d.ipcdemo;

import com.weshape3d.ipcdemo.Headphones;
import  com.weshape3d.ipcdemo.IArrivedListener;
interface HeadphonesManager{
   void addHeadphones(in Headphones hp);
    List<Headphones> getHeadphoneList();
    void registListener(IArrivedListener listener);
    void unRegistListener(IArrivedListener listener);
}

Copy the code

// IArrivedListener.aidl

package com.weshape3d.ipcdemo;
import com.weshape3d.ipcdemo.Headphones;
interface IArrivedListener {
   void onArrived(in Headphones hp);
}

Copy the code

It is ok to finish the build in app/build/generated under the aidl/debug saw the aidl tool to help us to generate a good Java classes. We can use it.

Server side concrete implementation

The server implements the binder. stub method and then returns it in the Service.

Deregistration failure

The reason is that when we pass through Binder to the server, we create two entirely new objects. The nature of object transfer across processes is deserialization.

RemoteCallBackList is used to unregister the listener of a process.

Precautions for the server

  • Aidl methods implemented on the server run in the server’s thread pool, which requires thread-safety when multiple clients connect to the server.
  • Server-side methods run in a thread pool and are not afraid to enable time-consuming operations.
package com.weshape3d.ipcdemo;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ServerService extends Service {
    private  CopyOnWriteArrayList<Headphones> headphonesCopyOnWriteArrayList = new CopyOnWriteArrayList<>();
    private RemoteCallbackList<IArrivedListener> arrivedListenerRemoteCallbackList = new RemoteCallbackList<>();

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
    Binder binder = new HeadphonesManager.Stub() {
        @Override
        public void addHeadphones(Headphones hp) throws RemoteException {
           haveNewHeadphones(hp);
        }
        @Override
        public List<Headphones> getHeadphoneList() throws RemoteException {
            returnheadphonesCopyOnWriteArrayList; } @Override public void registListener(IArrivedListener listener) throws RemoteException { arrivedListenerRemoteCallbackList.register(listener); } @Override public void unRegistListener(IArrivedListener listener) throws RemoteException { arrivedListenerRemoteCallbackList.unregister(listener); }}; /** * add new headphones * @param headphones */ private void haveNewHeadphones(headphones){ headphonesCopyOnWriteArrayList.add(headphones); int n = arrivedListenerRemoteCallbackList.beginBroadcast();for(int i = 0; i<n; i++){ IArrivedListener listener = arrivedListenerRemoteCallbackList.getBroadcastItem(i); try { listener.onArrived(headphones); } catch (RemoteException e) { e.printStackTrace(); } } arrivedListenerRemoteCallbackList.finishBroadcast(); }}Copy the code

Client side concrete implementation

package com.weshape3d.ipcdemo;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;


import java.util.List;



public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView tv_show;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.bt).setOnClickListener(this);
        tv_show = findViewById(R.id.tv);
        Intent intent = new Intent(this,ServerService.class);
        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
    }
    HeadphonesManager headphonesManager;
    ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { headphonesManager = HeadphonesManager.Stub.asInterface(iBinder); Try {/ / registered to monitor headphonesManager. RegistListener (new IArrivedListener.Stub() {
                    @Override
                    public void onArrived(Headphones hp) throws RemoteException {
                        Log.d("drummor"."I got a new book, old man."+hp.brand); }}); } catch (RemoteException e) { e.printStackTrace(); } try {// Add ibInder.linkToDeath (DeathRecipient,0); tv_show.setText("Connection successful");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };
    private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if(headphonesManager==null)
                return; headphonesManager.asBinder().unlinkToDeath(deathRecipient,0); headphonesManager = null; }}; @Override protected voidonDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }

    @Override
    public void onClick(View view) {
        if(headphonesManager! =null){ Headphones headphones = new Headphones(1,"bose".""."2018"); try { headphonesManager.addHeadphones(headphones); List<Headphones> arrayList = headphonesManager.getHeadphoneList(); tv_show.setText(arrayList.toString()); } catch (RemoteException e) { e.printStackTrace(); }}}}Copy the code

Precautions for clients

  • When calling remote methods, you need to be careful not to easily ANR them in the main thread.
 ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { headphonesManager = HeadphonesManager.Stub.asInterface(iBinder); Try {/ / registered to monitor headphonesManager. RegistListener (new IArrivedListener.Stub() {
                    @Override
                    public void onArrived(Headphones hp) throws RemoteException {
                        Log.d("drummor"."I got a new book, old man."+hp.brand); }}); } catch (RemoteException e) { e.printStackTrace(); } try {// Add ibInder.linkToDeath (DeathRecipient,0); tv_show.setText("Connection successful");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };
Copy the code

Register the listener with the server

Registering listeners to the server in the normal way may cause problems when removing listeners. The reason is that a Binder converts objects passed by a client into new objects, which is essentially a deserialization process. RemoteCallbackList is provided to register a listener for deleting cross-processes.

Use the RemoteCallbackList

RemoteCallbackList must be paired with beginBroadcast and finishBroadcast. The following

final int N =  mListenerList.beginBroadcast();
for(int I = 0; i<N; I++) {Listener. L = mListenerList getBroadcastItem (I)if(l! =null){ l.callBack(); / / call listen}} mListenerList. FinishBroadcast ();Copy the code

At the top of the concrete can be used in our demo code examples or lot code: https://github.com/poecook/IPCDemo

Summary of problems

  • Aidl compilation fails when the file structure under AIDL is inconsistent with the main file structure
  • Object serialization using Parcelable
  • Listen remotely using RemoteCallbackList
  • Death listeners use DeathRecipient, which is called when terminated outside the remote binder.
  • Remote methods run in the server’s thread pool, so there may be time consuming possibilities, so NAR can occur when a client calls a remote time consuming operation.