Understanding Android Binder Mechanism (3/3): Java Layer

Keywords: Java Android Oracle SDK

This is the third and final article of Android Binder mechanism analysis. This article explains the logic of the Java part of the Binder Framework.

For the first two articles on Binder mechanism analysis, please step here:

Understanding Android Binder Mechanism (1/3): Driver Chapter

Understanding Android Binder Mechanism (2/3): C++ Layer: Driver Chapter

The path of the related source code described below in the AOSP source tree is as follows:

// Binder Framework JNI
/frameworks/base/core/jni/android_util_Binder.h
/frameworks/base/core/jni/android_util_Binder.cpp
/frameworks/base/core/jni/android_os_Parcel.h
/frameworks/base/core/jni/android_os_Parcel.cpp

// Binder Framework Java Interface
/frameworks/base/core/java/android/os/Binder.java
/frameworks/base/core/java/android/os/IBinder.java
/frameworks/base/core/java/android/os/IInterface.java
/frameworks/base/core/java/android/os/Parcel.java

Main structure

Android applications are developed in the Java language, and the Binder framework naturally provides interfaces at the Java layer.

As we have seen in the previous article, the Binder mechanism has been fully implemented in the C++ layer. Therefore, the Java layer does not need to repeat the implementation at all, but links up the C++ layer through JNI to reuse its implementation.

The following figure describes the interface between the Binder Framework Java layer and the C++ layer.

Here are some classes of Java layer and JNI layer in the diagram (see the C++ layer for an explanation). Here ):

Name type Explain
IInterface interface Interface for inheritance of Java Layer Binder Service Interface
IBinder interface The IBinder class in the Java layer provides a transact method to call remote services
Binder class The IBinder interface is implemented and the implementation of JNI is encapsulated. Base Class of Java Layer Binder Service
BinderProxy class The IBinder interface is implemented and the implementation of JNI is encapsulated. Providing transact method calls to remote services
JavaBBinderHolder class JavaBBinder is internally stored
JavaBBinder class Pass onTransact calls on the C++ side to the Java side
Parcel class Data wrapper in Java layer, see Parcel class analysis in C++ layer

The two classes of IInterface, IBinder and C++ layer here are of the same name. The same name is not coincidental: they are not only the same name, but their role and the interfaces they contain are almost the same. The difference lies only in the C++ layer and the Java layer.

In addition to IInterface and IBinder, here the Binder and BinderProxy classes correspond to the C++ classes. The corresponding relationships between the Java layer and the C++ layer classes are listed below.

C++ Java Layer
IInterface IInterface
IBinder IBinder
BBinder Binder
BpProxy BinderProxy
Parcel Parcel

Connection of JNI

The full name of JNI is Java Native Interface, which is a mechanism provided by Java Virtual Machine. This mechanism enables native code to communicate with Java code. Simply put, we can call Java code on the C/C++ side or C/C++ code on the Java side.

For a detailed description of JNI, see Oracle's official documentation: Java Native Interface This is not much to say.

In fact, many services or mechanisms in Android are implemented in the C/C++ layer. To reuse these implementations into the Java layer, it is necessary to connect them through JNI. In AOSP source code, / frameworks/base/core/jni / directory source code is specifically used to dock the JNI implementation of the Framework layer.

Looking at the implementation of Binder.java, we can see that many of these methods are modified with native keywords, and there is no way to implement entities. These methods are actually implemented in C++:

public static final native int getCallingPid();

public static final native int getCallingUid();

public static final native long clearCallingIdentity();

public static final native void restoreCallingIdentity(long token);

public static final native void setThreadStrictModePolicy(int policyMask);

public static final native int getThreadStrictModePolicy();

public static final native void flushPendingCommands();

public static final native void joinThreadPool();

The following code in the android_util_Binder.cpp file sets the corresponding relationship between Java method and C++ method:

static const JNINativeMethod gBinderMethods[] = {
    { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid },
    { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
    { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity },
    { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity },
    { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy },
    { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
    { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
    { "init", "()V", (void*)android_os_Binder_init },
    { "destroy", "()V", (void*)android_os_Binder_destroy },
    { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
};

This correspondence implies that when the getCallingPid method in Binder.java is called, the real implementation is actually android_os_Binder_getCallingPid. When the getCallingUid method is called, the real implementation is android_os_Binder_getCallingUid, which is the same as the others.

Then we'll look at the implementation of the android_os_Binder_getCallingPid method and see that it's actually a connection to libbinder:

static jint android_os_Binder_getCallingPid(JNIEnv* env, jobject clazz)
{
    return IPCThreadState::self()->getCallingPid();
}

Here you see how the Java-side code calls the C++ method in libbinder. So, how does the opposite direction be invoked? Most importantly, how can BBinder::onTransact in libbinder be invoked to Binder::onTransact in Java?

This logic is handled in JavaBBinder::onTransact in android_util_Binder.cpp. Java BBinder is a subclass of BBinder. Its class structure is as follows:

JavaBBinder::onTransact key code is as follows:

virtual status_t onTransact(
   uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
{
   JNIEnv* env = javavm_to_jnienv(mVM);

   IPCThreadState* thread_state = IPCThreadState::self();
   const int32_t strict_policy_before = thread_state->getStrictModePolicy();

   jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
       code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
   ...
}

Notice this line in this code:

jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
  code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);

This line of code actually calls the method offset for mExecTransact on mObject. Several parameters are described as follows:

  • mObject points to the Binder object on the Java side
  • gBinderOffsets.mExecTransact points to the execTransact method of the Binder class
  • data calls the parameters of the execTransact method
  • code, data, reply, flags are all parameters passed to the calling method execTransact

JNIEnv.CallBooleanMethod is implemented by a virtual machine. That is, Virtual Opportunities provide native methods to call a Java Methods on Object (we'll talk about Java virtual machines on Android in the future).

In this way, the Java layer Binder::execTransact method is called in Java BBinder:: onTransact of the C++ layer. In the Binder::execTransact method, we call our own onTransact method, which guarantees that the whole process is connected in series:

private boolean execTransact(int code, long dataObj, long replyObj,
       int flags) {
   Parcel data = Parcel.obtain(dataObj);
   Parcel reply = Parcel.obtain(replyObj);
   boolean res;
   try {
       res = onTransact(code, data, reply, flags);
   } catch (RemoteException|RuntimeException e) {
       if (LOG_RUNTIME_EXCEPTION) {
           Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
       }
       if ((flags & FLAG_ONEWAY) != 0) {
           if (e instanceof RemoteException) {
               Log.w(TAG, "Binder call failed.", e);
           } else {
               Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
           }
       } else {
           reply.setDataPosition(0);
           reply.writeException(e);
       }
       res = true;
   } catch (OutOfMemoryError e) {
       RuntimeException re = new RuntimeException("Out of memory", e);
       reply.setDataPosition(0);
       reply.writeException(re);
       res = true;
   }
   checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
   reply.recycle();
   data.recycle();

   StrictMode.clearGatheredViolations();

   return res;
}

Examples of Java Binder services

Like the C++ layer, here's a concrete example to see how the Binder service in the Java layer is implemented.

The following is a class diagram of the Activity Manager implementation:

Following are the descriptions of several classes in the figure above:

Class name Explain
IActivityManager Public interface for Binder services
ActivityManagerProxy Remote Interface for Client Call
ActivityManagerNative Base Class of Binder Service Implementation
ActivityManagerService Real Implementation of Binder Service

After looking at the implementation of Binder C++ layer, it should be easy to understand this structure. The organizational structure and the implementation of C++ layer services are the same.

For developers of Android applications, instead of directly touching the classes shown above, we use interfaces in Android. app. Activity Manager.

Let's take a look at the relationship between the interface in Android. app. Activity Manager and the implementation of the above figure. Let's take a look at one of these ways:

public void getMemoryInfo(MemoryInfo outInfo) {
   try {
       ActivityManagerNative.getDefault().getMemoryInfo(outInfo);
   } catch (RemoteException e) {
       throw e.rethrowFromSystemServer();
   }
}

The implementation of this method calls the method in ActivityManagerNative.getDefault(), so let's look at what ActivityManagerNative.getDefault() returns to.

static public IActivityManager getDefault() {
   return gDefault.get();
}

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
   protected IActivityManager create() {
       IBinder b = ServiceManager.getService("activity");
       if (false) {
           Log.v("ActivityManager", "default service binder = " + b);
       }
       IActivityManager am = asInterface(b);
       if (false) {
           Log.v("ActivityManager", "default service = " + am);
       }
       return am;
   }
};

As we can see in this code, it's actually through IBinder first. B = Service Manager. getService ("activity"); Get the Binder object of Activity Manager ("activity" is the Binder service identifier of Activity Manager Service), and then let's look at the implementation of asInterface(b):

static public IActivityManager asInterface(IBinder obj) {
   if (obj == null) {
       return null;
   }
   IActivityManager in =
       (IActivityManager)obj.queryLocalInterface(descriptor);
   if (in != null) {
       return in;
   }

   return new ActivityManagerProxy(obj);
}

It should be clear here: first, determine whether there is a local Binder through queryLocal Interface, and if there is one, return it directly, otherwise create an ActivityManagerProxy object. Obviously, assuming that this method is invoked in the process in which the Activity Manager Service is located, queryLocalInterface will return directly to the local Binder, while assuming that it is invoked in other processes, this method will return empty, which leads to the other invocation to obtain an object that is actually Activity Manager Proxy. I think the reader can also understand the route of calling the ActivityManagerProxy method after getting the ActivityManagerProxy object: calling the method in ActivityManagerService through Binder driver across processes.

The implementation of the asInterface method here will make us feel familiar. Yes, because the implementation mode here is the same as that in C++ layer.

Service Manager in Java Layer

Source path:

frameworks/base/core/java/android/os/IServiceManager.java
frameworks/base/core/java/android/os/ServiceManager.java
frameworks/base/core/java/android/os/ServiceManagerNative.java
frameworks/base/core/java/com/android/internal/os/BinderInternal.java
frameworks/base/core/jni/android_util_Binder.cpp

With Java-side Binder services, there is no lack of Java-side Service Manager. Let's first look at the structure of Service Manager on the Java side:

From this class diagram, we can see that the interface between the Java layer ServiceManager and the C++ layer is the same.

Then we select the addService method to see the implementation:

public static void addService(String name, IBinder service, boolean allowIsolated) {
   try {
       getIServiceManager().addService(name, service, allowIsolated);
   } catch (RemoteException e) {
       Log.e(TAG, "error in addService", e);
   }
}
    
   private static IServiceManager getIServiceManager() {
   if (sServiceManager != null) {
       return sServiceManager;
   }

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

Obviously, the key to this code is the following call:

ServiceManagerNative.asInterface(BinderInternal.getContextObject());

Then we need to look again at the BinderInternal.getContextObject() and ServiceManagerNative.asInterface methods.

BinderInternal.getContextObject() is a JNI method. In fact, the modern code is in android_util_Binder.cpp:

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}

The implementation of Service Manager Native. asInterface is the same as other Binder services.

static public IServiceManager asInterface(IBinder obj)
{
   if (obj == null) {
       return null;
   }
   IServiceManager in =
       (IServiceManager)obj.queryLocalInterface(descriptor);
   if (in != null) {
       return in;
   }
   
   return new ServiceManagerProxy(obj);
}

The query Local Interface is used to see if the local Binder can be obtained, and if not, the ServiceManagerProxy object is created and returned.

And Service Manager Proxy is naturally the same implementation routine as other Binder Proxy:

public void addService(String name, IBinder service, boolean allowIsolated)
       throws RemoteException {
   Parcel data = Parcel.obtain();
   Parcel reply = Parcel.obtain();
   data.writeInterfaceToken(IServiceManager.descriptor);
   data.writeString(name);
   data.writeStrongBinder(service);
   data.writeInt(allowIsolated ? 1 : 0);
   mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
   reply.recycle();
   data.recycle();
}

With the above explanation, this code should be relatively easy to understand.

About AIDL

As the last part of the Binder mechanism, let's talk about the AIDL mechanism that developers often use.

The full name of AIDL is Android Interface Definition Language, which is a mechanism provided by Android SDK. With this mechanism, applications can provide cross-process services for other applications. A detailed description of AIDL can be found in the official development document: https://developer.android.com/guide/components/aidl.html.

Here, let's look at the relationship between AIDL and the Binder framework with examples from official documents.

Developing an AIDL-based Service requires three steps:

  1. Define a. aidl file
  2. Implementation interface
  3. Exposing interfaces to clients

Ail files are defined using Java language grammar. Each. Ail file can contain only one interface and all method declarations of the interface.

By default, data types supported by AIDL include:

  • Basic data types (i.e. int, long, char, boolean, etc.)
  • String
  • CharSequence
  • List (List element type must be AIDL supported)
  • Map (Elements in Map must be AIDL-supported)

For the interface in AIDL, it can contain 0 or more parameters and return void or a value. All non-basic types of parameters must contain a label describing the flow of data, possibly in, out or inout.

Here is an example of an aidl file:

// IRemoteService.aidl
package com.example.android;

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

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** 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);
}

This file contains two interfaces:

  • getPid is an interface with no parameters and the return value type is int
  • Basic Types, which contains several basic types as parameters, has no return value

For projects containing. aidl files, Android IDE (formerly Eclipse, now Android Studio) generates the corresponding Java files for the aidl files when compiling the project.

The structure of the java file generated for the above aidl file is shown in the following figure:

In this generated Java file, it includes:

  • An interface named IRemoteService, which inherits from android.os.IInterface and contains the interface methods we declared in the aidl file
  • IRemoteService contains a static internal class named Stub, which is an abstract class inherited from android.os.Binder and implements the IRemoteService interface. This class contains an onTransact method
  • Stub also contains a static inner class named Proxy, which also implements the IRemoteService interface.

Look closely at the methods contained in Stub class and Proxy. Are you familiar with them? Yes, this is the same pattern as the service implementation described earlier. Here we list the corresponding relationships among different levels:

C++ Java Layer AIDL
BpXXX XXXProxy IXXX.Stub.Proxy
BnXXX XXXNative IXXX.Stub

Finally, we'll look at the implementation logic in the generated Stub and Proxy classes for the sake of the integrity of the whole structure.

Stub is the parent class for developers to implement business, while Proxy implements the interface provided to them. Both Stub and Proxy classes have an asBinder method.

The asBinder implementation in the Stub class returns its own object:

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

The implementation of asBinder in Proxy is to return the mRemote object obtained in the constructor. The relevant code is as follows:

private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {
	mRemote = remote;
}

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

And the mRemote object here is actually the identification of the remote service in the current process.

As we mentioned above, Stub classes are the parent classes used to provide developers with business logic. Developers inherit from Stub and then complete their own business logic implementations, such as:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
   public int getPid(){
       return Process.myPid();
   }
   public void basicTypes(int anInt, long aLong, boolean aBoolean,
       float aFloat, double aDouble, String aString) {
       // Does something
   }
};

This Proxy class is the external interface used by the caller. We can see how the interface in Proxy is implemented:

The getPid method in Proxy is implemented as follows:

@Override
public int getPid() 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);
		mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
		_reply.readException();
		_result = _reply.readInt();
	} finally {
		_reply.recycle();
		_data.recycle();
	}
	return _result;
}

This is where the interface for the corresponding remote service is invoked through the Parcel object and transact. In the Stub class, the generated onTransact method handles the requests here:

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
		throws android.os.RemoteException {
	switch (code) {
	case INTERFACE_TRANSACTION: {
		reply.writeString(DESCRIPTOR);
		return true;
	}
	case TRANSACTION_getPid: {
		data.enforceInterface(DESCRIPTOR);
		int _result = this.getPid();
		reply.writeNoException();
		reply.writeInt(_result);
		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;
	}
	}
	return super.onTransact(code, data, reply, flags);
}

What onTransact does is:

  1. Identify which interface the request is based on code
  2. Get the parameters of the request through data
  3. Call abstract methods implemented by subclasses

With the previous explanation, it should not be difficult to understand this part of the content.

At this point, we've finally finished talking about Binder.

Congratulations, you have mastered one of the most complex modules of Android system.

Above

References and Recommended Readings


Original: http://qiangbo.space/2017-03-15/Android Anatomy_Binder_Java/

Posted by Simon180 on Fri, 21 Jun 2019 15:29:16 -0700