In the last article we looked at some of the concepts of Service and how to use it. This article focuses on 2 ways to use Service for IPC communication.

IPC communication with AIDL

A, code implementation — remote process Service binding

The above code communicates with the Service in the current process. Now let’s implement how to bind the Service in different processes.

AIDL:Android Interface Definition Language.

Service transfers data across processes using AIDL. The main steps are as follows:

  1. Write AIDL files, AS automatically generated Java classes to implement the proxy for IPC communication
  2. Inherit your own AIDL class and implement the methods inside
  3. Return our implementation class in onBind(), exposing it to the outside world
  4. Objects that need to communicate with a Service are bound to the Service through bindService and receive data in ServiceConnection.

Let’s do this in code:

  1. First we need to create a new Service

    public class MyRemoteService extends Service {
    	@Nullable
    	@Override
    	public IBinder onBind(Intent intent) {
      		Log.e("MyRemoteService"."MyRemoteService thread id = " + Thread.currentThread().getId());
        return null; }}Copy the code
  2. In the manifest file, we declare that our Service also specifies the name of the process to run

    <service
            android:name=".service.MyRemoteService"
            android:process=":remote" />
    
    Copy the code
  3. Create a new AIDL file for users to transfer data between processes.

    AIDL supports the following types: Eight basic data types, String, CharSequence, List, Map, and custom types. Lists, maps, and custom types are explained below.

    There will be a default implementation method, just delete, here we create a new file as follows:

    package xxxx;// The package name where aidl resides
    //interface cannot be preceded by modifiers
    interface IProcessInfo {
    	// All the credit methods you want can be added here
    	int getProcessId(a);
    }
    Copy the code
  4. Implement our AIDL class

    public class IProcessInfoImpl extends IProcessInfo.Stub {
    	@Override
    	public int getProcessId(a) throws RemoteException {
      		returnandroid.os.Process.myPid(); }}Copy the code
  5. Return in onBind() of the Service

    public class MyRemoteService extends Service {
    	IProcessInfoImpl mProcessInfo = new IProcessInfoImpl();
    	@Nullable
    	@Override
    	public IBinder onBind(Intent intent) {
      		Log.e("MyRemoteService"."MyRemoteService thread id = " + Thread.currentThread().getId());
        returnmProcessInfo; }}Copy the code
  6. Binding Service

     mTvRemoteBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyRemoteService.class); bindService(intent, mRemoteServiceConnection, BIND_AUTO_CREATE); }}); mRemoteServiceConnection =new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
                Log.e("MainActivity"."MyRemoteService onServiceConnected");
    			// Retrieve data from aiDL
                IProcessInfo processInfo = IProcessInfo.Stub.asInterface(service);
                try {
                    Log.e("MainActivity"."MyRemoteService process id = " + processInfo.getProcessId());
                } catch(RemoteException e) { e.printStackTrace(); }}@Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e("MainActivity"."MyRemoteService onServiceDisconnected"); }};Copy the code

As long as the binding is successful, the process ID of MyRemoteService will be printed in the log. This completes the process of communicating with the services of different processes.

Two, code operation — call Service of other app

Calling a Service defined by another app has a few subtle differences compared to calling a Service defined by a different app process

  1. The implicit invocation used by bindService() is not appropriate because it needs to be accessed by other apps. Action needs to be defined at Service definition time

    We define the following Service in App A of the thread we define:

    <service android:name=".service.ServerService"> <intent-filter android:name="com.jxx.server.service.bind" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>Copy the code
  2. We need to do this in App B where we need bindService

    • First, copy the aidl file defined in A to B. For example, copy the iprocessInfo. aidl file defined above, including the path.

    • Call Service in B by explicit call

      mTvServerBind.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              Intent intent = new Intent();
              intent.setAction("com.jxx.server.service.bind");/ / Service action
              intent.setPackage("com.jxx.server");//App A package namebindService(intent, mServerServiceConnection, BIND_AUTO_CREATE); }});Copy the code
Passing of custom objects in AIDL

The main steps are as follows:

  1. To define custom objects, implement the Parcelable interface
  2. Create an AIDL file for a custom object
  3. Reference custom objects in the AIDL file where the data is passed
  4. Copy custom objects and aiDL files to the app that requires bindService, leaving the main path intact

Let’s look at the code:

  1. Define custom objects and implement the Parcelable interface

    public class ServerInfo implements Parcelable {
    
    public ServerInfo(a) {
    
    }
    
    String mPackageName;
    
    public String getPackageName(a) {
        return mPackageName;
    }
    
    public void setPackageName(String packageName) {
        mPackageName = packageName;
    }
    
    protected ServerInfo(Parcel in) {
        mPackageName = in.readString();
    }
    
    public static final Creator<ServerInfo> CREATOR = new Creator<ServerInfo>() {
        @Override
        public ServerInfo createFromParcel(Parcel in) {
            return new ServerInfo(in);
        }
    
        @Override
        public ServerInfo[] newArray(int size) {
            return newServerInfo[size]; }};@Override
    public int describeContents(a) {
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mPackageName);
    }
    
    You need to add this method yourself when using out or inout modifiers
    public void readFromParcel(Parcel dest) { mPackageName = dest.readString(); }}Copy the code
  2. Create an AIDL file for a custom object

    package com.jxx.server.aidl;
    // Note that parcelable is lowercase
    parcelable ServerInfo;
    Copy the code
  3. Reference custom objects

    package com.jxx.server.aidl;
    // Even in the same package, here also need to guide the package
    import com.jxx.server.aidl.ServerInfo;
    interface IServerServiceInfo {
    	ServerInfo getServerInfo(a);
    	void setServerInfo(inout ServerInfo serverinfo);
    }
    Copy the code

    Notice the set method here, which uses inout, there are three modifiers

    -out: The modification of the server is synchronized to the client, but the object obtained by the server may be empty. -inout: the modification is synchronizedCopy the code

    When using out and inout, add readFromParcel(Parcel Dest) manually in addition to Parcelable

  4. Copy custom objects and aiDL files into the referenced App.

  5. reference

    mServerServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IServerServiceInfo serverServiceInfo = IServerServiceInfo.Stub.asInterface(service);
                try {
                    ServerInfo serviceInfo = serverServiceInfo.getServerInfo();
                    Log.e("MainActivity"."ServerService packageName = " + serviceInfo.getPackageName());
                } catch(RemoteException e) { e.printStackTrace(); }}@Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e("MainActivity"."ServerService onServiceDisconnected"); }};Copy the code

The objects referenced in a List or Map should also be custom objects that meet the above requirements, or any of several other data types.

Use Messenger for IPC communication

Here’s how it works:

  1. Create a new Messenger object on the Server in response to the Client’s registration and pass it in onBind()
  2. In the ServiceConnection on the Client side, the Messenger object passed from the Server side is saved
  3. At the same time, the Client also creates a Messenger object and registers it with the Server through the Messenger transferred from the Server to maintain communication.
  4. As long as the Client keeps the Messenger object on the Server, it can still communicate with the Server, regardless of whether the unbindService() operation is performed.

First, server-side code

public class MessengerService extends Service {

    static final int MSG_REGISTER_CLIENT = 1;
    static final int MSG_UNREGISTER_CLIENT = 2;
    static final int MSG_SET_VALUE = 3;

    // This is used by the client to receive parameters
    static final int MSG_CLIENT_SET_VALUE = 4;

    static class ServiceHandler extends Handler {

        private final List<Messenger> mMessengerList = new ArrayList<>();

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_REGISTER_CLIENT:
                    mMessengerList.add(msg.replyTo);
                    break;
                case MSG_UNREGISTER_CLIENT:
                    mMessengerList.remove(msg.replyTo);
                    break;
                case MSG_SET_VALUE:
                    int value = msg.arg1;
                    for (Messenger messenger : mMessengerList) {
                        try {
                            messenger.send(Message.obtain(null, MSG_CLIENT_SET_VALUE, value, 0));
                        } catch(RemoteException e) { e.printStackTrace(); }}break;
                default:
                    super.handleMessage(msg); }}}private Messenger mMessenger = new Messenger(new ServiceHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        returnmMessenger.getBinder(); }}Copy the code

Second, the Client code

public class MessengerClientActivity extends AppCompatActivity {
  // These types need to correspond to the Server side
    static final int MSG_REGISTER_CLIENT = 1;
    static final int MSG_UNREGISTER_CLIENT = 2;
    static final int MSG_SET_VALUE = 3;
    static final int MSG_CLIENT_SET_VALUE = 4;

    class ClientHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {

            if (msg.what == MSG_CLIENT_SET_VALUE) {
                mTvValue.setText(msg.arg1 + "");
            } else {
                super.handleMessage(msg);
            }
        }
    }

    TextView mTvServerBind;
    TextView mTvServerUnbind;
    TextView mTvValue;
    TextView mTvSend;

    ServiceConnection mServerServiceConnection;
    Messenger mServerMessenger;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_messenger);

        mTvServerBind = findViewById(R.id.tv_server_bind);
        mTvServerUnbind = findViewById(R.id.tv_server_unbind);
        mTvValue = findViewById(R.id.tv_value);
        mTvSend = findViewById(R.id.tv_send);

        mTvServerBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("jxx.com.server.service.messenger");
                intent.setPackage("jxx.com.server"); bindService(intent, mServerServiceConnection, BIND_AUTO_CREATE); }}); mTvServerUnbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Even if we unbindService, as long as we still have the mServerMessenger object,
                // We can continue to communicate with the ServerunbindService(mServerServiceConnection); }}); mTvSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mServerMessenger ! =null) {
                    try {
                        // Test if you can set the data
                        Message test = Message.obtain(null, MSG_SET_VALUE, new Random().nextInt(100), 0);
                        mServerMessenger.send(test);
                    } catch(RemoteException e) { e.printStackTrace(); }}}}); mServerServiceConnection =new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // Messenger on the server
                mServerMessenger = new Messenger(service);

                // Now start building the messenger client uses to send and receive messages
                Messenger clientMessenger = new Messenger(new ClientHandler());

                try {
                    // Register the client with the server
                    Message register = Message.obtain(null, MSG_REGISTER_CLIENT);
                    register.replyTo = clientMessenger;// This is the registered operation. We can see the object being fetched in the Server code above
                    mServerMessenger.send(register);

                    Toast.makeText(MessengerClientActivity.this."Binding successful", Toast.LENGTH_SHORT).show();

                } catch(RemoteException e) { e.printStackTrace(); }}@Override
            public void onServiceDisconnected(ComponentName name) {}}; }Copy the code