Content of this article:

  1. How do I obtain a ServiceManager
  2. Why can a Binder be called like an interface
  3. The process of adding and acquiring services is also a process of communication with Binder
  4. Why Parcel and Parcelable

Read this article.

  1. The concept of process isolation requires understanding why IPC is needed
  2. What are user-mode and kernel-mode, need to know IPC implementation mechanism and why IPC needs memory copy
  3. The role of JNI, JNI can be understood as the communication protocol between Java objects and Native objects, it can operate both Java objects and Native objects, this is important to understand
  4. The nature of Java objects is also important because JNI manipulates Java objects with an understanding of their theoretical model in the Java Virtual machine (not a model, but an image displayed)

For those who are not clear on the first and second points, you can read OS: Three Easy Pieces, which is the best introduction to operating system principles in my opinion. It is thick because it explains the concepts in detail and focuses on virtualization, concurrency, and file systems, rather than being big and comprehensive

For the third and fourth points that are unclear, take a look at understanding the Java Virtual Machine in Depth

The use of the Binder

Here’s gityuan’s code as an example

public class ClientDemo {
	public static void main(String[] args) throws RemoteException {
		System.out.println("Client start");
		IBinder binder = ServiceManager.getService("MyService"); // Get the service named "MyService"
		IMyService myService = new MyServiceProxy(binder); // Create the MyServiceProxy object
		myService.sayHello("binder"); // Call the interface's methods through the MyServiceProxy object
		System.out.println("Client end"); }}public class ServerDemo {
	public static void main(String[] args) {
		System.out.println("MyService Start");
		Looper.prepareMainLooper(); // Start loop execution
		android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND); // Set to foreground priority
		ServiceManager.addService("MyService".new MyService());// Register serviceLooper.loop(); }}Copy the code

Get ServiceManager

Can be seen in ServerDemo before access to services, if you want to add service ServiceManager ServiceManager first. The addService (), since have to add, it must be a place to store it, storage place is ServiceManager, So how do you get a ServiceManager? Start with ServiceManager. The addService (), before you start to take a look at first to obtain ServiceManager UML diagrams

The class diagram

There are a few special classes to explain

  1. BinderProxy, a storage class for Native Binder, stores Pointers to Native Binder and is not initialized by Java classes. BinderProxy is loaded and created in JNI code
  2. IServiceManager.Stub is an abstract class with no implementation. Binder is generally used to inherit it as a Binder entity class. This approach is also common when acquiring C++ Binder services

ServiceManager.addService()

AddService (String Name, IBinder Service, Boolean allowIsolated, int dumpPriority) is called, and it contains only one line of code

getIServiceManager().addService(name, service, allowIsolated, dumpPriority)
Copy the code

See the main getIServiceManager()

ServiceManager.getIServiceManager()

private static IServiceManager getIServiceManager(a) {
    if(sServiceManager ! =null) {
        return sServiceManager;
    }

    // Find the service manager
    sServiceManager = ServiceManagerNative
        .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
    return sServiceManager;
}
Copy the code

There is no thread protection because this interface is not used by applications and applications cannot directly operate addService() and getService() interfaces, so getIServiceManager() can be considered to be running on the main thread. Yes, Our communication also USES the Binder, with ServiceManager just ServiceManager Binder is a little special, we see first ServiceManagerNative. AsInterface () this method

ServiceManagerNative.asInterface()

public static IServiceManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }

    // ServiceManager is never local
    return new ServiceManagerProxy(obj);
}
Copy the code

A ServiceManagerProxy object is created directly

ServiceManagerProxy initialization

public ServiceManagerProxy(IBinder remote) {
    mRemote = remote;
    mServiceManager = IServiceManager.Stub.asInterface(remote);
}
Copy the code
  1. mRemote = remote;This is the old way, Gityuan’sBinder 7- Framework layer analysis”Is used in this way
  2. mServiceManager = IServiceManager.Stub.asInterface(remote);AIDL is the way to communicate, which is a little more concise

As with Retrofit and OKHTTP, AIDL generates Java code in the Out directory, so if you want to analyze AIDL, you need to compile the Android source code first. Specific path is out/soong/intermediates/frameworks/base/framework – minus – apex/android_common javac/shard30 / classes/android/OS/ISer Vicemanager.class can be opened using AndroidStudio, IDEA or decompiler tools. If you want to see it directly, I made a copy of iserVicemanager.java

IServiceManager.Stub.asInterface()

public static IServiceManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    } else {
        IInterface iin = obj.queryLocalInterface("android.os.IServiceManager");
        return(IServiceManager)(iin ! =null && iin instanceof IServiceManager ? (IServiceManager)iin : newIServiceManager.Stub.Proxy(obj)); }}Copy the code

The logic is simple

  1. Binder objects are returned if the process is the same. Since the ServiceManager is in a separate process, this will not be the same process. How the local Service connects to the process will be discussed later
  2. If the process is not the same, create oneIServiceManager.Stub.Proxy

Initialization IServiceManager. Stub. The Proxy

Proxy(IBinder remote) {
    this.mRemote = remote;
}
Copy the code

The old way of initialization and ServiceManagerProxy isn’t the same, take a look at IServiceManager. The Stub. Proxy. The addService ()

public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority) throws RemoteException {
    Parcel _data = Parcel.obtain();
    Parcel _reply = Parcel.obtain();

    try {
        _data.writeInterfaceToken("android.os.IServiceManager");
        _data.writeString(name);
        _data.writeStrongBinder(service);
        _data.writeInt(allowIsolated ? 1 : 0);
        _data.writeInt(dumpPriority);
        boolean _status = this.mRemote.transact(3, _data, _reply, 0);
        if(! _status && IServiceManager.Stub.getDefaultImpl() ! =null) {
            IServiceManager.Stub.getDefaultImpl().addService(name, service, allowIsolated, dumpPriority);
            return;
        }

        _reply.readException();
    } finally{ _reply.recycle(); _data.recycle(); }}Copy the code

It’s basically the same as the old way, so the new way just automatically generates this code, reduces the amount of code, makes it look like IPC is a method call, but it ends up calling ibinder.transact () for IPC, Then back to ServiceManager. GetIServiceManager () method, the above can only be classified as some code technique, the next step is the core of the Binder, obtain the ServiceManager IBinder object.

BinderInternal.getContextObject()

This is a native method corresponding to android_util_binder.cpp’s android_os_BinderInternal_getContextObject()

android_os_BinderInternal_getContextObject()

Two things are done here:

  1. By calling theProcessState.getContextObject(NULL)Obtain sp<IBinder>, pay attention to the parameter NULL here, that is, to obtain IServiceManager Binder, this step is reserved for native Binder parsing process in detail
  2. calljavaObjectForIBinder()Converting an IBinder to a Java object, that is, a BinderProxy object, is done by storing the IBinder pointer (of type Long) in mNativeData of BinderProxy

javaObjectForIBinder()

  1. Binder type check, this step is to determine whether the incoming JavaBBinder type

  2. Create a pointer of type BinderProxyNativeData and initialize the corresponding fields

  3. The IBinder smart pointer reference in BinderProxyNativeData. MObject

  4. Call binderproxy.getInstance () with CallStaticObjectMethod() and pass the corresponding argument. ** We all know that calling a static method of a class triggers a class load. Can see here directly travels is gBinderProxyOffsets mClass, explanation BinderProxy. Class object has been loaded, so how does this gBinderProxyOffsets initialization? ** The answer is that the call stack is loaded when the virtual machine starts

    //AndroidRuntime.cpp, this part is to bind the function pointer, the purpose of using macros is to facilitate DEBUG, can define different structures, different initialization operations
    #define REG_JNI(name)      { name }
    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
    };
    extern int register_android_os_Binder(JNIEnv* env);// Declare the function, using static links
    static const RegJNIRec gRegJNI[] = {
      ...
    REG_JNI(register_android_os_Binder),// macro expansion is the structure initialization {register_android_os_Binder}. };//AndroidRuntime.cpp, which is called when the process starts
    AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
    int AndroidRuntime::startReg(JNIEnv* env)
    static int register_jni_procs(const RegJNIRec array[].size_t count, JNIEnv* env)
    //android_util_Binder.cpp
    int register_android_os_Binder(JNIEnv* env)
    static int int_register_android_os_BinderInternal(JNIEnv* env)
    Copy the code
  5. The following code is not understood, but it does not matter much

This returns an object instance of BinderProxy, which implements the IBinder interface

BinderProxy.getInstance()

Private static BinderProxy getInstance(Long nativeData, Long iBinder) is a private static method that returns an object instance of BinderProxy. To compare the CallStaticObjectMethod (gBinderProxyOffsets. MClass, gBinderProxyOffsets mGetInstance, jlong nativeData, (jlong) val.get()), should feel very similar. In front of the two parameters gBinderProxyOffsets. MClass and gBinderProxyOffsets. MGetInstance represent respectively is BinderProxy. The class object and getInstance method name, Static int int_register_android_os_BinderProxy(JNIEnv* env) initialize gBinderProxyOffsets The latter two arguments are the two arguments to getInstance, passing the pointer to jlong into binderproxy.getInstance (). Now look at the logic in getInstance

  1. BinderProxy can be cached because BinderProxy<==>Binder is one-to-one, so for a Binder service, you only need a BinderProxy. So it could be global
  2. If not, create a BinderProxy instance and store it in the cache
  3. The subsequent initialization of BinderProxy does only one thing that will be passedBinderProxyNativeDataThe jLong corresponding to the pointer of is stored in mNativeData, realizing the association between BinderProxy and Native binder

The above is to get IServiceManager the whole process, need to remember is IServiceManager all interface, is ultimately called binderproxy.transact () method

ServiceManager.addService()

Back to where the service was originally added, the method is signed as follows

public static void addService(String name, IBinder service, boolean allowIsolated,int dumpPriority){
    try {
        getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
    } catch (RemoteException e) {
        Log.e(TAG, "error in addService", e); }}Copy the code

AddService () after getting IserviceManager will call the aiDL-generated method

IServiceManager.Proxy.addService()

The code for this method is posted above and the logic is as follows

  1. Fetch a Parcel object, which comes in two types: Data, which delivers data, and reply, which gets a reply
  2. Write two strings, the token and the service name
  3. Write the Binder object, which we discuss below
  4. callIBinder.transact()Method, as we said before when we got IServiceManager, the return isBinderProxyObject instance, so lookBinderProxy.transact()methods

BinderProxy.transact()

  1. Binder for async, there’s a variable heremWarnOnBlocking, remember before in the callBinderInternal.getContextObject()And then we did one more thingBinder.allowBlocking(BinderInternal.getContextObject())This is going to bemWarnOnBlokingSet it to false, so this logic is usually lost
  2. Whether to add Trace is mainly for performance tracing
  3. What follows is a bunch of operations that I don’t quite understand what to do, and finally the call totransactNative()This is a native method with the following signaturetransactNative(int code, Parcel data, Parcel reply, int flags), corresponding toandroid_util_Binder.cpptheandroid_os_BinderProxy_transact()

android_os_BinderProxy_transact()

  1. Check whether dataObj is NULL, so call it even if no data is passedtransact()Before the callParcel.obtain()Get a Parcel object
  2. Converting a Java-side Parcel object to a native Parcel, including Data and Reply, works in much the same way as BinderProxy, which we’ll discuss later
  3. After the callgetBPNativeData()willBinderProxy.mNativeDataTo point toBinderProxyNativeDataSp <IBinder>
  4. Then call native Bindertransact()Method, save this part for native parsing

IServiceManager.getService()

In fact, the same logic is delivered to JNI and native binder through Binderproxy.tansact (), including the initial Demo where the client calls sayHello() to communicate with the service. Is it similar to addService()? The logic is exactly the same. The only difference between getService() and addService() is that for the processing of reply, getService() needs to get the IBinder returned from native, which we’ll see when we talk about Parcel next

Parcel and Parceable

Why Parcelable

Deep and shallow copies of objects are involved here, and since Java objects are used through references, references are essentially Pointers. Herein lies the problem, shallow copy just copy one byte bytes of data from the object, if the object contains an object reference, point to the same object can cause copy out, this is just one of, for remote invocation, if it is simply shallow copy, copy of object reference is null memory isolation (process).

The solution is deep copy, the so-called deep copy is the object reference is constantly “parse”, how to “parse”? Don’t forget the eight basic types in Java that make up all Java objects, which are parsed into an ordered sequence of bytes. It’s ordered because parsing is done in a certain order, or it can’t be reversed. The process of “de-parsing” is the process of turning an ordered sequence of bytes into an object, the process of “parsing” is serialization, and the process of “de-parsing” is deserialization

Serialization in Java requires implementing Serializeable and setting a serialVersionUID, but since Serializeable produces a large number of temporary objects, Android serialization uses Parceable

The role of the Parcel

A Parcel serializes all objects that need to be passed to the Binder driver into an ordered sequence of bytes (which is why Parceable’s createFromParcel() and writeToParcel() write and read orders can’t be out of order). The data is then read from the Binder driver, deserialized into objects, and returned to the caller or waiter. A Parcel supports the following types:

  1. Java’s six basic types are supported except for char and short, which do not see interfaces for either
  2. String or CharSequence
  3. The object of Parceable
  4. Binder object
  5. Some common collection classes and arrays of the above type

Parcel.writeInt()

WriteByte () and writeBoolean() also end up calling writeInt(). For example, take a look at the Parcel process where writeInt() directly calls the nativeWriteInt() method

android_os_Parcel_writeInt()

CPP calls writeInt32() to convert the pointer to the native Parcel object to writeInt32(). How is the Parcel object initialized? Parcel data = Parcel.obain()

Parcel.obtain()

  1. See if there are Parcel objects in the cache pool and return if there are
  2. If not, creating a new Parcel object returns

Parcel construction method

It’s just init(nativePtr);

Parcel.init()

The method signature is private void init(Long nativePtr), you need to pass a parameter nativePtr, in the constructor is passed 0, so it is equivalent to a null pointer, so the process is else

private void init(long nativePtr) {
    if(nativePtr ! =0) {
        mNativePtr = nativePtr;
        mOwnsNativeParcelObject = false;
    } else {
        mNativePtr = nativeCreate();
        mOwnsNativeParcelObject = true; }}Copy the code

android_os_Parcel_create()

Static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz) A Parcel object is created and the pointer is returned as jlong without initializing anything

Why a Parcel can write IBinder(writeStrongBinder()IBinder is neither a Parcelable nor a base type nor a Parcelable object

To solve this problem, we need to first understand what Binder services correspond to in Native.

Binder initialization

The function is signed like public Binder(String Descriptor), and one of the really important things that you do in there is call getNativeBBinderHolder()

android_os_Binder_getNativeBBinderHolder()

GetNativeBBinderHolder () corresponds to android_util_Binder. CppJNI function android_os_Binder_getNativeBBinderHolder(), The JavaBBinderHolder object is created and the jLong corresponding to the pointer is returned

Parcel.writeStrongBinder()

Call the nativeWriteStrongBinder ()

android_os_Parcel_writeStrongBinder()

NativeWriteStrongBinder () corresponds to android_os_Parcel. CppJNI function android_os_Parcel_writeStrongBinder(),

  1. Fetch a pointer of type long to the local Parcel stored in the Java Parcel
  2. callibinderForJavaObject()For sp < IBinder >
  3. Calling the local ParcelwriteStrongBinder()Function, which is reserved for carding with native Binder

ibinderForJavaObject()

Note that the method jumps to android_os_binder.cpp,

  1. Determines whether a Java object is null
  2. Binder (equivalent to Instanceof), true in this case
    1. Gets the JavaBBinderHolder pointer
    2. Call the javabbinderholder.get () function
  3. Check whether it is a BinderProxy type

JavaBBinderHolder.get()

  1. Try to get mBinder as previously inandroid_os_Binder_getNativeBBinderHolder()Does not initialize this property, so NULL is obtained
  2. If sp

    is NULL, a new JavaBBinder object is created and the Java layer Binder object is passed in

The problem summary

For Java layer IBinder, instead of Java objects, it writes JavaBBinder objects, which are native layer IBinder objects, and then native Parcel, which raises a new problem. The answer to how JavaBBinder connects to Java layer binders lies in the Binder objects passed in when creating JavaBBinder objects, Binder.ontransact () will be called after the Java layer binder.exectransact () in javabbinder.ontransact ()