# stephen-shili-android-jni **Repository Path**: stephenzelin/stephen-shili-android-jni ## Basic Information - **Project Name**: stephen-shili-android-jni - **Description**: F:\app\jnitestfromok android jni学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-02-22 - **Last Updated**: 2024-03-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: 示例, Android, jni ## README 文件:F:\app\jnitestfromok # stephen-shili-android-jni #### 介绍 android jni学习 jni tips https://doc.yonyoucloud.com/doc/wiki/project/jni-ndk-developer-guide/string.html === java语言的数据类型分为基本数据类型和引用数据类型, 其中基本数据类型有8种:byte 、char、short、int、long 、float、double、boolean。 对应的Jni数据类型为:    jbyte、jchar、jshort、jint、jlong、jfloat、jdouble、jboolean。 除了基本类型,其他为引用类型:Object、String、数组等。 所有JNI引用类型都是jobject的子类,这些子类和java中常用的引用类型相对应, JNI的引用类型: 字符串:string class字节码对象:jclass 数组: jarray jarray派生8种基本数据类型: jintArray jshortArray jlongArray == char为16bit 引用类型: ==== JNI 基本数据类型定义: typedef unsigned char jboolean; typedef unsigned short jchar; typedef short jshort; typedef float jfloat; typedef double jdouble; typedef int jint; #ifdef _LP64 typedef long jlong; #else typedef long long jlong; #endif typedef signed char jbyte; === 访问字符串: 方式1: JNIEnv* env; const char* textArray = env->GetStringUTFChars(name, NULL);//name:java传递给jni的字符串 方式2: JNIEnv* env; // convert Java string to UTF-8     const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL); === 异常检查 释放字符串 ReleaseStringUTFChars env->ReleaseStringUTFChars(j_str, c_str); env->ReleaseStringUTFChars(data, byteData); env->ReleaseStringUTFChars(jstring data, const char *byteData); 创建字符串NewStringUTF  新创建的字符串会自动转换成JAVA支持的Unicode编码。 JNIEnv *env env->NewStringUTF("at null"); 获取释放unicode(UTF-16)字符串 GetStringChars java使用的UTF-16不是以零为终止,允许使用\u0000, 因此需要保留字符串长度和jchar指针。 c语言的UTF-8编码会将 \u0000编码为0xc0 0x80而不是0x00 jstring text; const jchar* textArray = env->GetStringChars(text, NULL); env->ReleaseStringChars(text, textArray); 释放UTF-16字符串: ReleaseStringChars static jfloat measureText_StringIII(JNIEnv* env, jobject jpaint, jstring text, int start, int end, jint bidiFlags) { const jchar* textArray = env->GetStringChars(text, NULL); env->ReleaseStringChars(text, textArray); } ==== 获取Unicode编码字符串长度: GetStringLength static jfloat measureText_StringIII(JNIEnv* env, jobject jpaint, jstring text, int start, int end, jint bidiFlags) { size_t textLength = env->GetStringLength(text); } === 获取UTF-8编码字符串长度: GetStringUTFLength AutoJavaStringToUTF8(JNIEnv* env, jstring str) : fEnv(env), fJStr(str) { fCStr = env->GetStringUTFChars(str, NULL); fLength = env->GetStringUTFLength(str); } === 获取JVM源字符串指针 GetStringCritical ReleaseStringCritical static int android_media_AudioSystem_setParameters(JNIEnv *env, jobject thiz, jstring keyValuePairs) { const jchar* c_keyValuePairs = env->GetStringCritical(keyValuePairs, 0); env->ReleaseStringCritical(keyValuePairs, c_keyValuePairs); } 获取指定范围的内容 获取Unicode字符串指定范围内的内容 获取Unicode字符串指定内容 GetStringRegion  获取UFT-8编码字符串指定内容 GetStringUTFRegion =================== F:\app\jnitestfromok\app\src\main\java\com\android\test\jnidemo jni访问数组 访问基本数组 本地代码: //直接拷贝方式 通过调用 GetIntArrayRegion 函数,将 int 数组中的所有元素拷贝到 C 临时缓冲区中,然后在本地代码中访问缓冲区中的元素 //1:获取数组长度 arr_len = env->GetArrayLength(j_int_arr); //2:根据数组长度和数组元素的数据类型申请存放java数组元素的缓冲区 c_array = (jint*)malloc(sizeof(jint) * arr_len); //3:初始化缓冲区 memset(c_array, 0, sizeof(jint) * arr_len); LOGD("jni-ndk arr_len = %d", arr_len); //4:拷贝java数组中的所有元素到缓冲区中 env->GetIntArrayRegion(j_int_arr,0,arr_len,c_array); for(k=0; k < arr_len; k++) { LOGD("jni-ndk arr[%d]=%d",k,c_array[k]); } //5:释放缓冲区 free(c_array); const char *c_str = NULL; c_str = env->GetStringUTFChars(j_str, NULL); if(c_str == NULL){ return;//memory out } LOGD("jni-ndk c_str:%s\n",(char *)c_str); env->ReleaseStringUTFChars(j_str, c_str); //获取数组元素指针的方式 //数组中的元素在内存中可能不是连续的,JVM会复制原始数据到缓冲区,然后返回缓冲区的指针 JNI 还提供一系列直接获取数组元素指针的函数 Get/ReleaseArrayElements c_array = env->GetIntArrayElements(j_int_arr, NULL); if(c_array == NULL){ return; } arr_len = env->GetArrayLength(j_int_arr); LOGD("jni-ndk arr_len = %d", arr_len); for(k=0; k < arr_len; k++) { LOGD("jni-ndk GetIntArrayElements arr[%d]=%d",k,c_array[k]); } //释放可能复制的缓冲区 env->ReleaseIntArrayElements(j_int_arr,c_array,0); Get/ReleasePrimitiveArrayCritical 这对函数,本地代码在访问数组对象时会暂停 GC 线程。 c_array = (jint *)(env->GetPrimitiveArrayCritical(j_int_arr, (jboolean *)j_isCopy)); LOGD("jni-ndk j_isCopy=%d",j_isCopy); if(c_array == NULL){ return; } arr_len = env->GetArrayLength(j_int_arr); LOGD("jni-ndk arr_len = %d",arr_len); for(k=0; k < arr_len; k++) { LOGD("jni-ndk GetPrimitiveArrayCritical arr[%d]=%d",k,c_array[k]); } env->ReleasePrimitiveArrayCritical(j_int_arr,c_array,0); 小结 对于小量的、固定大小的数组,应该选择 Get/SetArrayRegion 函数来操作数组元素是效率最高的。 因为这对函数要求提前分配一个 C 临时缓冲区来存储数组元素,你可以直接在 Stack(栈)上或用 malloc 在堆上来动态申请, 当然在栈上申请是最快的。有童鞋可能会认为,访问数组元素还需要将原始数据全部拷贝一份到临时缓冲区才能访问而觉得效率低? 我想告诉你的是,像这种复制少量数组元素的代价是很小的,几乎可以忽略。这对函数的另外一个优点就是, 允许你传入一个开始索引和长度来实现对子数组元素的访问和操作(SetArrayRegion函数可以修改数组), 不过传入的索引和长度不要越界,函数会进行检查,如果越界了会抛出 ArrayIndexOutOfBoundsException 异常。 如果不想预先分配 C 缓冲区,并且原始数组长度也不确定,而本地代码又不想在获取数组元素指针时被阻塞的话, 使用 Get/ReleasePrimitiveArrayCritical 函数对,就像 Get/ReleaseStringCritical 函数对一样,使用这对函数要非常小心,以免死锁。 Get/ReleaseArrayElements 系列函数永远是安全的,JVM 会选择性的返回一个指针,这个指针可能指向原始数据,也可能指向原始数据的复制。 === 访问对象数组: 本地代码 extern "C" JNIEXPORT jobjectArray JNICALL Java_com_android_test_jnidemo_JniText_intInt2DArray (JNIEnv *env, jclass obj, jint size) { //jobjectArray start jint k; jobjectArray result; jclass clsIntArray; jint j; //1:获得一个Int型二维数组类的引用 LOGD("jni-ndk clsIntArray"); clsIntArray = env->FindClass("[I"); if(clsIntArray == NULL){ return NULL; } //2:创建一个数组对象 //length i :引用数组的成员个数 //elementClass :引用数组的的成员的类型 LOGD("jni-ndk NewObjectArray"); result = env->NewObjectArray(size,clsIntArray,NULL); if(result == NULL){ return NULL; } for(k = 0; k < size; k++) { //用来给整型数组填充数据的缓冲区 jint buff[256]; //声明一个整型数组 jintArray intArr = env->NewIntArray(size); if(intArr == NULL){ return NULL; } for(j = 0; j < size; j++) { buff[j] = k + j; } env->SetIntArrayRegion(intArr,0, size, buff); env->SetObjectArrayElement(result,k,intArr); env->DeleteLocalRef(intArr); } LOGD("jni-ndk 2Array ok"); return result; } int [][] arr_2d = JniText.intInt2DArray(3); for(int k = 0; k < 3; k++){ for(int j = 0; j < 3; j++) { //Log.d(TAG,"arr_2d[%d][%d] = %d", k,j,arr_2d[k][j]); Log.d(TAG,"k:" + k + "j:" + j + "arr_2d[k][j]:" + arr_2d[k][j]); } } 本地函数 initInt2DArray 首先调用 JNI 函数 FindClass 获得一个 int 型的二维数组类的引用,传递给FindClass 的参数"[I"是 JNI class descript(JNI 类型描述符,后面为详细介绍),它对应着 JVM 中的int[]类型。如果 int[]类加载失败的话,FindClass 会返回 NULL,然后抛出一个java.lang.NoClassDefFoundError: [I异常。 接下来,NewObjectArray 创建一个新的数组,这个数组里面的元素类型用 intArrCls(int[])类型来表示。函数NewObjectArray 只能分配第一维,JVM 没有与多维数组相对应的数据结构,JNI 也没有提供类似的函数来创建二维数组。由于 JNI 中的二维数组直接操作的是 JVM 中的数据结构,相比 JAVA 和 C/C++创建二维数组要复杂很多。给二维数组设置数据的方式也非常直接,首先用 NewIntArray 创建一个 JNI 的 int 数组,并为每个数组元素分配空间,然后用 SetIntArrayRegion 把 buff[]缓冲中的内容复制到新分配的一维数组中去,最后在外层循环中依次将 int[]数组赋值到 jobjectArray 数组中,一维数组中套一维数组,就形成了一个所谓的二维数组。 另外,为了避免在循环内创建大量的 JNI 局部引用,造成 JNI 引用表溢出,所以在外层循环中每次都要调用DeleteLocalRef 将新创建的 jintArray 引用从引用表中移除。在 JNI 中,只有 jobject 以及子类属于引用变量,会占用引用表的空间,jint,jfloat,jboolean 等都是基本类型变量,不会占用引用表空间,即不需要释放。引用表最大空间为 512 个,如果超出这个范围,JVM 就会挂掉。 来源: https://doc.yonyoucloud.com/doc/wiki/project/jni-ndk-developer-guide/array.html c/c++访问java 参考 F:\app\android-ndk-samples-jnitest\android-ndk\android-ndk\jnitest\app\src\main\cpp 在 ClassMethod 方法中定义要被c 调用的方法 F:\app\jnitestfromok\app\src\main\java\com\android\test\jnidemo\ClassMethod.java public class ClassMethod { static String TAG = "jni-ndk"; private static void callStaticMethod(String str, int i){ Log.d(TAG,"ClassMethod::callStaticMethod called!--> str=" + str + "i=" + i); } private void callInstanceMethod(String str, int i){ Log.d(TAG,"ClassMethod::callInstanceMethod called ---> str=" + str + "i=" + i); } } c++调用 F:\app\jnitestfromok\app\src\main\jni\JNIHello.cpp extern "C" JNIEXPORT void JNICALL Java_com_android_test_jnidemo_JniText_calJavaStaticMethod (JNIEnv *env, jclass cls) { jclass clazz = NULL; jstring str_arg = NULL; jmethodID mid_static_method; //1:从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象 //clazz = (*env).FindClass("com/android/test/jnidemo/ClassMethod"); clazz = env->FindClass("com/android/test/jnidemo/ClassMethod"); if(clazz == NULL){ return; } //2.从clazz类中查找callStaticMethod方法 mid_static_method = env->GetStaticMethodID(clazz,"callStaticMethod", "(Ljava/lang/String;I)V"); if(mid_static_method == NULL){ LOGD("jni-ndk 找不到callStaticMethod这个静态方法"); return; } //3:调用clazz类的callStaticMethod静态方法 str_arg = env->NewStringUTF("我是静态方法"); env->CallStaticVoidMethod(clazz,mid_static_method,str_arg,100); //4:删除局部引用 env->DeleteLocalRef(clazz); env->DeleteLocalRef(str_arg); } extern "C" JNIEXPORT void JNICALL Java_com_android_test_jnidemo_JniText_callJavaInstaceMethod (JNIEnv *env, jclass cls) { jclass clazz = NULL; jobject jobj = NULL; jmethodID mid_construct = NULL; jmethodID mid_instance = NULL; jstring str_arg = NULL; //1:从classpath路径下搜索ClassMethod这个类,并返回该类的class对象 clazz = env->FindClass("com/android/test/jnidemo/ClassMethod"); if(clazz == NULL){ LOGD("jni-ndk 找不到com.android.test.jnidemo.ClassMethod 这个类"); return; } //2.获取类的默认构造方法ID mid_construct = env->GetMethodID(clazz,"", "()V"); if(mid_construct == NULL){ LOGD("jni-ndk 找不到默认的构造方法"); return; } //3:查找实例方法的id mid_instance = env->GetMethodID(clazz,"callInstanceMethod","(Ljava/lang/String;I)V"); if(mid_instance == NULL){ LOGD("jni-ndk mid_instance id error"); return; } //4:创建该类的实例 jobj = env->NewObject(clazz,mid_construct); if(jobj == NULL){ LOGD("jni-ndk 在com.android.test.jnidemo.ClassMethod类中找不到callInstanceMethod方法"); return; } //5:调用对象的实例方法 str_arg = env->NewStringUTF("我是实例方法"); env->CallVoidMethod(jobj,mid_instance,str_arg,200); //删除局部引用 env->DeleteLocalRef(clazz); env->DeleteLocalRef(jobj); env->DeleteLocalRef(str_arg); } ==== 方法签名 (参数)返回值 在上面的的例子中,无论是调用静态方法还是实例方法,都必须传入一个 jmethodID 的参数。因为在 Java 中存在方法重载(方法名相同,参数列表不同),所以要明确告诉 JVM 调用的是类或实例中的哪一个方法。调用 JNI 的 GetMethodID 函数获取一个 jmethodID 时,需要传入一个方法名称和方法签名,方法名称就是在 Java 中定义的方法名,方法签名的格式为:(形参参数类型列表)返回值。形参参数列表中,引用类型以 L 开头,后面紧跟类的全路径名(需将.全部替换成/),以分号结尾。下面是一些示例: Java 基本类型与方法签名中参数类型和返回值类型的映射关系如下: 比如,String fun(int a, float b, boolean c, String d)对应的 JNI 方法签名为:"(IFZLjava/lang/String;)Ljava/lang/String;"。 总结: 调用静态方法使用 CallStaticXXXMethod/V/A 函数,XXX 代表返回值的数据类型。如:CallStaticIntMethod 调用实例方法使用 CallXXXMethod/V/A 函数,XXX 代表返回的数据类型,如:CallIntMethod 获取一个实例方法的 ID,使用 GetMethodID 函数,传入方法名称和方法签名 获以一个静态方法的 ID,使用 GetStaticMethodID 函数,传入方法名称和方法签名 获取构造方法 ID,方法名称使用"" 获取一个类的 Class 实例,使用 FindClass 函数,传入类描述符。JVM 会从 classpath 目录下开始搜索。 创建一个类的实例,使用 NewObject 函数,传入 Class 引用和构造方法 ID 删除局部变量引用,使用 DeleteLocalRef,传入引用变量 方法签名格式:(形参参数列表)返回值类型。注意:形参参数列表之间不需要用空格或其它字符分隔 类描述符格式:L 包名路径/类名;,包名之间用/分隔。如:Ljava/lang/String; 调用 GetMethodID 获取方法 ID 和调用 FindClass 获取 Class 实例后,要做异常判断 来源: https://doc.yonyoucloud.com/doc/wiki/project/jni-ndk-developer-guide/function.html ==== C/C++ 访问 Java 实例变量和静态变量 1:java中的变量的类 F:\app\jnitestfromok\app\src\main\java\com\android\test\jnidemo\ClassField.java public class ClassField { private static int num; private String str; public int getNUm() { return num; } public void setNum(int num) { ClassField.num = num; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } } F:\app\jnitestfromok\app\src\main\java\com\android\test\jnidemo\JniText.java public native static void accessInstanceField(ClassField obj); public native static void accessStaticField(); jni实现 F:\app\jnitestfromok\app\src\main\jni\JNIHello.cpp //c/c++ 修改java实例变量 extern "C" JNIEXPORT void JNICALL Java_com_android_test_jnidemo_JniText_accessInstanceField (JNIEnv *env, jclass cls, jobject obj){ jclass clazz; jfieldID fid; jstring j_str; jstring j_newStr; const char *c_str = NULL; //1:获取AccessField类的Class引用 clazz = env->GetObjectClass(obj); if(clazz == NULL){ return; } //2.获取AcessField类实例变量str的属性ID fid = env->GetFieldID(clazz,"str","Ljava/lang/String;"); if(fid == NULL){ return; } //3.获取实例变量str的值 j_str = (jstring)env->GetObjectField(obj,fid); //4. 将unicode编码的java字符串转换为c风格字符串 c_str = env->GetStringUTFChars(j_str, NULL); if(c_str == NULL){ return; } LOGD("jni-ndk In c ---> ClassField.str = %s\n", c_str); env->ReleaseStringUTFChars(j_str,c_str); //5:修改实例变量str的值 j_newStr = env->NewStringUTF("This is C String"); if(j_newStr == NULL){ return; } env->SetObjectField(obj, fid, j_newStr); //6:删除局部引用 env->DeleteLocalRef(clazz); env->DeleteLocalRef(j_str); env->DeleteLocalRef(j_newStr); } extern "C" JNIEXPORT void JNICALL Java_com_android_test_jnidemo_JniText_accessStaticField (JNIEnv *env, jclass cls){ jclass clazz; jfieldID fid; jint num; //1:获取ClassFied类的Class引用 clazz = env->FindClass("com/android/test/jnidemo/ClassField"); if(clazz == NULL){ return; } //2:获取ClassField类静态变量num的属性ID fid = env->GetStaticFieldID(clazz, "num","I"); if(fid == NULL){ return; } //3:获取静态变量num的值 num = env->GetStaticIntField(clazz,fid); LOGD("jni-ndk In C ---> ClassField.num = %d\n", num); //4:修改静态变量num的值 env->SetStaticIntField(clazz,fid,80); //删除局部引用 env->DeleteLocalRef(clazz); } === 由于 JNI 函数是直接操作J VM 中的数据结构,不受 Java 访问修饰符的限制。即,在本地代码中可以调用 JNI 函数可以访问 Java 对象中的非 public 属性和方法 访问和修改实例变量操作步聚: 调用 GetObjectClass 函数获取实例对象的 Class 引用 调用 GetFieldID 函数获取 Class 引用中某个实例变量的 ID 调用 GetXXXField 函数获取变量的值,需要传入实例变量所属对象和变量 ID 调用 SetXXXField 函数修改变量的值,需要传入实例变量所属对象、变量 ID 和变量的值 访问和修改静态变量操作步聚: 调用 FindClass 函数获取类的 Class 引用 调用 GetStaticFieldID 函数获取 Class 引用中某个静态变量 ID 调用 GetStaticXXXField 函数获取静态变量的值,需要传入变量所属 Class 的引用和变量 ID 调用 SetStaticXXXField 函数设置静态变量的值,需要传入变量所属 Class 的引用、变量 ID和变量的值 来源: https://doc.yonyoucloud.com/doc/wiki/project/jni-ndk-developer-guide/variable.html == ndk-bundle jni 开发tips F:\app\jnitestfromok 1:生成.h头文件报错 javah -classpath . -jni com.android.test.jnidemo.JniText F:\app\jnitestfromok\app\src\main\java>javah -classpath . -jni com.android.test.jnidemo.JniText 'javah' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 解决方案: 1:setx PATH "%PATH%;D:\soft\codeSoft\androidstudio_ide_2021\jre\bin 2: JDK在JDK10之后就取消了javah命令,如果想要生成头文件需要改成javac -h命令生成。 3: F:\app\jnitestfromok\app\src\main\java>javac -encoding utf8 -h . F:\app\jnitestfromok\app\src\main\java\com\android\test\jnidemo\JniText.java ----》在与包com同目录执行javac 参考:https://www.cnblogs.com/virgosnail/p/10711165.html 2:c文件的实现: .h头文件 JNIEXPORT jstring JNICALL Java_com_android_test_jnidemo_JniText_sayHello (JNIEnv *, jclass); 对应.c的实现 extern "C" JNIEXPORT jstring JNICALL Java_com_android_test_jnidemo_JniText_sayHello (JNIEnv *env, jclass type ){ } 3:编写Android.mk Application.mk 4:添加对Jni的编译 在app的build.gradle中添加 android { defaultConfig { ndk { //ldLibs "log" moduleName "Hello" abiFilters 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a' } } externalNativeBuild { ndkBuild{ path "src/main/jni/Android.mk" } } //指定ndk版本 ndkVersion '25.1.8937393' } android studio相关错误解决 1:高版本的android studio 导入低版本工程报错: https://zhuanlan.zhihu.com/p/646062116 报错: cvc-complex-type.2.4.a: 发现了以元素 'base-extension' 开头的无效内容。应以 '{layoutlib}' 之一开头。 解决方案 a:在build.gradle(:app) 中添加: dependencies { compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7' } b:在gradle-wrappper.properties总添加 distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip c:在项目的build.gradle中添加 repositories { google() // 这里把google补上 jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } allprojects { repositories { jcenter() google() } } d:出现The specified Gradle installation directory 'D:\Program Files\Android\Android Studio\gradle\gradle-2.10' does not exist. 、打开顶部菜单:File -> Setting -> Build,Execution,Deployment -> BuildTools -> Gradle 找到Gradle projects 下面的Gradle -> Use Gradle from -> 下来选择修改为:'gradle-wrapper.properties' file 原文链接:https://blog.csdn.net/qq_33721320/article/details/124927895 === f: Could not install Gradle distribution from 'https://services.gradle.org/distributions/gradle-2.10-all.zip'. 解决方案:手动下载放到 C:\Users\stephen\.gradle\wrapper\dists\gradle-2.10-all\a4w5fzrkeut1ox71xslb49gst ====》 #Sat Dec 10 16:31:06 CST 2022 This version of the Android Support plugin for IntelliJ IDEA (or Android Studio) cannot open this project, please retry with version 2021.2.1 or newer. 解决方案: 修改gradle-wrapper.properties 原本是7.1.1,这里已经改为支持的6.6。修改方式如图在主页面菜单栏的Project Structure 修改点二: 降低build.gradle中的build tools版本 修改项目的build.gradle,如下: plugins { id 'com.android.application' version '7.1.0' apply false id 'com.android.library' version '7.1.0' apply false } ===》 修改 defaultConfig { applicationId "com.android.test.jnidemo" minSdk 21 targetSdk 32 versionCode 1 versionName "1.0" 为: defaultConfig { applicationId "com.android.test.jnidemo" minSdk 19 targetSdk 32 versionCode 1 versionName "1.0" ==== 项目报错-failed to find build tools revision 26.0.1 解决方法: 第一步: tools ->android ->sdk manager->sdk tools查看下载的版本有哪些. 第二步: File->project structure ->app ->property 选择compile SDK version,build tools version的版本为刚才查询的已下载的版本即可。 第三步:appcompat-v7要与sdk tools大版本一致。 compile 'com.android.support:appcompat-v7:25.3.1' === The option 'android.useDeprecatedNdk' is deprecated. The current default is 'false'. It has been removed from the current version of the Android Gradle plugin. NdkCompile is no longer supported === Configuration 'compile' is obsolete and has been replaced with 'implementation' and 'api'. ====》 F:\app\jnitestfromok\app\src\main\java>javah -classpath . -jni com.android.test.jnidemo.JniText 'javah' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 解决方案 setx PATH "%PATH%;D:\soft\codeSoft\androidstudio_ide_2021\jre\bin JDK在JDK10之后就取消了javah命令,如果想要生成头文件需要改成javac -h命令生成。 javah -classpath . -jni com.android.test.jnidemo.JniText 2:导入工程出现: Exception java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema [in thread "Daemon worker"] 参考: https://blog.csdn.net/quantum7/article/details/133775241 然后出现: cvc-complex-type.2.4.a: 发现了以元素 'base-extension' 开头的无效内容。应以 '{layoutlib}' 之一开头。 === 出现: No valid Native abi found to request! at: No valid Native abi found to request! 解决方案:使用ndk21不支持armeabi,改为armeabi-v7a 修改为 arm { ndk { abiFilter 'armeabi-v7a' } } === 报错: All flavors must now belong to a named flavor dimension. Learn more at 添加 defaultConfig { applicationId 'com.example.hellojni' minSdkVersion 19 targetSdkVersion 23 versionCode 1 versionName "1.0" flavorDimensions "versionCode" externalNativeBuild { cmake { arguments '-DANDROID_TOOLCHAIN=clang' } } } 报错: ABIs [mips, mips64] are not supported for platform. Supported ABIs are [arm64-v8a, armeabi-v7a, x86, x86_64].