1. Multiple processes in Android

1, define,

First of all, process generally refers to an execution unit, in mobile devices is a program or application, we say in Android multi-process (IPC) generally means that an application contains multiple processes. The reason for using multiple processes is twofold: some modules need to run in separate processes due to special needs; Increase the available memory space of the application.

2. Start multiple processes

The only way to start multithreading in Android is to specify the Android :process attribute when registering Service, Activity, Receiver, and ContentProvider in androidManifest.xml. For example:

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

<activity
    android:name=".MyActivity"
    android:process="com.shh.ipctest.remote2">
</activity>
Copy the code

The values of the Android :process attribute we specify for MyService and MyActivity are different. The differences are as follows:

  • :remoteTo::The system will attach the current package name before the current process name, and the full process name:com.shh.ipctest:remoteAnd at the same time to:The initial process belongs to the private process of the current application. Components of other applications cannot run in the same process with it.
  • com.shh.ipctest.remote2: This is the complete naming method and does not append package names to other applications and processesShareUID,The signatureSame, can run with it in the same process, the realization of data sharing.
3. Problems caused by multiple processes

Although it is simple to start multiple processes, the following issues must be noted.

  • Static member and singleton modes fail
  • The thread synchronization mechanism is invalid
  • The reliability of SharedPreferences decreases
  • The Application is created multiple times

For the first two problems, it can be understood as follows: in Android, the system allocates independent virtual machines for each application or process. Different virtual machines naturally occupy different memory address Spaces, so objects of the same class will produce different copies, resulting in data sharing failure, and inevitably cannot achieve thread synchronization.

Since SharedPreferences uses XML files to read and write, multiple concurrent reads and writes may cause data exceptions.

Similar to the previous two problems, when the system allocates multiple VMS, it is equivalent to restarting the same Application for several times, which will inevitably lead to the creation of the Application for several times. In order to prevent unnecessary repeated initialization in the Application, you can use the process name to filter. Set global initialization only for the specified process:

public class MyApplication extends Application{
    @Override
    public void onCreate(a) {
        super.onCreate();
        String processName = "com.shh.ipctest";
        if (getPackageName().equals(processName)){
            // do some init}}}Copy the code
4. Multi-process communication in Android

Android supports the following multi-process communication modes, each of which has its own advantages and disadvantages. You can select one based on the application scenario:

  • AIDL: powerful, supports one-to-many real-time concurrent communication between processes, and can implement RPC (remote procedure call).
  • Messenger: Supports one-to-many serial real-time communication, a simplified version of AIDL.
  • Bundle: a process communication mode of the four components. Only the data types supported by the Bundle can be transmitted.
  • ContentProvider: Powerful data source access support, mainly CRUD operations, one-to-many data sharing between processes, such as our application access system address book data.
  • BroadcastReceiver: indicates a broadcast, but only one-way communication. The receiver can only passively receive messages.
  • File sharing: Sharing simple data without high concurrency.
  • Socket: transmits data over the network.

Here we mainly discuss the four components of the Service in the use of multi-process communication, which involves AIDL, Messenger these two multi-process communication, next focus on the two IPC methods.

Second, the AIDL

AIDL stands for Android Interface Definition Language. Using AIDL to communicate between processes requires defining a server and a client, which can be in the same application or in different applications. Here, our server can be regarded as a library, providing the client with the latest book inquiry, book donation, new book notification service.

1. Server implementation

Create an AIDL file that declares the interface the server wants to expose to the client. Then create a Service that listens for client connection requests and implements the interface in the AIDL file.

Note that for ease of development, aiDL-related files are generally kept in the same package so that when the client is another application, the entire package can be copied to the client project. The final AIDL package is as follows:

First, look at the data types supported by AIDL files:

  • Basic data type
  • String, CharSequence
  • ArrayList, HashMap, and their internal elements also need to be supported by AIDL
  • Object that implements the Parcelable interface
  • The interface is of the AIDL type and is not a common interface

If the AIDL file uses a custom Parcelable object, you need to provide a book. AIDL file as well:

package com.shh.ipctest;

parcelable Book;
Copy the code

Ilibrarymanager. aidl defines the interface that the server exposes to the client:

package com.shh.ipctest;

import com.shh.ipctest.Book;

interface ILibraryManager{
    // Recent book search
    List<Book> getNewBookList(a);
    // Book donation
    void donateBook(in Book book);
}
Copy the code

Note that although ilibraryManager.aidl and Book are in the same package, you still need to display the import Book class. In addition to the basic type of data, other types of parameters need to be marked with directions. The optional directions are as follows:

  • in: Input type parameter
  • out: Output type parameter
  • inout: Input/output type parameters

Next comes the LibraryManagerService service class. Before writing the service class, compile the project so that you can use the Java class generated by AIDL in the service class.

public class LibraryManagerService extends Service {

    private static final String TAG = "LibraryManagerService";

    // CopyOnWriteArrayList supports concurrent read and write
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
   
    private Binder mBinder = new ILibraryManager.Stub() {

        @Override
        public List<Book> getNewBookList(a) throws RemoteException {
            return mBookList;
        }

        @Override
        public void donateBook(Book book) throws RemoteException { mBookList.add(book); }};public LibraryManagerService(a) {}@Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onCreate(a) {
        super.onCreate();
        mBookList.add(new Book("book0"));
        mBookList.add(new Book("book1")); }}Copy the code

First create an mBinder object through ilibraryManager.stub (), implement the interface methods defined in ilibraryManager.aidl, return the created mBinder in the onBind() method, and add two books when serving onCreate(). Finally register the service in AndroidManifest.xml:

<service
    android:name=".LibraryManagerService"
    android:process=":aidl_remote">
</service>
Copy the code

At this point the basic functionality of the server is complete.

2. Client implementation

The AIDL file path is specified in the build.gradle of the project app:

android {
    ......

    // Specify the aiDL path
    sourceSets {
        main {
            java.srcDirs = ['src/main/java'.'src/main/aidl']}}}Copy the code

Then there’s the binding service:

public class AIDLActivity extends AppCompatActivity {
    private static final String TAG = "AIDLActivity";

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            ILibraryManager libraryManager = ILibraryManager.Stub.asInterface(service);
            try {
                // Recent book search
                List<Book> books = libraryManager.getNewBookList();
                Log.e(TAG, "books:" + books.toString());
                // Donate a book
                libraryManager.donateBook(new Book("book" + books.size()));
                List<Book> books2 = libraryManager.getNewBookList();
                Log.e(TAG, "books:" + books2.toString());
            } catch(RemoteException e) { e.printStackTrace(); }}@Override
        public void onServiceDisconnected(ComponentName name) {}};@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        bindNewService();
    }

    private void bindNewService(a) {
        Intent intent = new Intent(AIDLActivity.this, LibraryManagerService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy(a) {
        unbindService(mServiceConnection);
        super.onDestroy(); }}Copy the code

The IBinder object is called an ILibraryManager object with an onServiceConnected() method. Then the IBinder object is called an IlibraryManager.aidl method. Next, bind the service:

Intent intent = new Intent(AIDLActivity.this, LibraryManagerService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
Copy the code

After running the project, observe the log:

What if the client and server are in different applications? First copy the AIDL package on the server to the main directory of the client project, specify the AIDL file path in the build.gradle of the client project app, and finally bind the service. Because the client needs to implicitly bind the service, Change the LibraryManagerService registry mode to:

<service
    android:name=".LibraryManagerService"
    android:process=":aidl_remote">
    <intent-filter>
        <action android:name="android.intent.action.LibraryManagerService" />
    </intent-filter>
</service>
Copy the code

The next step is to implicitly start the service on the client side:

Intent intent = new Intent();
intent.setAction("android.intent.action.LibraryManagerService");
intent.setPackage("com.shh.ipctest");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
Copy the code
3, notifications,

If you want to add a new book reminder function, that is, the library needs to notify users who subscribe to the new book reminder function after purchasing a new book, how to modify the server and client?

Begin by creating a server notify the client IOnNewBookArrivedListener. An aidl interface:

package com.shh.ipctest;

import com.shh.ipctest.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book book);
}
Copy the code

We have agreed that the server must register before receiving notifications, and can also unregister, so we need to add a method to the previous ilibraryManager.aidl:

package com.shh.ipctest;

import com.shh.ipctest.Book;
import com.shh.ipctest.IOnNewBookArrivedListener;

interface ILibraryManager{...// Registration notification
    void register(IOnNewBookArrivedListener listener);
    // Cancel registration
    void unregister(IOnNewBookArrivedListener listener);
}
Copy the code

Next, modify LibraryManagerService on the server:

Public Class LibraryManagerService extends Service {...... / / the system to provide dedicated to save, delete across processes the listener class private RemoteCallbackList < IOnNewBookArrivedListener > mListenerList = new RemoteCallbackList<>(); // AtomicBoolean supports concurrent read and write private AtomicBoolean mIsServiceDestroy = new AtomicBoolean(false);

    private Binder mBinder = new ILibraryManager.Stub() {... @Override public void register(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); Log.e(TAG,"register success");
        }

        @Override
        public void unregister(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
            Log.e(TAG, "unregister success"); }}; . @Override public voidonCreate() { super.onCreate(); . // Create a new book every 3 seconds in the child Thread and notify all registered clients of new Thread(new)Runnable() {
            @Override
            public void run() {// If the service has not terminatedwhile(! mIsServiceDestroy.get()) { try { Thread.sleep(3 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } Book book = new Book("book" + mBookList.size());
                    mBookList.add(book);
                    bookArrivedNotify(book);
                }
            }
        }).start();
    }
    
    private void bookArrivedNotify(Book book) {
        int n = mListenerList.beginBroadcast();
        for (int i = 0; i < n; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if(listener ! = null) { try { listener.onNewBookArrived(book); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } @Override public voidonDestroy() {
        super.onDestroy();
        mIsServiceDestroy.set(true); }}Copy the code

Note the use of the RemoteCallbackList class, which is a system-provided class for removing cross-process listeners. It is difficult to ensure that the listener registered by the client and the listener stored by the server are the same with a normal collection, and the unregistration will fail. The binding and unbinding of the notification interface are implemented in register() and unregister(). Periodically notifies the client of a new book in onCreate().

The notification interface needs to be registered and deregistered on the client:

Public class AIDLActivity extends AppCompatActivity {...... private ILibraryManager mLibraryManager; private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.e(TAG, "new book:" + msg.obj);
                    break;
            }
            return true; }}); private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {@override public void onNewBookArrived(Book Book) throws RemoteException {// Because onNewBookArrived is called on the child thread, Mhandler.obtainmessage (MESSAGE_NEW_BOOK_ARRIVED, book).sendtoTarget (); }}; private ServiceConnection mServiceConnection = newServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ILibraryManager libraryManager = ILibraryManager.Stub.asInterface(service); mLibraryManager = libraryManager; try { ...... Librarymanager.register (listener); } catch (RemoteException e) { e.printStackTrace(); }}... }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_aidl);
        bindNewService();
    }

    @Override
    protected void onDestroy() {
        unbindService(mServiceConnection);
        if(mLibraryManager ! = null && mLibraryManager. AsBinder () isBinderAlive ()) {try {/ / cancellation registration mLibraryManager unregister (the listener); } catch (RemoteException e) { e.printStackTrace(); } } super.onDestroy(); }}Copy the code

First is to create IOnNewBookArrivedListener interface object, in order to facilitate the UI operations can be in a callback Handler shall notify the result of the server to switch to the UI thread, and print the new title, the onServiceConnected () in the registration interface, Unregister in onDestroy(). After running the project, look at the log of client and server:

4. Permission verification

At present, our server can be connected to any client. For the sake of security in the project development, we need to do permission verification on the server. Only when the client is configured with the specified permission can the method on the server be invoked.

A server can verify this by using permission+ package name. First declare a permission on the server’s Androidmanifest.xml:

<permission
        android:name="com.shh.ipctest.permission.ACCESS_LIBRARY_SERVICE"
        android:protectionLevel="normal" />
Copy the code

The next step is to verify in LibraryManagerService that the client to connect to has the permission configured and that the package name complies with the specified rules. The first is to verify in onBind() :

@Override
public IBinder onBind(Intent intent) {
    if(! passBindCheck()) { Log.e(TAG,"bind denied");
        return null;
    }
    return mBinder;
}

private boolean permissionCheck(a) {
    // Whether the client has applied for the specified permission
    int check = checkCallingOrSelfPermission("com.shh.ipctest.permission.ACCESS_LIBRARY_SERVICE");
    if (check == PackageManager.PERMISSION_DENIED) {
        return false;
    }

    // Verify that the client package name starts with com.shh
    String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
    if(packages ! =null && packages.length > 0 && !packages[0].startsWith("com.shh")) {
        return false;
    }
    return true;
}
Copy the code

Note that onBind() is called when the client connects to the server. If the client fails to pass validation here, it will not connect to the service. If the client and server are two applications, then onBind() cannot implement the verification function!

The second place is in the onTransact() method of the IlibraryManager.Stub class. The validation in this method solves the problem of onBind() not being able to implement the validation when the client and server are two applications.

private Binder mBinder = new ILibraryManager.Stub() {
    ......
    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        if(! permissionCheck()) { Log.e(TAG,"bind denied");
            return false;
        }
        return super.onTransact(code, data, reply, flags); }};Copy the code

Note that this method executes after onBind(), meaning that the client and server are already connected, and the client will invoke the onTransact() method when calling the server method.

Finally, you need to configure permission in androidmanifest.xml on the client:

<uses-permission android:name="com.shh.ipctest.permission.ACCESS_LIBRARY_SERVICE" />
Copy the code
5. Reconnect

The service can be killed when the server process unexpectedly terminates due to reasons such as insufficient memory, so it is necessary to reconnect to the service in this case. There are two ways to connect:

The first, which is relatively simple, is to reconnect the service in the onServiceDisconnected() method of the ServiceConnection interface, which is executed in the UI thread.

The second is to register a DeathRecipient listener for the Binder object that the client gets. First create the listener:

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if(mLibraryManager ! = null) { mLibraryManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mLibraryManager = null; // Reconnect the servicebindNewService(); }}}Copy the code

The binderDied() method is called when the service is killed, and the next step is to set the death listener after the service has been successfully connected:

@Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        ILibraryManager libraryManager = ILibraryManager.Stub.asInterface(service);
        mLibraryManager = libraryManager;
        try {
            // Turn mLibraryManager into a Binder object and register the death listen
            mLibraryManager.asBinder().linkToDeath(mDeathRecipient, 0);
        } catch(RemoteException e) { e.printStackTrace(); }... }Copy the code

Third, Binder

In the AIDL example, the communication between the client and the server is realized by the Binder classes provided by the system, which implement the IBinder interface. We used the Binder classes in the previous example. For example, create a Binder object in the Service of the server and return onBind() :

Binder mBinder = new ILibraryManager.Stub(){
......
}
Copy the code

When the client successfully binds the service, the Binder object returned by the server is converted into an ILibraryManager object that can call the method exposed by the server:

private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ILibraryManager libraryManager = ILibraryManager.Stub.asInterface(service); . }... };Copy the code

You can see that both the client and the server use the ilibraryManager.java class. Recall that we created an ilibraryManager.aidl file earlier. So ilibraryManager. Java should be generated by ilibraryManager. aidl, and it should be in the following directory:

ILibraryManager.java

public interface ILibraryManager extends android.os.IInterface {
    
    public static abstract class Stub extends android.os.Binder implements com.shh.ipctest.ILibraryManager {
        // Unique identifier of Binder
        private static final java.lang.String DESCRIPTOR = "com.shh.ipctest.ILibraryManager";

        public Stub(a) {
            this.attachInterface(this, DESCRIPTOR);
        }
        // Convert the Binder objects on the server to the interface objects required by the client
        // Return the Stub object itself if the client and server are in the same process, otherwise return the Stub
        public static com.shh.ipctest.ILibraryManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if(((iin ! =null) && (iin instanceof com.shh.ipctest.ILibraryManager))) {
                return ((com.shh.ipctest.ILibraryManager) iin);
            }
            return new com.shh.ipctest.ILibraryManager.Stub.Proxy(obj);
        }
        // Return the current Binder object
        @Override
        public android.os.IBinder asBinder(a) {
            return this;
        }
        /** * The Binder thread pool runs on the server side, and cross-process method calls made by the client end up being handled by the method *@paramCode determines which method * is called by the client@paramData holds the parameters * needed for the method to be called@paramReply saves the return value * at the end of the method to be called@param flags 
         * @returnReturn false call *@throws android.os.RemoteException
         */
        @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_getNewBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.shh.ipctest.Book> _result = this.getNewBookList();
                    // Encapsulate the returned data
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_donateBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shh.ipctest.Book _arg0;
                    if ((0! = data.readInt())) {// Deserialize the specific request parameters
                        _arg0 = com.shh.ipctest.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.donateBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_register: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shh.ipctest.IOnNewBookArrivedListener _arg0;
                    _arg0 = com.shh.ipctest.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
                    this.register(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_unregister: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shh.ipctest.IOnNewBookArrivedListener _arg0;
                    _arg0 = com.shh.ipctest.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
                    this.unregister(_arg0);
                    reply.writeNoException();
                    return true; }}return super.onTransact(code, data, reply, flags);
        }
        // When a client calls a server method across processes, the corresponding method in the class is first executed on the client.
        // Call mremote.transact () to initiate a remote Procedure call (RPC) request while the client thread is suspended.
        // Then execute the server's onTransact() method until the RPC request ends and the client thread continues execution,
        // This can be a time-consuming process, so avoid ANR issues on the client.
        private static class Proxy implements com.shh.ipctest.ILibraryManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder(a) {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor(a) {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.shh.ipctest.Book> getNewBookList() throws android.os.RemoteException {
                // The Parcel object that holds the request parameters
                android.os.Parcel _data = android.os.Parcel.obtain();
                // Save the Parcel object that returns the result
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.shh.ipctest.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    // Initiate an RPC request
                    mRemote.transact(Stub.TRANSACTION_getNewBookList, _data, _reply, 0);
                    _reply.readException();
                    // Retrieve the data to be returned from the result of the RPC request
                    _result = _reply.createTypedArrayList(com.shh.ipctest.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void donateBook(com.shh.ipctest.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);
                        // Save the parameter serialization
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_donateBook, _data, _reply, 0);
                    _reply.readException();
                } finally{ _reply.recycle(); _data.recycle(); }}@Override
            public void register(com.shh.ipctest.IOnNewBookArrivedListener listener) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try{ _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((listener ! =null))? (listener.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_register, _data, _reply, 0);
                    _reply.readException();
                } finally{ _reply.recycle(); _data.recycle(); }}@Override
            public void unregister(com.shh.ipctest.IOnNewBookArrivedListener listener) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try{ _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((listener ! =null))? (listener.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_unregister, _data, _reply, 0);
                    _reply.readException();
                } finally{ _reply.recycle(); _data.recycle(); }}}// The code corresponding to the method that the client can call
        static final int TRANSACTION_getNewBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_donateBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_register = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
        static final int TRANSACTION_unregister = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
    }

    public java.util.List<com.shh.ipctest.Book> getNewBookList() throws android.os.RemoteException;

    public void donateBook(com.shh.ipctest.Book book) throws android.os.RemoteException;

    public void register(com.shh.ipctest.IOnNewBookArrivedListener listener) throws android.os.RemoteException;

    public void unregister(com.shh.ipctest.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
}
Copy the code

The ILibraryManager class inherits from the Android.os. IInterface interface. All interfaces defined in the aiDL type file are compiled to generate a Java class inherits from IInterface. The class looks a bit complex, but the structure is clear. There are two main parts: the Stub class that inherits from the Binder, and the methods declared by the interface in IlibraryManager.aidl. The key parts of the Stub class are annotated. In fact, the core of the Stub class is to convert the cross-process method calls initiated by the client process to the server process for execution, and finally get the execution result! Therefore, it is intuitive to see that Binder exists in Service as a bridge for cross-process communication. On this basis, we can better understand the principle behind using AIDL!

Fourth, the Messenger

Messenger is a lightweight, multi-process communication method. It is encapsulated on the basis of AIDL, and can be seen as a simplified version of AIDL. It supports one-to-many serial real-time communication, processing only one request at a time, and there is no concurrency problem. Similar to the use of AIDL, but much simpler, with both server and client implementations required.

The server receives the message sent by the client and responds with a message:

public class MessengerService extends Service {
    private static final String TAG = "MessengerService";
    // Associate Messenger with Handler
    private Messenger mServiceMessenger = new Messenger(new MessengerHandler());

    public MessengerService(a) {}@Override
    public IBinder onBind(Intent intent) {
        return mServiceMessenger.getBinder();
    }

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MessengerActivity.MESSAGE_FROM_CLIENT:
                    // Print the received client message
                    Log.e(TAG, "receive message from client:" + msg.getData().getString("msg"));
                    // Send a message back to the client
                    Messenger clientMessenger = msg.replyTo;
                    Message message = Message.obtain();
                    message.what = MessengerActivity.MESSAGE_FROM_SERVICE;
                    Bundle bundle = new Bundle();
                    bundle.putString("msg"."I am fine,thank you!");
                    message.setData(bundle);
                    try {
                        clientMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break; }}}}Copy the code

We first create a Handler object and receive and reply messages in its handleMessage(). Note that the reply Message is sent by sending a Message object from the Messenger object passed by the client. We need to associate it with the Messenger created on the server:

Messenger mServiceMessenger = new Messenger(new MessengerHandler());
Copy the code

And returns the Binder object contained in the server Messenger in onBind().

After connecting to the server successfully, send a message to the server and receive the reply message from the server:

public class MessengerActivity extends AppCompatActivity {
    private static final String TAG = "MessengerActivity";

    public static final int MESSAGE_FROM_CLIENT = 1;
    public static final int MESSAGE_FROM_SERVICE = 2;

    private Messenger mServiceMessenger;

    private Messenger mClientMessenger = new Messenger(new MessengerHandler());

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mServiceMessenger = new Messenger(service);
            Message message = Message.obtain();
            message.what = MESSAGE_FROM_CLIENT;
            Bundle bundle = new Bundle();
            bundle.putString("msg"."how are you?");
            message.setData(bundle);
            // Pass the Messenger that the server needs to use to reply to the client
            message.replyTo = mClientMessenger;
            try {
                mServiceMessenger.send(message);
            } catch(RemoteException e) { e.printStackTrace(); }}@Override
        public void onServiceDisconnected(ComponentName name) {}};@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);

        Intent intent = new Intent(MessengerActivity.this, MessengerService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy(a) {
        unbindService(mServiceConnection);
        super.onDestroy();
    }

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MessengerActivity.MESSAGE_FROM_SERVICE:
                    Log.e(TAG, "receive message from service:" + msg.getData().getString("msg"));
                    break; }}}}Copy the code

In onServiceConnected(), a server Binder is converted into a Messenger object on the server side and then sends messages. Since the server still needs to reply messages to the client side, a Messenger object needs to be created on the client side to be attached to the message and sent to the server.

In Messenger, you can also perform permission verification and server termination and reconnection operations, similar to AIDL.

Here’s the final effect:

Five, the summary

The content of the article reference “Android development art exploration”, I hope to help you! Source address: github.com/Othershe/IP…