Search code examples
androidc++java-native-interfaceoverridingjnienv

Implementing Android Event Handlers Using C++


I have a layout design in Java that I am currently porting over to C++ via JNI. I am practically done at this point, but I am currently puzzled on how I am supposed to set up event handlers like setOnClickListener for example. I have gone through the JNI specification and have not gotten much luck.

If anyone can port the following snippet to C++ or lead me in the right direction (more reasonable due to how much code the result would be), that would be greatly appreciated.

    public void setOnClickListener(boolean modification, int index, int commandIndex, final TextView textView){
        final int key = index;
        final int command = commandIndex;
        if(modification) {
            textView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    changingMenu(key, command, textView);
                    Runnable r = new Runnable() {
                        @Override
                        public void run() {
                            resetMenu(key, command, textView);
                        }
                    };

                    Handler h = new Handler();
                    h.postDelayed(r, 250);
                }
            });
            return;
        }
        menuTitle.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                toggleMenu();
            }
        });
    }

EDIT: Passing bad argument to setOnClickListener

Java

Object getProxy (MyInvocationHandler mih) {
    ClassLoader classLoader = new ClassLoader() {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return super.loadClass(name);
        }
    };
    return java.lang.reflect.Proxy.newProxyInstance(classLoader, new Class[] {  }, mih);
}

C++

jobject createProxyInstance(JNIEnv *env, jclass cls, CFunc cfunc) {
    jclass cls_IH = env->FindClass("com/app/core/MyInvocationHandler");
    jmethodID cst_IH = env->GetMethodID(cls_IH, "<init>", "(J)V");
    jobject myIH = env->NewObject(cls_IH, cst_IH, (jlong)cfunc);

    jclass klass = env->FindClass("com/app/core/Activity");
    jmethodID method = env->GetMethodID(klass, "getProxy", "(Lcom/app/core/MyInvocationHandler;)Ljava/lang/Object;");
    return env->CallObjectMethod(context, method, myIH); //Returning wrong object?
}

jobject aa (JNIEnv *env, jobject obj, jobject proxy, jobject method, jobjectArray args) {
    __android_log_print(ANDROID_LOG_ERROR, "TEST", "SUCCESS");
}

void setListeners() {
    jclass klass = env->FindClass("android/view/View");
    jmethodID method = env->GetMethodID(klass, "setOnClickListener", "(Landroid/view/View$OnClickListener;)V");
    klass = env->FindClass("android/view/View$OnClickListener");
    env->CallVoidMethod(imageView, method, createProxyInstance(env, klass, &aa));
}

Solution

  • The syntax you show boils down to creating anonymous inner classes at compile time and inserting calls to create objects of these classes (with the correct variables in scope) in place of the new View.OnClickListener() { ... } expression.

    I see the following two options:

    • For each different interface, you create a small Java class that implements the interface, with a native implementation of the interface's method(s). This is the most direct approach, but it does require you to keep the tens or hundreds of interface implementations straight.

    • You use java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) to dynamically create objects that implement the necessary interfaces. This will re-route each method invocation to your InvocationHandler implementation, which should be a Java class that has a native Object invoke(Object proxy, Method method, Object[] args) implementation.

      To make all this reusable, you can implement this InvocationHandler to wrap a std::function object, so the final call to eg menuTitle.setOnClickListener might look like the following:

    env->CallVoidMethod(menuTitle, menuTitle_setOnClickListener,
     createProxyInstance(env, cls_View_OnClickListener, [](JNIEnv *env, jobject proxy, jobject method, jobjectArray args) {
       ... 
    });
    

    For the latter solution, define the following Java class:

    class MyInvocationHandler implements InvocationHandler {
      private long cfunc;
      MyInvocationHandler(long cfunc) { this.cfunc = cfunc; }
      public native static Object invoke(Object proxy, Method method, Object[] args);
    }
    

    Which you implement on the C++ side as:

    typedef jobject (*CFunc)(JNIEnv *env, jobject obj, jobject proxy, jobject method, jobjectArray args)
    extern "C" jobject Java_MyInvocationHandler_invoke(JNIEnv *env, jobject obj, jobject proxy, jobject method, jobjectArray args) {
      jclass cls_myIH = env->GetObjectClass(obj);
      jfieldID fld_myIH_cfunc = env->GetFieldID(cls_myIH, "cfunc", "J");
      CFunc cfunc = (CFunc)env->GetLongField(obj, fld_myIH_cfunc);
      cfunc(env, proxy, method, args);
      return nullptr;
    }
    

    Finally, we can implement createProxyInstance as follows:

    jobject createProxyInstance(JNIEnv *env, jclass cls, CFunc cfunc) {
      jclass cls_IH = env->GetClass("MyInvocationHandler");
      jmethodID cst_IH = env->GetMethodID(cls_ID, "<init>", "(J)V");
      jobject myIH = env->NewObject(cls_ID, cst_IH, (jlong)cfunc);
    
      // now call Proxy.createProxyInstance with this object as InvocationHandler
    }