JNI&NDK开发最佳实践(十):补充要点(持续更新)

一、在C中实现Java回调函数

我们知道在C中通过传递函数指针可以轻易实现函数回调的效果,而在java中则一般是通过构造匿名内部类对象来间接实现函数回调。那么如何在C中构造一个具有回调函数功能的对象呢?
例如在java中给一个Button设置点击事件回调

mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
});

那倘若我们要在C中通过JNI的方式来实现给Button设置点击回调,该如何实现呢?

  1. java中声明本地方法
public native void setOnclickListener();

2.通过env反射调用View#setOnClickListener方法

extern "C"
JNIEXPORT void JNICALL
Java_com_example_taoying_testndkapp_MainActivity_setOnclickListener(JNIEnv *env, jobject instance) {

    // TODO
    /*mBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

        }
    });*/
    //获取java mBtn的引用
    jclass mainActivityClass = env->FindClass("com/example/taoying/testndkapp/MainActivity");
    jfieldID buttonField = env->GetFieldID(mainActivityClass, "mBtn", "Landroid/widget/Button;");
    jobject button = env->GetObjectField(instance, buttonField);

    //调用mBtn.setOnclickListener
    jclass buttonClass = env->FindClass("android/widget/Button");
//    jclass viewClass = env->FindClass("android/view/View");
    jmethodID setOnClickListener = env->GetMethodID(buttonClass, "setOnClickListener",
                                                    "(Landroid/view/View$OnClickListener;)V");

    jobject listener = creatOnClickListener(env);
    env->CallVoidMethod(button, setOnClickListener, listener);
}

3.反射创建java层的NativeOnclickListener对象(继承自OnClickListener),将具体的回调实现通过构造方法把该函数指针传递到java层。

static jobject creatOnClickListener(JNIEnv *env) {
    jclass nativeOnclickListenerClass = env->FindClass(
            "com/example/taoying/testndkapp/NativeOnclickListener");
    jmethodID init = env->GetMethodID(nativeOnclickListenerClass, "", "(J)V");
    jlong lptr = reinterpret_cast(onClick);
//    LOGE("onClick: %p, long: %lld", ptr, lptr);
    jobject listener = env->NewObject(nativeOnclickListenerClass, init, lptr);
    return listener;
}
static void onClick(JNIEnv *env, jobject view) {
    LOGD("ZZZZZZZZZ");
}

4.java层实现继承自OnClickListener 的NativeOnclickListener,在其onClick回调中调用本地方法,最后调用到ptr所指向的本地函数。

public class NativeOnclickListener implements View.OnClickListener {
    private static final String TAG = "NativeOnclickListener";

    private final long ptr;

    public NativeOnclickListener(long ptr) {
        this.ptr = ptr;
    }

    @Override
    public void onClick(View v) {
        Log.d(TAG, "onClick: ready to call nativeOnClick"+ Long.toHexString(ptr));
        MainActivity.nativeOnClick(ptr, v);//java层声明的本地方法
    }

}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_taoying_testndkapp_MainActivity_nativeOnClick(JNIEnv *env, jclass type,
                                                               jlong ptr, jobject view) {
    LOGD("do Java_com_example_taoying_testndkapp_MainActivity_nativeOnClick: %p", onClick);
    void (*func)(JNIEnv*, jobject) = reinterpret_cast(ptr);
    if (!func){
        return;
    }
    func(env, view);
}

核心思想在于:将C中实现回调逻辑的函数的函数指针传给java。

二、C调Java之泛型的处理

在jni中反射调用java时遇到泛型该如何处理?例如如何反射调用java.util.ArrayList#add(E)???

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

处理方式和在java中反射一样。事实上在java中由于泛型擦除的缘故,即在进入 JVM 之前,与泛型相关的信息会被擦除掉。举个例子

  • List和 List在 jvm 中的 Class 都是 List.class
  • public class Erasure 中的T类型在jvm即为java.lang.Object
  • public class Erasure 中的T类型在jvm即为java.lang.String

三、C调Java之多态的处理

先提几个问题?

  • c中拿到java中的子类对象后,获取父类的方法id,反射调用,此时调用的是父类方法还是子类方法?
  • c中拿到java中的子类对象后,获取子类的方法id,反射调用,此时调用的是父类方法还是子类方法?
  • c中拿到java中的子类对象后,如何调用其父类方法和子类方法?
  • env->CallObjectMethod与env->CallNonvirtualObjectMethod二者有何区别和联系?
    至此,我们需要先了解java与C中多态的区别
    在java中,由于父类引用指向子类对象,因此持有子类对象时调用的方法总是子类实现的方法。
    而在C++中,默认是调用的父类方法,若想执行子类方法,需要将父类该方法用virtual声明为虚函数。java中所有的函数都是虚函数。
    下面通过一段代码来演示如上所述问题。
    在java中声明成员变量tomson
MainActivity.java
...
Father tomson = new Son("sonname", 5);
...

其各自实现如下所示

public class Father{
    private final String name;
    private final int age;
    private String sex = "man";

    public Father(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String speak(){
        return "I'm Father";
    }
}

public class Son extends Father {
    public Son(String name, int age) {
        super(name, age);
    }

    @Override
    public String speak() {
//        return super.speak();
        return "I'm Son";
    }
}

在C中作如下测试

extern "C"
JNIEXPORT void JNICALL
Java_com_example_taoying_testndkapp_MainActivity_printInC(JNIEnv *env, jobject instance,
                                                          jobject tomson1) {
    jclass pJclass = env->GetObjectClass(instance);
    //Father tomson时,能用Father.class找到,但无法用Son.class找到
    //Son tomson时,能用 Son.class找到,但无法用Father.class找到
    jfieldID tomsonField = env->GetFieldID(pJclass, "tomson", "Lcom/example/taoying/testndkapp/bean/Father;");//用父类class和子类class都能找到Field?
//    jfieldID tomsonField = env->GetFieldID(pJclass, "tomson", "Lcom/example/taoying/testndkapp/bean/Son;");//
    jobject tomson = env->GetObjectField(instance, tomsonField);

    jclass FatherClass = env->FindClass("com/example/taoying/testndkapp/bean/Father");
    jclass SonClass = env->FindClass("com/example/taoying/testndkapp/bean/Son");
    jmethodID speak = env->GetMethodID(FatherClass, "speak", "()Ljava/lang/String;");

    jmethodID speakInSon = env->GetMethodID(SonClass, "speak", "()Ljava/lang/String;");



    jstring str1 = static_cast(env->CallObjectMethod(tomson, speak));//调用的是子类的方法
    jstring str2 = static_cast(env->CallNonvirtualObjectMethod(tomson, SonClass, speak));//调用的是父类的方法,将FatherClass换为SonClass,依然调用的是父类的方法
    LOGD("str1 = %s,str2 = %s",env->GetStringUTFChars(str1,JNI_FALSE),env->GetStringUTFChars(str2,JNI_FALSE));

    jstring str3 = static_cast(env->CallObjectMethod(tomson, speakInSon));//调用的是子类的方法
    jstring str4 = static_cast(env->CallNonvirtualObjectMethod(tomson, SonClass, speakInSon));//调用的是子类的方法,将SonClass换为FatherClass,报错
    LOGD("str3 = %s,str4 = %s",env->GetStringUTFChars(str3,JNI_FALSE),env->GetStringUTFChars(str4,JNI_FALSE));

}

测试结果如下


测试结果.png
  • Q1:c中拿到java中的子类对象后,获取父类的方法id,用CallObjectMethod反射调用,此时调用的是父类方法还是子类方法?
    A:子类方法。
  • Q2:c中拿到java中的子类对象后,获取子类的方法id,用CallObjectMethod反射调用,此时调用的是父类方法还是子类方法?
    A:子类方法。
  • Q3:c中拿到java中的子类对象后,如何调用其父类方法和子类方法?
    A:env->CallObjectMethod调用子类方法,env->CallNonvirtualObjectMethod调用父类方法。
  • env->CallObjectMethod与env->CallNonvirtualObjectMethod二者有何区别和联系?
    A:如上。

四、cmake配置之如何配置多个本地文件

build.gradle.png

工程目录结构.png

方式一:
CMakeLists.txt

...
file(GLOB native_srcs "*.cpp")//此处配置源文件文件夹的路径

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
#        native-lib.cpp native-lib2.cpp
        ${native_srcs}
        )
...

方式二:
CMakeLists.txt

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp native-lib2.cpp
#        ${native_srcs}
        )

五、在C中如何打印LOG

mylog.h

//
// Created by taoying on 2019/7/17.
//

#ifndef TESTNDKAPP_MYLOG_H
#define TESTNDKAPP_MYLOG_H

#endif //TESTNDKAPP_MYLOG_H
#include 
#define TAG "projectname" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

在需要使用地方将上面的头文件include进来即可

#include 
#include 
#include 
#include "mylog.h"

const char *
        KNOWN_DANGEROUS_APPS_PACKAGES[] = {"com.koushikdutta.rommanager",
                                           "com.dimonvideo.luckypatcher",
                                           "com.chelpus.lackypatch", "com.ramdroid.appquarantine"};
...

你可能感兴趣的:(JNI&NDK开发最佳实践(十):补充要点(持续更新))