Summary of Binder

Binder is ubiquitous in Android, using Bindder to call media services, sensors, startActivity, startService, etc. The entire Android system can be seen as a C/S architecture based on Binder, Binder is the glue that binds systems together. Binder is so important that as Android developers we need to understand it.

Begin your Binder learning journey.

Binder is used to communicate between processes. Android is based on Linux. Linux already has some solutions for communication between processes, such as pipes, shared memory, sockets, etc. Then we need to know their strengths and weaknesses

The pipe

For example, if there is A pipe between A and B, A copies data into the pipe, and B reads data from the pipe, the process requires the pipe to be set up and the data to be copied twice

And the pipe is half duplex meaning that the data can only flow in one direction, so if you want to communicate with each other, you have to build two pipes

So pipes cost performance

The Shared memory

Multiple processes share an area of memory, which is efficient without copying, but is difficult to manage and secure because it is visible to all processes

Socket

Socket is a universal interface, mainly used for communication between the network, although it can achieve inter-process communication, is to kill chicken with a knife, low transmission efficiency, high overhead, also need two copies.

Binder

Only one copy of data is required, second only to shared memory in performance. In terms of stability, Binder is based on C/S architecture mode, which leaves whatever the client needs to be done to the server, with a clear structure and clear responsibilities.

In terms of security, traditional inter-process communication does not do this. There are so many apps in an Android system, and each APP runs in a separate process. We don’t want other processes to be able to access our APP information.

Android assigns its own UID, PID, to each newly installed application, which is an important proof of identity during communication.

Binder has four important roles:

  • Server
  • Client
  • ServiceManager
  • Binder drive

As the picture above shows

  1. The server uses Binder drivers to register our services with the ServiceManager
  2. The client uses the Bindr driver to query services registered in the ServiceManager
  3. The ServiceManager returns proxy objects to the server using Binder drivers
  4. The client takes the proxy object from the server and communicates with the process.

Client and Server are implemented by developers, Binder drivers and ServiceManager are provided by the system.

Core principles of Binder

  • The Client sends data to the kernel cache, also known as copy
  • This kernel cache maps to the data cache in Binder drivers
  • The server and Bindr data cache have a direct memory mapping relationship.
  • This is equivalent to copying the data directly to the server

Binder source code (9.0)

The code below is all system code myself, and I’m not sure about it myself, but we don’t need to go deep into it, just to strengthen the understanding of how it works

1. Open the Binder equipment

Source location: / frameworks/native/CMDS servicemanager/service_manager. C

The main method in this file has a sentence driver = “/dev/binder”; Here the Binder driver is turned on

2. Create buffer for transferring data between processes, open up memory mapping (128K)

Bs = binder_open(driver, 128*1024); This is to open a 128K memory map

Memory mapping command is mmap (), it’s in/frameworks/native/CMDS servicemanager/binder. The c file, Mmap (NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

It’s called when the system starts up, In a 9.0 System source/device/Google/cuttlefish_kernel / 4.4 – x86_64 / System. The 25306 lines in the map file can see the following instruction ffffffff815dbf50 t binder_mmap to open mapping

3. The ServiceManager starts

Source location in the system/system/core/rootdir/init. Rc file, start servicemanager instructions can be found

4. Package to Parcel and write data to binder device copy_from_user

In the system source: / frameworks/native/libs/binder/IServiceManager CPP can see the parcel in the packaging process

 virtual status_t addService(const String16& name, const sp<IBinder>& service,
                                bool allowIsolated, int dumpsysPriority) {
        Parcel data, reply;
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        data.writeInt32(allowIsolated ? 1 : 0);
        data.writeInt32(dumpsysPriority);
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }
Copy the code

In the system files/frameworks/native/libs/binder/IPCThreadState CPP, Err = writeTransactionData(BC_TRANSACTION, FLAGS, Handle, code, data, NULL);

Encapsulate the information in the parcel into a structure and write it to mOut, waiting for it to return

5. Register the service and add it to svclist

The Server registers with the ServiceManager

Code in the system: / frameworks/native/CMDS/servicemanager/service_manager c file

  if(! svc_can_register(s, len, spid, uid)) { ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
             str8(s, len), handle, uid);
        return - 1;
    }
    // Go to svclist to see if the service is registeredsi = find_svc(s, len); .Copy the code

Define the thread pool in the main thread

In the system source code/frameworks/native/libs/binder/IPCThreadState joinThreadPool methods can be found in the CPP files.

So here’s the thread pool that defines the main thread, which reads and writes all the time

Loop to fetch read/write requests from MLN and mOut min.setdatacapacity (256); mOut.setDataCapacity(256); They default to 256 bytes in size.

In the talkWithDriver method, it determines whether it can be read or written and ultimately sent to a binder device.

All of this code is really hard to read, and you just need to use it to deepen the implementation of Binder.

Hand rolled AIDL

Binder is a bit of a hassle to work with directly, and AIDL is used to simplify using Binder in Andorid.

AIDL four important objects

  • IBinder: An interface that represents the ability to communicate across processes
  • IInterance: What capabilities does the server process have and what methods can it provide
  • Binder: Binder’s native objects inherit from IBinder
  • Stub: Inheriting from Binder implements IInterance, a natively implemented server-side capability

Example: Using AIDL to implement A third-party login, now there is an application A and an application B, application A calls application B to implement the login.

The final effect is shown below:

package com.chs.binderb;

interface ILoginInterface {
    void login(a);

    void loginCallBack(boolean isSuccess,String user);
}

Copy the code

Create two methods: a login method and a login callback.

Then copy the full package name and files of the AIDL to the same location in project A. It must be copied exactly and directly.

In project B, create A LoginService to listen for messages sent by project A. FLAG_ACTIVITY_NEW_TASK specifies the Intent.FLAG_ACTIVITY_NEW_TASK

public class LoginService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginInterface.Stub() {
            @Override
            public void login(a) throws RemoteException {
                Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }

            @Override
            public void loginCallBack(boolean isSuccess, String user) throws RemoteException {}}; }}Copy the code

Then register the service in the androidMainfest.xml file

 <service android:name=".service.LoginService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote_server"
            >
            <intent-filter>
                <action android:name="BinderB_Action"></action>
            </intent-filter>
</service>
Copy the code
  • Android: Enabled =”true” indicates that it can be instantiated
  • Android: Exported =”true” indicates that it can be called implicitly by other applications
  • Android :process=”:remote_server” : start a new process named remote_server
  • The name in the action is used when A applies the implicit call

Let’s go to project A and write the method called

public class MainActivity extends AppCompatActivity {
    /** * Whether to bind the remote service */
    private boolean isStartRemote;
    private ILoginInterface mILoginInterface;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initBinderService();
    }

    /** * Bind the service */ of application B via implicit schematics
    private void initBinderService(a) {
        Intent intent = new Intent();
        / / set the action
        intent.setAction("BinderB_Action");
        // Set the package name of application B
        intent.setPackage("com.chs.binderb");
        // Bind the service
        bindService(intent,cnn,BIND_AUTO_CREATE);
        isStartRemote = true;
    }

    ServiceConnection cnn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Bind successfully, you can use the server-side method
            mILoginInterface = ILoginInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {}};public void startWeiXinLogin(View view) {
        if(mILoginInterface! =null) {try {
                mILoginInterface.login();
            } catch (RemoteException e) {
                e.printStackTrace();
                Toast.makeText(this."Please install wechat first",Toast.LENGTH_SHORT).show(); }}}@Override
    protected void onDestroy(a) {
        super.onDestroy();
        if(isStartRemote){ unbindService(cnn); }}}Copy the code

The layout style is the one shown in the GIF above, and the wechat icon is clicked by the startWeiXinLogin method. It calls the login method of ILoginInterface

So let’s talk about ILoginInterface

Once we’ve created the AIDL file and rebuilt the project, the system will generate an ILoginInterface file for us, Location in the app/build/generated \ aidl_source_output_dir \ debug \ compileDebugAidl \ out \ com \ CHS \ binderb \ ILoginInterface Java

/* * This file is auto-generated. DO NOT MODIFY. * Original file: D:\\android\\A1\\BinderA\\app\\src\\main\\aidl\\com\\chs\\binderb\\ILoginInterface.aidl */
package com.chs.binderb;

public interface ILoginInterface extends android.os.IInterface {
    /** * Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.chs.binderb.ILoginInterface {
        private static final java.lang.String DESCRIPTOR = "com.chs.binderb.ILoginInterface";

        /** * Construct the stub at attach it to the interface. */
        public Stub(a) {
            this.attachInterface(this, DESCRIPTOR);
        }

        /** * Cast an IBinder object into an com.chs.binderb.ILoginInterface interface, * generating a proxy if needed. */
        public static com.chs.binderb.ILoginInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if(((iin ! =null) && (iin instanceof com.chs.binderb.ILoginInterface))) {
                return ((com.chs.binderb.ILoginInterface) iin);
            }
            return new com.chs.binderb.ILoginInterface.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_login: {
                    data.enforceInterface(descriptor);
                    this.login();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_loginCallBack: {
                    data.enforceInterface(descriptor);
                    boolean _arg0;
                    _arg0 = (0! = data.readInt()); java.lang.String _arg1; _arg1 = data.readString();this.loginCallBack(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags); }}}private static class Proxy implements com.chs.binderb.ILoginInterface {
            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 login(a) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
                    _reply.readException();
                } finally{ _reply.recycle(); _data.recycle(); }}@Override
            public void loginCallBack(boolean isSuccess, java.lang.String user) 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(((isSuccess) ? (1) : (0)));
                    _data.writeString(user);
                    mRemote.transact(Stub.TRANSACTION_loginCallBack, _data, _reply, 0);
                    _reply.readException();
                } finally{ _reply.recycle(); _data.recycle(); }}}static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_loginCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public void login(a) throws android.os.RemoteException;

    public void loginCallBack(boolean isSuccess, java.lang.String user) throws android.os.RemoteException;
}

Copy the code

It inherits the IInterface interface, which is required for all interfaces that can be transmitted in Binder. It is also an interface itself.

It declares two methods login() and loginCallBack which we wrote in our AIDL file. Two integer ids, TRANSACTION_login and TRANSACTION_loginCallBack, are declared to identify the two methods. These two ids are used in the onTransact method to identify which method the client is requesting

It has an internal class Stub, a Binder, that communicates with its internal Proxy, which has several important methods

asInterface

Binder objects on the server side are converted into AIDL interface type objects that can be used by clients. This transformation is process-specific, returning the Stub itself if the client and server are in the same process, or the stub.proxy (obj) Proxy object if they are in different processes

asBinder

Returns the current Binder object

onTransact

This method is the onTransact method in the Binder class overridden. It runs in a pool of Binder threads on the server side, and when a remote client initiates a request, the request is wrapped by the system and handed to the method for processing. It determines which method to call with a different code. The method is then executed and the return value is written

Proxy#login

So this method is running on the client side, so in the previous MainActivity we called the asInterface method and basically we got this Proxy object, so we can call its login method, The incoming Parcel object _data and the outgoing Parcel object _reply are created when the client calls the method, and the transact method is called to initiate the remote call request. The current thread is suspended, after which the onTransact method on the server is called until it completes and returns the result

Proxy#loginCallBack

Same as the login method above.

OK now goes back to MainActivity and binds the service in the B application with an implicit call in the onCreate method.

When the button is clicked, the onBind method of the LoginService in application B will be called and the login Activity will start.

In fact, the cross-process communication from A to B has been completed, but if we click on the user name and password in application B, the success or failure should be reported to application A, how to feedback?

The method is the same as when A communicates with B. Write A Service in A and ask B to bind the Service in A. After login, call A’s remote method.

The following code

public class LoginService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginInterface.Stub() {
            @Override
            public void login(a) throws RemoteException {}@Override
            public void loginCallBack(boolean isSuccess, String user) throws RemoteException {
                Log.i("Login Status"."State."+isSuccess+">>>>>user:"+user); }}; }}Copy the code

Create A LoginService, print the callback status and user name in the callback method, and register it in androidMasfet.xml

Methods in B that simulate logging in and invoking the service in A

public class MainActivity extends AppCompatActivity {
    private static final String NAME = "chs";
    private static final String PWD = "123";


    private EditText etName;
    private EditText etPwd;
    /** * Whether to bind the remote service */
    private boolean isStartRemote;
    private ILoginInterface mILoginInterface;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        etName = findViewById(R.id.et_name);
        etPwd = findViewById(R.id.et_pwd);

        initBinderService();

    }

    /** * Bind the service */ of application A via implicit schematics
    private void initBinderService(a) {
        Intent intent = new Intent();
        / / set the action
        intent.setAction("BinderA_Action");
        // Set the package name of application B
        intent.setPackage("com.chs.bindera");
        // Bind the service
        bindService(intent,cnn,BIND_AUTO_CREATE);
        isStartRemote = true;
    }
    ServiceConnection cnn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Bind successfully, you can use the server-side method
            mILoginInterface = ILoginInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {}};public void qqLogin(View view) {
        final String name = etName.getText().toString();
        final String pwd = etPwd.getText().toString();
        ProgressDialog dialog = new ProgressDialog(this);
        dialog.setTitle("Logging in");
        new Thread(){
            @Override
            public void run(a) {
                super.run();
                SystemClock.sleep(1000);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run(a) {
                        boolean isSuccess = false;
                        if(name.equals(NAME)&&pwd.equals(PWD)){
                            isSuccess = true;
                            showToast("Login successful");
                            finish();
                        }else {
                            showToast("Login failed");
                        }
                        try {
                            mILoginInterface.loginCallBack(isSuccess,name);
                        } catch(RemoteException e) { e.printStackTrace(); }}}); } }.start(); }private void showToast(String text) {
        Toast.makeText(this,text,Toast.LENGTH_SHORT).show();
    }

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

The OK code is done, and when it runs it looks like the GIF above. The following information is displayed about the callback of LoginService in A.

The 2019-07-10 21:51:27. 225, 10173-10191 / com. CHS. Bindera: remote_a I/login: status:false> > > > > the user: the 2019-07-10 21:51:35. 343, 10173-10191 / com. CHS. Bindera: remote_a I/login: status:true>>>>>user:chs
Copy the code