一、在C中实现Java回调函数
我们知道在C中通过传递函数指针可以轻易实现函数回调的效果,而在java中则一般是通过构造匿名内部类对象来间接实现函数回调。那么如何在C中构造一个具有回调函数功能的对象呢?
例如在java中给一个Button设置点击事件回调
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
那倘若我们要在C中通过JNI的方式来实现给Button设置点击回调,该如何实现呢?
- 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, "<init>", "(J)V");
jlong lptr = reinterpret_cast<jlong>(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<void (*)(JNIEnv *, jobject)>(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<String>和 List<Integer>在 jvm 中的 Class 都是 List.class
- public class Erasure <T>中的T类型在jvm即为java.lang.Object
- public class Erasure <T extends String>中的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<jstring>(env->CallObjectMethod(tomson, speak));//调用的是子类的方法
jstring str2 = static_cast<jstring>(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<jstring>(env->CallObjectMethod(tomson, speakInSon));//调用的是子类的方法
jstring str4 = static_cast<jstring>(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 <android/log.h>
#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 <jni.h>
#include <string>
#include <regex>
#include "mylog.h"
const char *
KNOWN_DANGEROUS_APPS_PACKAGES[] = {"com.koushikdutta.rommanager",
"com.dimonvideo.luckypatcher",
"com.chelpus.lackypatch", "com.ramdroid.appquarantine"};
...
网友评论