background

Do not memorize Binder, find a Binder application scenario and practice to solve the problem. After solving the problem, think about why Binder is designed in this way and why to use Binder.

That is, to master it through practice

  1. What is a Binder?
  2. How does it work?
  3. Why Binder mechanisms and not memory sharing or channels?

Binder related background

Binders are designed to address process communication. The first question that comes to mind is why processes communicate, which is easy because multiple processes are used. So why use multiple processes?

Why use multiple processes

Take the life cycle of a mature input method application as an example.

1.0 App= Input process

Input process is the core process of input method, used to take over the system’s InputMethodService, simple business representation is the user typing, I am responsible for the word on the screen.

2.0 App= input process + container process

With the development of business, input method extends some related functions, such as custom input skin, graph, mini game,emoji, text recognition, speech recognition and so on.

Technically, these businesses have also undergone the evolution of WebView->ReactNative->Flutter

These heavy services and components in the input process is obviously not reasonable, this will cause the input may be inexplicably slow, input service cold start obviously slow. So you need to split these functions into a container process that the desktop application can take over.

3.0 App= Input process + container process + communication process

As the business evolves, further optimization of the input experience is required, in addition to regular technical optimization. From the point of view of the service, some unnecessary background communication needs to be split out.

To put it simply:

  1. The client uses a large amount of encryption to report logs. Desensitized data needs to be reported to the background server.
  2. The service window is displayed, and the notification is delivered.
  3. Business strategy delivery, such as advertising strategy, functional strategy and so on.

These usually run in child threads, but there is no guarantee that they will not compete with the main thread of the input process for CPU chips, so they are directly independent of the background communication process.

Another advantage of this is that the container process or daemon code sending crash does not affect the input process. Especially when users use input methods in other apps, unreasonable architecture will not cause bad experience.

4.0 Why use multiple processes for general Applications

Probably for most apps, you don’t need appeals because input apps are too niche. But there are a few scenarios to consider and explore, such as:

Wechat mobile development team said in the article “Android Memory optimization talk” : “For Webview, gallery, etc., because there are memory system leaks or occupy too much memory, we can use a separate process. Wechat also currently places them in a separate Tools process.”

I think most teams will use WebViews, galleries, and images, but if you use a lot of them you can consider splitting the process.

In addition, ReactNative/Flutter cross-end technology is popular now. If the team does not have deep control over this technology stack, it is expected to gradually explore and master it. Is it possible to consider using a single process + mixed stack solution to gradually land.

Why use Binders for cross-process communication

So the next question is why Binder is used to communicate.

If you were a designer of the Android process communication mechanism a decade or two ago, there were three broad categories of cross-process communication solutions presented to you

  1. Memory sharing
  2. Binder
  3. Channel/Socket… Etc.

Considering performance and security, how do you choose?

Why not use memory sharing

Memory sharing is A solution that directly transfers the memory of application A to application B. In this way, zero-copy memory communication is realized. Therefore, the performance is the best solution. But there is a serious security problem.

For example, when application A and APPLICATION B are in different processes, there is A memory area that stores the account information of application A. If this memory is shared with application B, the account will be leaked. Therefore, authentication is required to determine whether USER B has the permission to access application A.

I understand that, but how do you code it?

All the physical addresses of memory are encoded and recorded, and then mapped and signed?

How difficult is this code to implement? Does the complexity of the scheme cause the small advantage of zero-copy memory to be sacrificed?

So without memory sharing, there is no guarantee of security.

Why not a passage?

Application A copies the memory to the system, and the system copies the memory to application B. Two copies of the

Because it is a clear C/S architecture, there is a clear communication code, so directly in the time of communication authentication, security is better to ensure.

The reason it’s not used is because of performance issues.

The second copy is too much. The data of A is sent to B, and the data of A is copied directly.

Yeah, you can, Binder.

Binder mechanisms are similar to channels. The difference is that application A does not actually copy the data to the system memory, and then the system copies the memory to application B. Binder makes A clever conversion. Binder does not copy the data when A is copied to the system, but when B needs the data of A,Binder directly copies the data of A to B. This scheme is called a memory mapping scheme.

How to use Binder

Since the input method project involves some company secrets, I will skip the input method demonstration and use a common Demo example.

The remote service

Purpose: Let the Activity call the getBooks() and addBook() methods of the Service process. Step by step implementation in the order written code:

1. Create the IBinder interface class -> declare cross-process transport capabilities

Interfaces inherit IInterface because interfaces need to have the capability of Binder transport in addition to their own capabilities

public interface BookManager extends IInterface {

    List<Book> getBooks(a) throws RemoteException;

    void addBook(Book book) throws RemoteException;
}
Copy the code

2. Inherit Binder classes -> the ability to transfer across processes

In order to implement the asBinder method of the IInterface, you must have a Binder object, so the easiest way is to simply have stubs inherit the Binder class

public abstract class Stub extends Binder implements BookManager {

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

    @Override
    public IBinder asBinder(a) {
        return this; }}Copy the code

3. Implement static methodsasInterface-> Link to invoke process

The server already has cross-process capability. How do you transfer this capability to the caller?

Let’s see what the tune can get. The ServiceConnection is available through the Service binding, where onServiceConnected provides an IBinder object.


    private void attemptToBindService(a) {
        Intent intent = new Intent(this, RemoteService.class);
        intent.setAction("com.baronzhang.ipc.server");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
    
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // The IBinder service is passed from the remote end.}}Copy the code

Now the problem becomes converting IBinder to BookManager. So we have the following conversion code:

    public static BookManager asInterface(IBinder binder) {
        if (binder == null)
            return null;
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        // If it is the same process, force conversion directly
        if(iin ! =null && iin instanceof BookManager)
            return (BookManager) iin;
        // If two processes use Proxy
        return new Proxy(binder);
    }
Copy the code

4. Implement the specific implementation of Proxy-> cross-process transmission

The asInterface method is running in the calling process. There is no way for different processes to get the desired BookManager instance object directly, so we need a Proxy object to help us to get the BookManager instance object Feeling.

So the Proxy needs to implement BookManager

public class Proxy implements BookManager {
Copy the code

Once implemented, what if BookManager still has three methods to implement? Since it is a proxy object will not be implemented, the real implementation should be in the server process. The Proxy needs to send messages to the service process


    private IBinder remote;

    public Proxy(IBinder remote) {

        this.remote = remote;
    }
    
    @Override
    public List<Book> getBooks(a) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        List<Book> result;

        try {
            data.writeInterfaceToken(DESCRIPTOR);
            remote.transact(Stub.TRANSAVTION_getBooks, data, replay, 0);
            replay.readException();
            result = replay.createTypedArrayList(Book.CREATOR);
        } finally {
            replay.recycle();
            data.recycle();
        }
        return result;
    }
Copy the code

For example, with the getBooks() method, the Proxy’s role is to encapsulate objects using a Parcel and send them using IBinder, which is the argument passed back when the service connects.

5. Implement Parcelable-> support cross-process transfer

The JavaBean object Book will be transferred across processes, so you need to support the Parcelable interface

The IDE automatically generates Parcelable code

public class Book implements Parcelable {
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.price);
        dest.writeString(this.name);
    }

    protected Book(Parcel in) {
        this.price = in.readInt();
        this.name = in.readString();
    }

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

        @Override
        public Book[] newArray(int size) {
            return newBook[size]; }}; }Copy the code

6. Complete the onTranstale method -> implement interface

The calling process sends messages and parameters to the service process. How does the service process produce results and then reply to the calling process?

Use the onTransact() method

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case TRANSAVTION_getBooks:
                data.enforceInterface(DESCRIPTOR);
                List<Book> result = this.getBooks();
                reply.writeNoException();
                reply.writeTypedList(result);
                return true; }}Copy the code

At this point a complete handwritten Binder service communication sample code is implemented.

I borrowed this example directly from a sample project on Github. Refer to the Demo.

AIDL

The above example uses Binder directly to complete the Demo. Binder communicates code that is mostly the same length. The IDE provides a template for automatically generating Binder code.

AIDL is the development template

1. Define the AIDL interface

interface AIDLBookManager {
    // Automatically generated
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
    // Add interface
    void addBook(in Book book);

    List<Book> getBookList(a);
}
Copy the code

2. Automatically implement the Parcelable interface

3. Rely on Parcelable+ to automatically generate code

  1. Create a new file book. AIDL in the same directory as the AIDL file and fill it in
// IBookManager.aidl
package com.baronzhang.ipc;
parcelable Book;
Copy the code
  1. Then introduce the dependency in aidlBookManager.aidl
package com.baronzhang.ipc;
/ / rely on
import com.baronzhang.ipc.Book;
Copy the code
  1. Compile, automatically generate code

Stub Proxy class

The asInterface() onTransact() methods are generated automatically.

Why is the code all in one file

  1. The code is generated automatically to solve a class of problems, with no concern for readability.
  2. The code generation method should be similar to APT, a file generation is easier to write.

Messenger

  1. Defining the service process
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }

    Messenger messenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    Message replyMsg = Message.obtain(null.1, books ! =null ? books : new ArrayList<Book>());
                    try {
                        msg.replyTo.send(replyMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                case 2:
                    Book book = (Book) msg.obj;
                    book.setPrice(book.getPrice() * 2);
                    books.add(book);
                    break;
                default:
                    throw new IllegalArgumentException("Unrealized");
            }
            super.handleMessage(msg); }});Copy the code
  1. Defining the calling process
    / / define
    Messenger messenger;
    Messenger handleMessengr = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    List<Book> books = (List<Book>) msg.obj;
                default:
                    break;
            }
            super.handleMessage(msg); }});/ / initialization
    Messenger messenger = new Messenger(service);
    
    / / execution
    Message message = Message.obtain();
    message.what = 1;
    message.replyTo = handleMessengr;
    messenger.send(message);
Copy the code

You can see that Messenger is relatively easy to use, but why do we tend to use AIDL instead of Messenger?

  1. AIDL defines method communication directly, whereas Messager needs to convert methods into messages, which in turn convert methods into methods, making the code on both sides much less readable.
  2. Messenger is based on handlers, a threaded communication model that runs on a single thread, making Messenger a single-threaded, sequentially executed structure.

conclusion

There are probably several reasons why you can’t grasp a classic knowledge point

  1. Use less, use more familiar, the best is actual combat
  2. Think little, know what is and what is