The process from Java layer to Native layer Binder

Keywords: Android Java

We already know in System Server analysis this article that through Service Manager. addService ("xx", new XXBinder (this)); many services have been added, which are services inherited from Binder and require cross-process operations. It's also true that services, including those we declare ourselves, are inherited from Binder.

And we have introduced the underlying process of adding services and query services in the last chapter, but the problem is that they are both in the native layer and there is no Java code, so how does Binder's communication connect with the Java layer?

With this in mind, we'll follow Binder's approach to construction.

    public Binder() {
        init();
        ...
    }

Where private native final void init();
Then go into android_utils_Binder.cpp

Here's a trick to share. I use Source Insight 3 to view the source code, so I use the shortcut Ctrl +? Search.
Because the jni file has a rule, such as init() in this Binder, the content of the search is package name + class name + method name.
Just change the. number to, so it's android_os_Binder_init().
static void android_os_Binder_init(JNIEnv* env, jobject obj)
{
    JavaBBinderHolder* jbh = new JavaBBinderHolder();//[1]
    if (jbh == NULL) {
        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
        return;
    }
    ALOGV("Java Binder %p: acquiring first ref on holder %p", obj, jbh);
    jbh->incStrong((void*)android_os_Binder_init);
    env->SetLongField(obj, gBinderOffsets.mObject, (jlong)jbh);//Set the JavaBBinderHolder object to gBinderOffsets.mObject
}

1 JavaBBinderHolder()

class JavaBBinderHolder : public RefBase
{
public:
    sp<JavaBBinder> get(JNIEnv* env, jobject obj)
    {
        AutoMutex _l(mLock);
        sp<JavaBBinder> b = mBinder.promote();
        if (b == NULL) {
            b = new JavaBBinder(env, obj);//[1.1]
            mBinder = b;
        }
        return b;
    }
    sp<JavaBBinder> getExisting()
    {
        AutoMutex _l(mLock);
        return mBinder.promote();
    }
private:
    Mutex           mLock;
    wp<JavaBBinder> mBinder;
};

1.1 JavaBBinder()

The object here is a service class that inherits Binder from the Java layer

Here we see gBinderOffsets.mExecTransact, which is the java layer method name. We find the corresponding code for assignment.

static int int_register_android_os_Binder(JNIEnv* env)
{
    jclass clazz = FindClassOrDie(env, kBinderPathName);

    gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
    gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");

    return RegisterMethodsOrDie(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));
}

That is to say, when int_register_android_os_Binder is called, the id of execTransact, the java layer method, is stored in the gBinderOffsets.mExecTransac variable.

class JavaBBinder : public BBinder
{
public:
    JavaBBinder(JNIEnv* env, jobject object)
        : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))
    {
        android_atomic_inc(&gNumLocalRefs);
        incRefsCreated(env);
    }
    bool checkSubclass(const void* subclassID) const
    {
        return subclassID == &gBinderOffsets;
    }

    jobject object() const
    {
        return mObject;
    }
protected:
    virtual ~JavaBBinder()
    {
        android_atomic_dec(&gNumLocalRefs);
        JNIEnv* env = javavm_to_jnienv(mVM);
        env->DeleteGlobalRef(mObject);
    }
    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();
        //This code calls the execTransact method to transfer these data.
        jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
            code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);

        if (code == SYSPROPS_TRANSACTION) {
            BBinder::onTransact(code, data, reply, flags);
        }
        return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;
    }

    virtual status_t dump(int fd, const Vector<String16>& args)
    {
        return 0;
    }

private:
    JavaVM* const   mVM;
    jobject const   mObject;//The corresponding is the Java layer Binder
};

Here we have linked the Binder service in the Java layer to the BBinder in the Native layer. The onTransact() method can be called from the driver layer to the execTransact() method of the Java layer.

Here we also notice a problem, because the onTransact() method is a function of BBinder, and now Java BBinder overrides this function, so the call will call execTransact(), then where is onTransact()?

BpBinder::transact()->
IPCThreadState::transact()->
IPCThreadState::waitForResponse()->
IPCThreadState::executeCommand()

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    BBinder* obj;
    RefBase::weakref_type* refs;
    status_t result = NO_ERROR;
    switch ((uint32_t)cmd) {
    ...
    case BR_TRANSACTION:
        if (tr.target.ptr) {
            sp<BBinder> b((BBinder*)tr.cookie);
            error = b->transact(tr.code, buffer, &reply, tr.flags);
        }
    }
    ...
}
status_t BBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){
    status_t err = NO_ERROR;
    switch (code) {
        case PING_TRANSACTION:
            reply->writeInt32(pingBinder());
            break;
        default:
            err = onTransact(code, data, reply, flags);
            break;
    }
  ...
}

At this point, the derived class of BBinder is the JavaBBinder class, and then calling its onTransact() method calls the execTransact() method of the java layer.

When using IPCThreadState::transact to send data, waitForResponse() is called, then executeCommand() is called, then transact() is called, and onTransact() is called.

That is to say, the onTransact() method is invoked when the data is returned at the native layer.

In Binder.java
Let's take a look at execTransact() in the Java layer.

    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);//Callback inherits the onTransact() method of the java layer Binder
        } catch (RemoteException e) {
            if ((flags & FLAG_ONEWAY) != 0) {
                Log.w(TAG, "Binder call failed.", e);
            } else {
                reply.setDataPosition(0);
                reply.writeException(e);
            }
            res = true;
        } catch (RuntimeException e) {
            if ((flags & FLAG_ONEWAY) != 0) {
            } 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;
    }

We'll see the corresponding method for the java layer Binder below.

Binder references applied in the Java layer are done through the agent of Binder.

Let's first look at a file generated by aidl and then analyze it:

public interface IHelloService extends android.os.IInterface
{
    public static abstract class Stub extends android.os.Binder implements IHelloService
    {
        private static final java.lang.String DESCRIPTOR = "IHelloService";
        public Stub()
        {
            this.attachInterface(this, DESCRIPTOR);
        }
        public static IHelloService asInterface(android.os.IBinder obj)
        {
            if ((obj==null)) {
            return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof IHelloService))) {
            return ((IHelloService)iin);
            }
            return new IHelloService.Stub.Proxy(obj);
        }

        @Override public android.os.IBinder asBinder()
        {
            return this;
        }
        @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_sayhello:
                {
                    data.enforceInterface(DESCRIPTOR);
                    this.sayhello();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_sayhello_to:
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _result = this.sayhello_to(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }


        private static class Proxy implements IHelloService
        {
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote)
            {
                mRemote = remote;
            }
            @Override public android.os.IBinder asBinder()
            {
                return mRemote;
            }
            public java.lang.String getInterfaceDescriptor()
            {
                return DESCRIPTOR;
            }
            @Override public void sayhello() 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_sayhello, _data, _reply, 0);
                    _reply.readException();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
            @Override public int sayhello_to(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);
                    mRemote.transact(Stub.TRANSACTION_sayhello_to, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                    return _result;
            }
        }

        static final int TRANSACTION_sayhello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_sayhello_to = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

    }

    public void sayhello() throws android.os.RemoteException;
    public int sayhello_to(java.lang.String name) throws android.os.RemoteException;
}

The following is the invocation method:

/* 1. getService */
IBinder binder = ServiceManager.getService("hello");
if (binder == null)
{
    System.out.println("can not get hello service");
    Slog.i(TAG, "can not get hello service");
    return;
}
IHelloService svr = IHelloService.Stub.asInterface(binder);

We can see from the java files generated by aidl through the asInterface method

public static IHelloService asInterface(android.os.IBinder obj)
{
    if ((obj==null)) {
    return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof IHelloService))) {
    return ((IHelloService)iin);
    }
    return new IHelloService.Stub.Proxy(obj);
}

private static class Proxy implements IHelloService
{
    private android.os.IBinder mRemote;
    Proxy(android.os.IBinder remote)
    {
        mRemote = remote;
    }
    ...
}

svr.sayhello() when called;

It calls:

@Override public void sayhello() 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_sayhello, _data, _reply, 0);
        _reply.readException();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

We all know that mRemote is actually a BpBinder object.

So call the transact method of the BpBinder object

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }

    return DEAD_OBJECT;
}

This method, which we have analyzed before, is to write and read data through BpBinder.

Conclusion:

Let's start with the construction of Binder, because the Binder service can be either a service added by System Server or an anonymous service created by ourselves, but it calls the construction method. It is found that the init method of the native layer is called in the constructor. What the init() method does is create a JavaBBinderHolder object in which the get method obtains a JavaBBinder object, which inherits the BBinder class. And by rewriting BBinder's onTransact() method, onTransact() calls execTransact() of the Java layer, which connects the Java layer to the native layer.

Then we analyze how to call the onTransact() method from the driver to BBinder. Our order of call is:
BpBinder::transact()-> IPCThreadState::transact()-> IPCThreadState::waitForResponse()-> IPCThreadState::executeCommand()
In our previous article, we analyzed that BpBinder is a member variable mRemote of BpService Manager, that is, the native layer obtained by SP < IService Manager > SM (default Service Manager ()); method.

Where BpBinder can call BpBinder:: transact () - > IPCThreadState:: self () - > transact () to communicate with the driver. This method will be called to IPCThreadState::executeCommand().

sp<BBinder> b((BBinder*)tr.cookie);
error = b->transact(tr.code, buffer, &reply, tr.flags);

Where b - > transact () calls onTransact(). Note that b is BBinder. We see JavaBBinder inheritance and BBinder in Java code, so we call onTransact() of JavaBBinder at this time. In this callback function, we will see the call of execTransact(), and then we see the call of onTransact() of Binder.java in the Binder.java's execTransact() method. Callback the onTransact() method in our generated aidl file.

Finally, we also see how to communicate from the java layer to the bottom layer, which we can see in the files generated by aidl

sayhello():
    mRemote.transact(Stub.TRANSACTION_sayhello, _data, _reply, 0);

mRemote is BpBinder.

In this way, we have completed the creation of the whole process from java to the bottom.

Posted by Albright on Tue, 16 Apr 2019 19:48:33 -0700