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
- Basic data types
- String CharSequence
- Parcelable
- Aidl document
- List(ArrayList only supported)
- 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.