最近一个项目要编译深度学习的库,需要用到 opencv 和 JNI,本文档用于记录环境配置中遇到的常见错误以及解决方案
failed
Invalid Gradle JDK configuration found
Invalid Gradle JDK configuration found. Open Gradle Settings
Change JDK location
解决办法: 删除文件
.idea/gradle.xml
和 .idea/workspace.xml
, 重新编译;
解决办法:Invalid Gradle JDK configuration found
原因是NDK版本过高,跟当前的AndroidStudio版本不匹配。选择升级AndroidStudio或者降低NDK版本即可。
重新下载21.3.6528147版本,配置NDK通过,rebuild项目通过,问题解决, 最新版的 Android Studio 降级到 Android Studio 4.2.1 版本,NDK 降级到 21.3.6528147;
问题:clang++.exe: error: unknown argument: ‘-static-openmp‘
NDK版本!clang++: error: unknown argument: ‘-static-openmp‘
解决方法:
compileSdkVersion 设置为28
Android res\values-v26\values-v26.xml:9:5-12:13: AAPT: error: resource android:attr/colorError not f
最后在自己的 nativelib module
的 build.gradle
的 android{}
加上
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
2 files found with path ‘lib/arm64-v8a/xxx.so‘ 问题
解决方案:替换库文件时候,同步替换头文件
undefined reference to `cv::String::deallocate()一种可能解决方案
关于解决gradle版本与gradle插件版本不一致问题的方法之一
在 D:\Android\sdk\.android
的目录下找到文件 debug.keystore
, 其实不缺少签名文件,这个问题,应该是之前安装了多个版本的 Android Studio,导致签名文件被覆盖了, 删除 debug.keystore
文件,重新签名即可。
GetFieldID
是得到 java
类中的参数 ID
,GetMethodID
得到 java
类中方法的 ID
,它们只能调用类中声明为 public
的参数或方法。举例说明:
jclass c = (*env)->FindClass(env,"com/camera/webcam/Test");
jfieldID width_id = (*env)->GetFieldID(env, c, "width", "I");
第一个参数:JNI接口对象;第二个参数:Java类对象;第三个参数:参数名(或方法名);第四个参数:该参数(或方法)的签名。
调用 JNI
的 GetMethodID
函数获取一个 jmethodID
时,需要传入一个方法名称和方法签名,方法名称就是在 Java
中定义的方法名,方法签名的格式为:(形参参数类型列表)返回值。
JNI GetFieldID和GetMethodID函数解释及方法签名
当您希望从一个函数返回多个“东西”时,您有两个选项(这并不是 JNI
特有的):要么创建一个包含所有结果的包装器对象(在您的例子中,是一个包含3个数组字段的 Java
类),要么使用 out
参数。也许,在您的情况下,如果您知道调用前的长度,后者可能会更容易一些。
所以,在 Java
中,您可以编写如下内容
package p;
public class C {
public void f() {
byte[] array1 = new byte[10];
int[] array2 = new int[20];
String[] array3 = new String[5];
fillArrays(array1, array2, array3);
}
native void fillArrays(byte[] byteArray, int[] intArray, String[] stringArray);
}
现在,在 C
中,这看起来是这样的:
JNIEXPORT void JNICALL
Java_p_C_fillArrays(JNIEnv *env, jobject thisC, jbyteArray byteArray, jintArray intArray, jobjectArray stringArray)
{
jboolean isCopy;
jint i = 0;
char* names[] = {"one", "two", "three"};
jbyte *c_byteArray = (*env)->GetByteArrayElements(env, byteArray, &isCopy);
for (i=0; i<(*env)->GetArrayLength(env, byteArray); i++) {
c_byteArray[i] = (jbyte)i;
}
(*env)->ReleaseByteArrayElements(env, byteArray, c_byteArray, 0);
jint *c_intArray = (*env)->GetIntArrayElements(env, intArray, &isCopy);
for (i=0; i<(*env)->GetArrayLength(env, intArray); i++) {
c_intArray[i] = i;
}
(*env)->ReleaseIntArrayElements(env, intArray, c_intArray, 0);
for (i=0; i<(*env)->GetArrayLength(env, stringArray) && i<sizeof(names)/sizeof(names[0]); i++) {
(*env)->SetObjectArrayElement(env, stringArray, i, (*env)->NewStringUTF(env, names[i]));
}
}
如何从JNI返回多个数组到Java?
在 JNI
中对 Java
层的数组赋值有两种方式:一是在 Java
层创建好数组,然后传递到 JNI
层,由 JNI
层进行赋值;二是直接在 JNI
层创建好数组并赋值,然后返回数组到 Java
层。下面是两种方式的对比实现:
创建两个 native
方法
//传递数组,操作后,返回
public native void passArrayMethod(int[] arr);
//创建指定长度数组
public native int[] createArrayMethod(int len);
生成对应的 C
函数
JNIEXPORT void JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_passArrayMethod
(JNIEnv *, jobject, jintArray);
JNIEXPORT jintArray JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_createArrayMethod
(JNIEnv *, jobject, jint);
传递数组给 JNI
,修改第一个元素值,然后排序
int com(const void *a, const void *b){
return *(int *)a - *(int *)b;//升序
}
JNIEXPORT void JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_passArrayMethod
(JNIEnv *env, jobject jobj, jintArray jarr){
//1.获取数组指针
jint *arr = env->GetIntArrayElements(jarr, NULL);
*arr = 100;
//2.获取数组长度
int len = env->GetArrayLength(jarr);
//3.排序
qsort(arr, len, sizeof(jint), com);
//4.释放资源
env->ReleaseIntArrayElements(jarr, arr, JNI_COMMIT);
// env->ReleaseIntArrayElements(jarr, arr, JNI_ABORT);
// 对于最后一个参数(如果指针指向的数组为副本时,否则该参数不起作用)
// 0 copy back the content and free the elems buffer
// JNI_COMMIT copy back the content but do not free the elems buffer
// JNI_ABORT free the buffer without copying back the possible changes
};
JNI
生成数组,并返回
JNIEXPORT jintArray JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_createArrayMethod
(JNIEnv *env, jobject jobj, jint len){
//1.新建长度len数组
jintArray jarr = env->NewIntArray(len);
//2.获取数组指针
jint *arr = env->GetIntArrayElements(jarr, NULL);
//3.赋值
int i = 0;
for(; i < len; i++){
arr[i] = i;
}
//4.释放资源
env->ReleaseIntArrayElements(jarr, arr, 0);
//5.返回数组
return jarr;
};
MainActivity
中调用
int[] arr = {1, 3, 2, 6, 8, 0};
Log.i(TAG, "arr修改前: " + getArrayString(arr));
jd.passArrayMethod(arr);
Log.i(TAG, "arr修改后: " + getArrayString(arr));
Log.i(TAG, "------------------------------------------");
int[] arr_new = jd.createArrayMethod(10);
Log.i(TAG, "arr_new: "+ getArrayString(arr_new) );
输出结果:
09-26 17:02:29.454 994-994/com.test.git.jnidemo I/MainActivity-: arr修改前: ,1,3,2,6,8,0
09-26 17:02:29.454 994-994/com.test.git.jnidemo I/MainActivity-: arr修改后: ,0,2,3,6,8,100
09-26 17:02:29.454 994-994/com.test.git.jnidemo I/MainActivity-: ------------------------------------------
09-26 17:02:29.454 994-994/com.test.git.jnidemo I/MainActivity-: arr_new: ,0,1,2,3,4,5,6,7,8,9
Java: JNI对数组赋值并返回给Java
JNIEnv
即 Java Native Interface Environment
,Java
本地编程接口环境。JNIEnv
内部定义了很多函数用于简化我们的 JNI
编程。
JNI
把 Java
中的所有对象或者对象数组当作一个 C
指针传递到本地方法中,这个指针指向 JVM
中的内部数据结构(对象用 jobject
来表示,而对象数组用 jobjectArray
或者具体是基本类型数组),而内部的数据结构在内存中的存储方式是不可见的。只能从 JNIEnv
指针指向的函数表中选择合适的 JNI
函数来操作 JVM
中的数据结构。
在 C
语言中,JNIEnv
是一个指向 JNINativeInterface_
结构体的指针:
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv; // C 语言
#endif
struct JNINativeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2;
void *reserved3;
jint (JNICALL *GetVersion)(JNIEnv *env);
jclass (JNICALL *DefineClass)
(JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
jsize len);
jstring (JNICALL *NewStringUTF)
(JNIEnv *env, const char *utf);
//省略其他函数指针
//......
}
JNINativeInterface_
结构体中定义了非常多的函数指针,这些函数用于简化我们的 JNI
编程。C
语言中,JNIEnv
中函数的使用方式如下:
JNIEnv * env
// env 的实际类型是 JNINativeInterface_**
(*env)->NewStringUTF(env,"Hello from JNI !");
在 C++
代码中,JNIEnv
是一个 JNIEnv_
结构体:
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
#ifdef __cplusplus
jint GetVersion() {
return functions->GetVersion(this);
}
jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
jsize len) {
return functions->DefineClass(this, name, loader, buf, len);
}
jclass FindClass(const char *name) {
return functions->FindClass(this, name);
}
jmethodID FromReflectedMethod(jobject method) {
return functions->FromReflectedMethod(this,method);
}
jfieldID FromReflectedField(jobject field) {
return functions->FromReflectedField(this,field);
}
jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) {
return functions->ToReflectedMethod(this, cls, methodID, isStatic);
}
jclass GetSuperclass(jclass sub) {
return functions->GetSuperclass(this, sub);
}
//省略其他函数
//......
}
JNIEnv_
结构体中同样定义了非常多的成员函数,这些函数用于简化我们的 JNI
编程。C++
语言中,JNIEnv
中函数的使用方式如下:
//JNIEnv * env
// env 的实际类型是 JNIEnv_*
env->NewstringUTF ( "Hello from JNI ! ");
更多详情见: JNI 编程上手指南之 JNIEnv 详解
Android Studio配置OpenCV的JNI接口
【Android+OpenCV】Android Studio的安装全过程+在Android Studio中配置OpenCV-重点关注
NDK开发遇到的三个错误:‘javah’ 不是内部或外部命令,编码GBK的不可映射字符, 程序包XX.XX不存在
OpenCV 在 Android Studio 的使用教程
Android Studio使用OpenCV进行图像基本处理
Android学习笔记之——基于Android的opencv开发(Android studio3.6+opencv4.3.0开发环境搭建)
解决办法:Invalid Gradle JDK configuration found
NDK版本!clang++: error: unknown argument: ‘-static-openmp‘
问题:clang++.exe: error: unknown argument: ‘-static-openmp‘
Android res\values-v26\values-v26.xml:9:5-12:13: AAPT: error: resource android:attr/colorError not f
JNI GetFieldID和GetMethodID函数解释及方法签名
如何从JNI返回多个数组到Java?
Java: JNI对数组赋值并返回给Java
JNI 编程上手指南之 JNIEnv 详解
Android NDK开发:JNI实战篇
java调用本地方法–JNI访问List集合
JNI系列(四)JAVA数据类型和JNI类型对照表