Binder communication

Due to my current understanding of Binder communication is not in place, I did not go deep into the source code, so I will mainly read others: A more detailed post, I think it is very detailed to the code, all involved. Blog.csdn.net/qq_38998213…

To summarize, Android has a Binder approach for cross-process communication (IPC). The architecture is C/S architecture, that is, one side Client, one side Server. Server registers its methods or data with Binder drivers, and clients register with Binder drivers to use services provided by Server.

Aidl

What is Aidl?

Android Interface Defination Language (Aidl) is an Interface design language provided by Android for users, which is intended to facilitate users to design cross-process communication. Instead of writing a bunch of code from scratch each time, just create a new AIDL file and specify the data and methods in it. The IDE automatically helps generate a basic framework for communication between the Binder drivers, servers and clients. What the user needs to achieve is:

  • Implement the specific functions of the Server
  • Associate yourself with the service you want to use in the client

Specific implementation

Create an AIDL file in a new Android project and Android Studio will automatically generate an initial AIDL file:

// IMyAidlInterface.aidl
package com.ronghua.deviceselfcheck;

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

interface IMyAidlInterface {
    /** * 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);
}
Copy the code

Aidl tells you here some basic types you can use if you need to transfer data content beyond these, as Binder communication can only transfer serialized data, you need to define it yourself by implementing the Parcelable interface. Here, the method of data types can be change, back to my own a piece of code in doing project examples, this method is derived from a blog darvincitech.wordpress.com/2019/11/04/ MagiskDetect…

I’m going to list a file that you don’t want to look at and you can just cross it out

interface IIsolatedProcess {
    /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
    boolean detectMagiskHide(a);
}
Copy the code

Build the project. The IDE generates the following code that we need to use:

/* * This file is auto-generated. DO NOT MODIFY. */
package com.ronghua.deviceselfcheck;
// Declare any non-default types here with import statements

public interface IIsolatedProcess extends android.os.IInterface {
    /** * Default implementation for IIsolatedProcess. */
    public static class Default implements com.ronghua.deviceselfcheck.IIsolatedProcess {
        /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
        @Override
        public boolean detectMagiskHide(a) throws android.os.RemoteException {
            return false;
        }

        @Override
        public android.os.IBinder asBinder(a) {
            return null; }}/** * Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.ronghua.deviceselfcheck.IIsolatedProcess {
        private static final java.lang.String DESCRIPTOR = "com.ronghua.deviceselfcheck.IIsolatedProcess";

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

        /** * Cast an IBinder object into an com.ronghua.deviceselfcheck.IIsolatedProcess interface, * generating a proxy if needed. */
        public static com.ronghua.deviceselfcheck.IIsolatedProcess asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if(((iin ! =null) && (iin instanceof com.ronghua.deviceselfcheck.IIsolatedProcess))) {
                return ((com.ronghua.deviceselfcheck.IIsolatedProcess) iin);
            }
            return new com.ronghua.deviceselfcheck.IIsolatedProcess.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_detectMagiskHide: {
                    data.enforceInterface(descriptor);
                    boolean _result = this.detectMagiskHide();
                    reply.writeNoException();
                    reply.writeInt(((_result) ? (1) : (0)));
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags); }}}private static class Proxy implements com.ronghua.deviceselfcheck.IIsolatedProcess {
            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;
            }

            /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
            @Override
            public boolean detectMagiskHide(a) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                boolean _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_detectMagiskHide, _data, _reply, 0);
                    if(! _status && getDefaultImpl() ! =null) {
                        return getDefaultImpl().detectMagiskHide();
                    }
                    _reply.readException();
                    _result = (0! = _reply.readInt()); }finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            public static com.ronghua.deviceselfcheck.IIsolatedProcess sDefaultImpl;
        }

        static final int TRANSACTION_detectMagiskHide = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

        public static boolean setDefaultImpl(com.ronghua.deviceselfcheck.IIsolatedProcess impl) {
            if (Stub.Proxy.sDefaultImpl == null&& impl ! =null) {
                Stub.Proxy.sDefaultImpl = impl;
                return true;
            }
            return false;
        }

        public static com.ronghua.deviceselfcheck.IIsolatedProcess getDefaultImpl(a) {
            returnStub.Proxy.sDefaultImpl; }}/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
    public boolean detectMagiskHide(a) throws android.os.RemoteException;
}

Copy the code

The file is quite long, but we can skip some of the code and focus on two areas

/* * This file is auto-generated. DO NOT MODIFY. */
package com.ronghua.deviceselfcheck;
// Declare any non-default types here with import statements

public interface IIsolatedProcess extends android.os.IInterface {
   // A bunch of... Don't need to look
    public static abstract class Stub extends android.os.Binder implements com.ronghua.deviceselfcheck.IIsolatedProcess {
        // A bunch of... Don't need to look
        
        public static com.ronghua.deviceselfcheck.IIsolatedProcess asInterface(android.os.IBinder obj) {
            // Return a Proxy that the client can use
        }
        // A bunch of... Don't need to look
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            / / blablabla
            boolean _result = this.detectMagiskHide();
            // Then return the result}}private static class Proxy implements com.ronghua.deviceselfcheck.IIsolatedProcess {
            private android.os.IBinder mRemote;
            @Override
            public boolean detectMagiskHide(a) throws android.os.RemoteException {
                / / blablabla
                boolean _status = mRemote.transact(Stub.TRANSACTION_detectMagiskHide, _data, _reply, 0);
                / / blablabla}}// A Stub implementation of default, but we don't care
       
    public boolean detectMagiskHide(a) throws android.os.RemoteException;
}

Copy the code

After this simplification, we have much less to focus on:

  • A Stub class
  • The Proxy class
  • IIsolatedProcess itself

The first is the Proxy class. The purpose of the Proxy class is to enable clients to use the functions of the Server. If I give you a Proxy, you can use it. So how does a Client get a Proxy?

We can now see that a static method in the Stub, asInterface, does just that. That is to say, the Client only needs to call the asInterface method at the appropriate time to obtain the Proxy I need. Through the Proxy, binder driver can be invoked to call the Server.

The Stub class is the binder driver described above and associates the communication between the two ends. However, there is no logical code in the Stub to help you implement the detectMagiskHide request when it is received. That is, if we can implement stubs on the server side, we are done.

So far our thinking goes like this:

  • Set up a Service that implements the detectMagiskHide real logic in the Stub and uses it as its own binder driver.
  • A Client binds to a Service in order to use the Service and expects a binder driver
  • After binding, the Service will tell the Client the Stub interface (Binder driver) that has just been implemented. The Client can use the Stub interface directly, but it is very troublesome because inter-process communication uses different data types. Therefore, we need to convert the asInterface into a Proxy that is convenient for us to use

Implementing the above logic into code looks like this:

  • Service.class
package com.ronghua.deviceselfcheck;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import androidx.annotation.Nullable;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class IsolatedService extends Service {
    private static String[] blackListMountPaths = {"/sbin/.magisk/"."/sbin/.core/mirror"."/sbin/.core/img"."/sbin/.core/db-0/magisk.db"};
    private static String TAG = "DetectMagisk";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private IBinder mBinder = new IIsolatedProcess.Stub() {
        @Override
        public boolean detectMagiskHide(a) throws RemoteException {
            try{
                Native.isLibLoaded();
            }catch(UnsatisfiedLinkError e){
                Log.i(TAG, "Native lib is not loaded successfully");
                return true;
            }
            boolean isMagiskDetect = false;
            int count = 0;
            try {
                FileReader fr = new FileReader("/proc/self/mounts");
                BufferedReader br = new BufferedReader(fr);
                String line = "";
                while((line = br.readLine()) ! =null) {for(String path:blackListMountPaths){
                        Log.i(TAG, "Checking mount path: " + path);
                        if(line.contains(path)){
                            count++;
                            Log.i(TAG, "Magisk path detected: " + path);
                            break; }}}if(count > 0)
                    isMagiskDetect = true;
                else{ isMagiskDetect = Native.detectMagiskNative(); }}catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            returnisMagiskDetect; }}; }Copy the code

The IsolatedService implements the Service and then returns the Binder drivers we wish to return using the onBind method. This will tell MainActivity what the driver is when the Service is bound.

  • MainActivity.class
package com.ronghua.deviceselfcheck;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IBinder;


public class MainActivity extends AppCompatActivity {

    private boolean isBound = false;
    private static String TAG = "DetectMagisk";
    private IIsolatedProcess mServiceBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onStart(a) {
        super.onStart();
        Intent intent = new Intent(this, IsolatedService.class);
        getApplicationContext().bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mServiceBinder = IIsolatedProcess.Stub.asInterface(service);
            mRemote = service;
            isBound = true;
            Log.i(TAG, "service is bound");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false; }}; }Copy the code

Bind to your Service (MainActivity onStart()), and save the Binder driver you have when you bind onServiceConnected(). As mentioned earlier, Binder is not convenient to use due to data format problems, so we obtained Proxy through asInterface. Whenever we want the Service to do a task, we can use the Proxy to do it.

Binder drivers handle onBind for different processes. The asInterface process directly obtains the Stub(Service and MainActivity process), so you can directly call the Stub method, which is similar to the Binder implementation, to obtain the Service instance. If it is a different process, we cannot get obj directly, so we will be given a BinderProxy to call Service with Binder driver.)