Binder is the Binder mechanism in Android for inter-process communication. Many students will recall their fear of being dominated by Binder and AIDL when they first learned Android. But as an Android developer, Binder is something we must learn. That’s because it’s the steel and concrete that frames the entire Android building, connecting Android services and upper-level applications. A deeper understanding of Android development and the Android Framework is possible only with the Binder mechanism. This is why advanced books, from Exploring the Art of Android Development to Understanding The Idea behind the Android Kernel, put interprocess communication and the Binder mechanism at the top of their chapters. It is so important that the entire Android Framework needs Binder.

We will not explore the underlying implementation of Binder in this article because, at this point, I do not know enough to export the principles of the Binder implementation. So, in order not to mislead, here’s a primer on Binder’s use and AIDL. The web abounds with information about Binder and AIDL’s basic uses, though. But there are not many articles that Binder and AIDL can write that are easy to understand. As a result, many people find it difficult to use both Binder and AIDL.

In this article, I will introduce Binder mechanism and AIDL through my own learning ideas.

Before we get started, I’d like to introduce you to the GitHub repository AndroidNote, which is my study notes and where I wrote the first draft of this article. This repository aggregates a large amount of Java and Android advanced knowledge. Is a more systematic and comprehensive Android knowledge base. It is also a valuable interview guide for students preparing for the interview. Welcome to the GitHub repository homepage.

1. Interprocess communication

In the operating system, each process has a separate memory space. In order to ensure the security of the program, the operating system will have a set of strict security mechanism to prevent unauthorized access between processes. After all, if your APP has access to other apps’ operating space, or if other apps have easy access to your APP’s operating space, imagine your heart breaking. Therefore, the operating system isolates the memory of application processes to ensure the safety of APP operation. However, many processes need to communicate with each other, such as the clipboard, which can copy information from one program to another. This is the context in which interprocess communication is born.

Broadly speaking, inter-process communication (IPC) refers to the exchange of data between several threads running in different processes.

Common interprocess communication modes in an OPERATING system include shared memory, pipes, UDS, and Binder. We will not go into the details of how these processes communicate in this article.

  • Shared Memory Communication between processes depends on applying for a Memory area, and then mapping the Memory to the process space, so that both processes can directly access the Memory. During interprocess communication, two processes can use this memory space to exchange data. In this way, the assignment of data is reduced, so there is a significant speed advantage in sharing memory for interprocess communication.

  • Pipe Is also a common communication mode between processes in the operating system. The communication between processes in Windows system depends on this mode. In the process of interprocess communication, the establishment of a read write (write) function between two processes, one process can write data, the other process can read data, so as to achieve interprocess communication problem.

  • UDS (UNIX Domain Socket) A UDS is also called an IPC Socket, but it is different from a network Socket. The internal implementation of the UDS does not depend on THE TCP/IP protocol, but is based on the local secure and Reliable operation. Inter-process communication such as UDS is widely used in Android.

  • Binder Binder is an interprocess communication method unique to Android. It relies on MMAP, which maps a chunk of physical memory to both the kernel and the target process’s user space with a single copy.

In this article, we focus on learning how to use binders for interprocess communication. Binder’s name alone has a mysterious feel to it, because the name alone might scare off beginners. Binder itself is not mysterious. It is simply a means of interprocess communication provided by Android to developers.

From the point of view of the above mentioned inter-process communication, no matter what kind of inter-process communication, all need a process to provide data, a process to obtain data. Therefore, we can call the end that provides the data the server and the end that gets the data the client. Is Binder a little bit like HTTP in this sense? You can think of a Binder as an HTTP protocol that allows clients to access data from servers. With this in mind, the use of binders is much easier.

Use binders to implement interprocess communication

Interprocess communication with binders is simple. Let’s take an example of querying grades. A server provides an interface to query grades by student name, and a client connects to the server to query grades by student name. The medium between the client and the server is Binder.

1. Server implementation

The server needs to provide a Service, so we need to start a Service to wait for the client to connect. We implement a GradeService and inherit Service to provide a score query interface. The code is as follows:

public class GradeService extends Service {
    public static final int REQUEST_CODE=1000;
    private final Binder mBinder = new Binder() {
        @Override
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            if (code == REQUEST_CODE) {
                String name = data.readString();
                // Query student scores by name and write them to the returned data
                int studentGrade = getStudentGrade(name);
                if(reply ! =null)
                    reply.writeInt(studentGrade);
                return true;
            }
            return super.onTransact(code, data, reply, flags);
        }
        // Query student's grades by name
        public int getStudentGrade(String name) {         
            returnStudentMap.getStudentGrade(name); }};@Nullable
    @Override
    public IBinder onBind(Intent intent) {
        returnmBinder; }}Copy the code

Since we want to implement cross-process communication, we set this server’s Service to the remote process in the AndroidManifest file as follows:

<service
    android:name="com.zhpan.sample.binder.server.GradeService"
    android:process=":server">
    <intent-filter>
        <action android:name="android.intent.action.server.gradeservice" />
    </intent-filter>
</service>
Copy the code

In this way, a remote, performance query service is completed.

2. Client implementation

The client is naturally connected to the server process score query. Therefore, we take the binding GradeService in the Activity of the client to query the results. The code is as follows:

public class BinderActivity extends AppCompatActivity {
    // Binder proxy for remote services
    private IBinder mRemoteBinder;

    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // Obtain Binder proxy for remote services
            mRemoteBinder = iBinder;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mRemoteBinder = null; }};@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder);
        // Bind the service
        findViewById(R.id.btn_bind_service).setOnClickListener(view -> bindGradeService());
        // Query student scores
        findViewById(R.id.btn_find_grade).setOnClickListener(view -> getStudentGrade("Anna"));
    }
    // Bind the remote service
    private void bindGradeService(a) {
        String action = "android.intent.action.server.gradeservice";
        Intent intent = new Intent(action);
        intent.setPackage(getPackageName());
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }
    // Query student scores from the remote service
    private int getStudentGrade(String name) {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        int grade = 0;
        data.writeString(name);
        try {
            if (mRemoteBinder == null) {
                throw new IllegalStateException("Need Bind Remote Server...");
            }
            mRemoteBinder.transact(REQUEST_CODE, data, reply, 0);
            grade = reply.readInt();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return grade;
    }
Copy the code

The code on the client side is to query the student grades by binding the remote service and then obtaining the Binder agent for the service. As you can see, implementing interprocess communication with binders is surprisingly simple. So what is AIDL that we wrote before? What’s all that stuff that AIDL is generating? So let’s move on.

Agent mode to optimize Binder usage

Although the code in the previous chapter is fairly simple, there is room for optimization. We can optimize this by using design patterns to make the code more concise.

First of all, we need to define an interface IGradeInterface for querying scores. The code is as follows:

public interface IGradeInterface {
    // Query the score interface
    int getStudentGrade(String name);
}
Copy the code

1. Server code optimization

Next, optimize the code on the server side. We implement a custom GradeBinder and implement the above interface as follows:

public class GradeBinder extends Binder implements IGradeInterface {

    @Override
    public int getStudentGrade(String name) {
        return StudentMap.getStudentGrade(name);
    }

    @Override
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        if (code == REQUEST_CODE) {
            String name = data.readString();
            int studentGrade = getStudentGrade(name);
            if(reply ! =null)
                reply.writeInt(studentGrade);
            return true;
        }
        return super.onTransact(code, data, reply, flags); }}Copy the code

This code moves the logic for querying scores from the Service to the GradeBinder. Therefore, the Service only needs to return the instance of GradeBinder on onBind. The code is as follows:

public class GradeService extends Service {

    public static final int REQUEST_CODE=1000;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return newGradeBinder(); }}Copy the code

2. Optimize the client code

The idea of client optimization is to instantiate a proxy class that holds a Binder when connecting to a remote service, and let the proxy class exercise the Binder’s rights. First look at the code implementation of the proxy class:

public class BinderProxy implements IGradeInterface {
    // Proxy Binder
    private final IBinder mBinder;
    // Private constructor
    private BinderProxy(IBinder binder) {
        mBinder = binder;
    }

    // Read the score by Binde
    @Override
    public int getStudentGrade(String name) {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        int grade = 0;
        data.writeString(name);
        try {
            if (mBinder == null) {
                throw new IllegalStateException("Need Bind Remote Server...");
            }
            mBinder.transact(REQUEST_CODE, data, reply, 0);
            grade = reply.readInt();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return grade;
    }
		
    // Instantiate Binder proxy class objects
    public static IGradeInterface asInterface(IBinder iBinder) {
        if (iBinder == null) {
            return null;
        } 
        
        if (iBinder instanceof IGradeInterface) {
            LogUtils.e("Current process");
            // If the request is made by the same process, the Binder is returned
            return (IGradeInterface) iBinder;
        } else {
            LogUtils.e("Remote process");
            // Return Binder's proxy object for cross-process queries
            return newBinderProxy(iBinder); }}}Copy the code

The constructor of the BinderProxy class is set to private. It also provides an asInterface method that determines whether a Binder is an IGradeInterface type and therefore cross-process communication. Returns the current Binder if it is not communicating across processes, otherwise returns the Binder’s proxy class.

The client then uses BinderProxy to obtain the Binder or BinderProxy instance when connecting to the remote service. The code is as follows:

public class BinderProxyActivity extends BaseViewBindingActivity<ActivityBinderBinding> {
    // This could be either BinderProxy or GradeBinder
    private IGradeInterface mBinderProxy;

    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            The BinderProxy or GradeBinder instance is obtained depending on whether the service is connected
            mBinderProxy = BinderProxy.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mBinderProxy = null; }};@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding.btnBindService.setOnClickListener(view -> bindGradeService());
      	MBinderProxy = mBinderProxy = mBinderProxy = mBinderProxy
        binding.btnFindGrade.setOnClickListener(view -> ToastUtils.showShort("Anna grade is " + mBinderProxy.getStudentGrade("Anna")));
    }

    // Bind the service
    private void bindGradeService(a) {
        String action = "android.intent.action.server.gradeservice";
        Intent intent = newIntent(action); intent.setPackage(getPackageName()); bindService(intent, mServiceConnection, BIND_AUTO_CREATE); }}Copy the code

As you can see, the code here is much cleaner than it was in chapter 1. However, the code doesn’t seem to be as handy to write as it was in chapter 1. This is mainly because we need to add an IGradeInterface and a custom GradeBinder, and we need to write the code for the proxy class, which is very tedious. So is there a way to keep your code clean and easy to write? The answer is yes, using AIDL.

Fourth, the AIDL

AIDL is short for Android Interface Description Languaged. A description language used to describe the client/server communication interface. AIDL is a big deal for many people. Defining an AIDL interface that generates so much code that it doesn’t make sense seems like a disaster. Don’t worry, if you understand chapter 3, you’ve already mastered AIDL. Yes, the piece of code that AIDL generates is actually the code we wrote in Chapter 3. In other words, the principle of AIDL is to optimize the use of Binder by using agent mode. AIDL ensures clean code and saves the need to write tedious code related to agent classes.

The use of AIDL is very simple.

1. Create an AIDL interface

To create an AIDL File, right-click New->AIDL-> AIDL File in the directory where you want to create AIDL, as shown in the following image:

Create an AIDL file named IGradeService and add the method getStudentGrade. The code is as follows:

// IGradeService.aidl
package com.zhpan.sample.binder.aidl;

// Declare any non-default types here with import statements

interface IGradeService {
    /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    int getStudentGrade(String name);
}
Copy the code

Then Rebuild the project and the IDE will automatically generate AIDL code.

2. Code generated by AIDL

In the project’s build directory com. Zhpan. Sample. The binder. The aidl package will see the automatically generated a called IGradeService interface, the code is as follows:

// This interface is equivalent to the IGradeInterface interface in the previous chapter
public interface IGradeService extends android.os.IInterface {...Stub is a Binder, equivalent to GradeBinder in the previous chapter
  public static abstract class Stub extends android.os.Binder
      implements com.zhpan.sample.binder.aidl.IGradeService {
    private static final java.lang.String DESCRIPTOR = "com.zhpan.sample.binder.aidl.IGradeService";

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

    public static IGradeService asInterface(android.os.IBinder obj) {
      
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if(((iin ! =null) && (iin instanceof com.zhpan.sample.binder.aidl.IGradeService))) {
        // Return the current Binder object if it is the current process
        return ((com.zhpan.sample.binder.aidl.IGradeService) iin);
      }
      // Return Binder's proxy object across processes
      return new com.zhpan.sample.binder.aidl.IGradeService.Stub.Proxy(obj);
    }

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

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
        throws android.os.RemoteException {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code) {
        case INTERFACE_TRANSACTION: {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_basicTypes: {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0! = data.readInt());float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_getStudentGrade: {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          int _result = this.getStudentGrade(_arg0);
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        }
        default: {
          return super.onTransact(code, data, reply, flags); }}}// Binder's proxy class is equivalent to BinderProxy in the previous chapter
    private static class Proxy implements com.zhpan.sample.binder.aidl.IGradeService {
      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 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
          double aDouble, java.lang.String aString) 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.writeInt(anInt);
          _data.writeLong(aLong);
          _data.writeInt(((aBoolean) ? (1) : (0)));
          _data.writeFloat(aFloat);
          _data.writeDouble(aDouble);
          _data.writeString(aString);
          boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
          if(! _status && getDefaultImpl() ! =null) {
            getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
            return;
          }
          _reply.readException();
        } finally{ _reply.recycle(); _data.recycle(); }}@Override public int getStudentGrade(java.lang.String name)
          throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(name);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getStudentGrade, _data, _reply, 0);
          if(! _status && getDefaultImpl() ! =null) {
            return getDefaultImpl().getStudentGrade(name);
          }
          _reply.readException();
          _result = _reply.readInt();
        } finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

      public static com.zhpan.sample.binder.aidl.IGradeService sDefaultImpl;
    }

    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getStudentGrade = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

    public static boolean setDefaultImpl(com.zhpan.sample.binder.aidl.IGradeService impl) {
      if (Stub.Proxy.sDefaultImpl == null&& impl ! =null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }

    public static com.zhpan.sample.binder.aidl.IGradeService getDefaultImpl(a) {
      returnStub.Proxy.sDefaultImpl; }}/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
  public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble,
      java.lang.String aString) throws android.os.RemoteException;

  public int getStudentGrade(java.lang.String name) throws android.os.RemoteException;
}
Copy the code

A glance at the code shows that the IGradeService interface has an inner class named Stub that inherits from the Binder and implements the IGradeService interface, and that it has a method called asInterface inside. This method is the same as asInterface in BinderProxy in the previous chapter, except that the location of the write is different. In addition, the code in the TRANSACTION_getStudentGrade condition of the Stub onTranscation method is the same as the code in GradeBinder’s onTranscation method.

Next, the Stub class also has an inner class called Proxy. The Proxy class corresponds to BinderProxy from the previous chapter. As you can see, the Proxy class constructor has no modifiers, and the BinderProxy constructor is declared private, preventing outsiders from instantiating the Proxy class object through the constructor area. The getStudentGrade method of the Proxy and the getStudentGrade method of the BinderProxy use the Binder to read data written to the server.

3. The AIDL client

The client-side implementation using AIDL is almost identical to the code in Chapter 3. The asInterface method under IGradeService.Stub is used to obtain the Binder or Binder proxy object after connecting to the server. The code is as follows:

public class AidlActivity extends BaseViewBindingActivity<ActivityBinderBinding> {

    private IGradeService mBinderProxy;

    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // After connecting the service, obtain the Binder or Binder's proxy object depending on whether the Binder is cross-process or not
            mBinderProxy = IGradeService.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mBinderProxy = null; }};@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding.btnBindService.setOnClickListener(view -> bindGradeService());
      	// Query student scores
        binding.btnFindGrade.setOnClickListener(view -> getStudentGrade("Anna"));
    }
  
    // Bind the service
    private void bindGradeService(a) {
        String action = "android.intent.action.server.aidl.gradeservice";
        Intent intent = new Intent(action);
        intent.setPackage(getPackageName());
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }
  
    // Query the score
    private void getStudentGrade(String name) {
        int grade = 0;
        try {
            grade = mBinderProxy.getStudentGrade(name);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        ToastUtils.showShort("Anna grade is "+ grade); }}Copy the code

This concludes the introduction to AIDL. Have you been surprised to find that AIDL is so simple!

Five, the summary

This article introduces interprocess communication and the use of binders and AIDL. Binder and AIDL are simple to learn from this article. With Binder in mind, we can learn more about the Android Framework layer.

AndroidNote