一、简述
JNI(Java Native Interface)Java本地接口,是为方便java调用C或者C++等本地代码所封装的一层接口。由于Java的跨平台性导致本地交互能力不好,一些和操作系统相关的特性Java无法完成,于是Java提供了JNI专门用于和本地代码交互。

NDK(Native Development Kit)本地开发工具链,是android提供的一个工具合集,帮助开发者快速开发C(或C++)的动态库,并能自动将.so和java应用一起打包成apk。NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出.so。

ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。

armeabi设备只兼容armeabi;armeabi-v7a设备兼容armeabi-v7a、armeabi;arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;X86设备兼容X86、armeabi;X86_64设备兼容X86_64、X86、armeabi;mips64设备兼容mips64、mips;mips只兼容mips;

Android的应用层的类都是以Java写的,这些Java类编译为Dex型式的字节码之后,必须依靠Dalvik虚拟机来运行,在Android中Dalvik虚拟机扮演很重要的角色.而Android中间件是由C/C++写的,这些C/C++写的组件并不是在Dalvik虚拟机上运行的。
一旦使用 JNI,JAVA 程序就丧失了 JAVA 平台的两个优点:
1、 程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。
2、 程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。 一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就降低了 JAVA 和 C 之间的耦合性。

二、java 与c/c++的交互
在java代码中,可以通过loadLibrary要求VM装载so文件,java代码一般如下形式:

public class jnitest {         static {             System.loadLibrary("jnitest");    }}

注:在代码运行时将会在/system/lib/目录下查找libjnitest.so文件,将载入VM中进行调用c库代码。

实现本地函数注册方法有两种:
1、静态方法
通过javah 生成.h 文件,接着实现.cpp文件的方式,再通过ndk-build方式生成so库,最后在java代码上声明对象进行调用类中的方法。通过javah 生成的方法名称 格式为

JNIEXPORT <返回类型> JNICALL Java_<包名>_<类名>_<方法名>(JNIEnv *, jobject,<参数>); 包名中的(.)用下划线(_)代替

2、动态方法
JNI_OnLoad方式 当Android的VM执行到C组件(*so)里的System.loadLibrary()函数时,会产生一个Load事件,接着会去执行C组件里的JNI_OnLoad()函数。
JNI_OnLoad 函数中会执行两个操作:
1)告诉java VM此C组件使用哪一个JNI版本。
2)JNI_OnLoad()来获取JNIEnv。JNIEnv代表java环境,通过JNIEnv指针就可以对java端的代码进行操作。

整个的流程:

1、首先进行重新 JNI_OnLoad函数,在函数中进行获取vm的JNIEnv属性信息if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {     return -1;}2、其次 通过FindClas 找到对应的本地类clazz = env->FindClass(className);3、再接下来通过RegisterNatives 来注册类中的方法if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {     return JNI_FALSE;}

三、常用的jni开发知识
1、数据类型转换
1.1、UTF-8 strings的转换方法

// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)// Can be mapped to null-terminated char-array C-stringconst char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy); // Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);   // Informs the VM that the native code no longer needs access to utf.   jstring NewStringUTF(JNIEnv *env, const char *bytes);   // Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.   jsize GetStringUTFLength(JNIEnv *env, jstring string);   // Returns the length in bytes of the modified UTF-8 representation of a string.   void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);   // Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding    // and place the result in the given buffer buf.

注:
1)JNI 中定义了 jstring 类型代替Java中的String 类型,String 类型的传递相比基本类型要复杂, 因为在Java中String 是个对象, 而在c中, string 是个 char类型数组。所以在传递String类型的时候, 在String(被jstring替换) 类型和 (char*)类型之间做转化。
2)第三个参数isCopy 如果设为JNI_TRUE , 则返回结果是原始Java String 的拷贝, 如果设为JNI_FALSE, 则直接返回 Java String 的地址, 然而在这种情况下, 无法对string内容进行修改。JNI在运行时会试图返回指针, 如果可以的话,否则会返回一个拷贝。 通常情况下, 我们并不关心底层string 的内容呢, 所以通常都设为 NULL。
1.2、unicode String类型

 // Unicode Strings (16-bit character)const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);   // Returns a pointer to the array of Unicode characters   void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);   // Informs the VM that the native code no longer needs access to chars.   jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);   // Constructs a new java.lang.String object from an array of Unicode characters.   jsize GetStringLength(JNIEnv *env, jstring string);   // Returns the length (the count of Unicode characters) of a Java string.   void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);   // Copies len number of Unicode characters beginning at offset start to the given buffer buf

注:NewStringUTF 为根据传入的UTF-8字符串创建一个Java String对象(即是要返回java层的),而GetStringUTFChars 是根据java层的数据来获得jni层(c底层)的数据。

jstring  NewString (JNIEnv *env, const jchar *unicodeChars,   jsize len);             功能:利用 Unicode 字符数组构造新的 java.lang.String 对象。                   参数:   env:JNI 接口指针。                        unicodeChars:指向 Unicode 字符串的指针。                     len:Unicode 字符串的长度。                 返回值: Java 字符串对象。如果无法构造该字符串,则为NULL。                 抛出: OutOfMemoryError:如果系统内存不足。                   jsize  GetStringLength (JNIEnv *env, jstring string);                功能:返回 Java 字符串的长度(Unicode 字符数)。                     参数:  env:JNI 接口指针。                       string:Java 字符串对象。              返回值: Java 字符串的长度。              const  jchar *  GetStringChars (JNIEnv*env, jstring string,  jboolean *isCopy);              功能:返回指向字符串的 Unicode 字符数组的指针。该指针在调用 ReleaseStringchars() 前一直有效。如果 isCopy 非空,则在复制完成后将 *isCopy 设为 JNI_TRUE。如果没有复制,则设为JNI_FALSE。     参数:   env:JNI 接口指针。                       string:Java 字符串对象。                     isCopy:指向布尔值的指针。                  返回值:   指向 Unicode 字符串的指针,如果操作失败,则返回NULL。                   void  ReleaseStringChars (JNIEnv *env, jstring string,  const jchar *chars);                     功能:通知虚拟机平台相关代码无需再访问 chars。参数chars 是一个指针,可通过 GetStringChars() 从 string 获得。       参数: env:JNI 接口指针。                     string:Java 字符串对象。                   chars:指向 Unicode 字符串的指针。                              jstring  NewStringUTF (JNIEnv *env, const char *bytes);               功能:利用 UTF-8 字符数组构造新 java.lang.String 对象。                  参数: env:JNI 接口指针。如果无法构造该字符串,则为 NULL。                       bytes:指向 UTF-8 字符串的指针。             返回值:Java 字符串对象。如果无法构造该字符串,则为NULL。                抛出:  OutOfMemoryError:如果系统内存不足。                   jsize  GetStringUTFLength (JNIEnv *env, jstring string);                 功能:以字节为单位返回字符串的 UTF-8 长度。                   参数:   env:JNI 接口指针。                       string:Java 字符串对象。             返回值:  返回字符串的 UTF-8    const char* GetStringUTFChars (JNIEnv*env, jstring string, jboolean *isCopy);              功能:返回指向字符串的 UTF-8 字符数组的指针。该数组在被ReleaseStringUTFChars() 释放前将一直有效.如果 isCopy 不是 NULL,*isCopy 在复制完成后即被设为 JNI_TRUE。如果未复制,则设为 JNI_FALSE。                参数:  env:JNI 接口指针。                       string:Java 字符串对象。                     isCopy:指向布尔值的指针。                  返回值:  指向 UTF-8 字符串的指针。如果操作失败,则为 NULL。               void  ReleaseStringUTFChars (JNIEnv *env, jstring string,  const char *utf);                  功能:通知虚拟机平台相关代码无需再访问 utf。utf 参数是一个指针,可利用 GetStringUTFChars() 获得。                     参数:   env:JNI 接口指针。                       string:Java 字符串对象。                     utf:指向 UTF-8 字符串的指针。   

1.3、数组类型
在JNI 中定义了 8种基本类型的数组对应Java 的8种基本类型数组,jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray , 和一种对象数组jobjectArray对应Java中的对象数组。
数组传递是处理JNI 数组和Native数组之间的转换。例如:

jintArray <-> jint[], jdoubleArray <-> jdouble[]
jintArray(JNI) --> (Native)jint[] : jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)jint[] --> jintArray : 调用 jintArray NewIntArray(JNIEnv *env, jsize len)分配内存,然后调用 SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf) 将jin[] 拷贝到 jintArray
// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray// PrimitiveType: int, byte, short, long, float, double, char, boolean// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jbooleanNativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

GET|ReleaseArrayElements() 用于根据jxxxArray创建 jxxx[]
GET|SetArrayRegion() 可以用于拷贝一个jxxxArray(或者其中一部分)到一个 预分配(pre-allocated)存储的 jxxx[]
NewArray() 用于为jxxxArray分配内存, 然后调用 SetArrayRegion() 方法 将jxxx[] 设值。
Get|ReleasePrimitiveArrayCritical() 则是在get 和 release周期之间, 不允许阻塞调用(blocking calls)。
1.4、访问对象的属性和方法

jclass GetObjectClass(JNIEnv *env, jobject obj);// Returns the class of an object.   jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);// Returns the field ID for an instance variable of a class. NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);// Get/Set the value of an instance variable of an object//  includes each of the eight primitive types plus Object.

通过以上方法,我们就可以实现在Native代码中访问Java对象的成员变量了, 具体实现为:
首先 通过GetObjectClass() 方法获取该对象的类的引用;
其次 通过GetFieldID() 方法从类引用中(Step1得到该引用)获取FieldID; 调用该方法需要传入成员变量的名称和对应field的描述(descriptor)(或者签名(signature))。

   jmethodID GetMethodID(JNIEnv *env, jclass clazz,    const char *name, const char *sig);                    功能:返回类或接口实例(非静态)方法的方法 ID。方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。 GetMethodID() 可使未初始化的类初始化。要获得构造函数的方法 ID,应将 <init> 作为方法名,同时将 void (V) 作为返回类型。              参数:  env:JNI 接口指针。                        clazz:Java 类对象。                        name:方法名。                      sig:方法的签名。               返回值: 方法 ID,如果找不到指定的方法,则为 NULL。                 抛出:   NoSuchMethodError:如果找不到指定方法。                        ExceptionInInitializerError:如果由于异常而导致类初始化程序失败。                      OutOfMemoryError:如果系统内存不足。                    Call<type>Method 例程  、Call<type>MethodA 例程  、Call<type>MethodV 例程   NativeType Call<type>Method (JNIEnv*en v,  jobject obj , jmethodID methodID, ...);     //参数附加在函数后面,                NativeType Call<type>MethodA (JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args);  //参数以指针形式附加    NativeType Call<type>MethodV (JNIEnv *env, jobject obj,jmethodID methodID, va_list args); //参数以"链表"形式附加

注:
说明:这三个操作的方法用于从本地方法调用Java 实例方法。它们的差别仅在于向其所调用的方法传递参数时所用的机制。
这三个操作将根据所指定的方法 ID 调用 Java 对象的实例(非静态)方法。参数 methodID 必须通过调用 GetMethodID() 来获得。当这些函数用于调用私有方法和构造函数时,方法 ID 必须从obj 的真实类派生而来,而不应从其某个超类派生。当然,附加参数可以为空 。
参数: env:JNI 接口指针。
obj:Java 对象。
methodID:方法 ID。
返回值: 返回调用 Java 方法的结果。
抛出: 执行 Java 方法时抛出的异常。

用户应将CallMethod 中的 type 替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型。

Java层返回值方法族本地返回类型NativeType返回值为voidCallVoidMethod( )A / V()返回值为引用类型:CallObjectMethod( )jobect返回值为boolean :  CallBooleanMethod ( )jboolean返回值为byteCallByteMethod( )jbyte返回值为charCallCharMethod( )       jchar返回值为short       CallShortMethod( )       jshort       返回值为intCallIntMethod( )         jint  返回值为longCallLongMethod()         jlong         返回值为floatCallFloatMethod()        jfloat        返回值为doubleCallDoubleMethod()       jdouble    调用静态方法:也存在如下方法:      jfieldID   GetStaticMethodID (JNIEnv *env,jclass clazz, const char *name, const char *sig);            NativeType  Call<type>Method (JNIEnv*env,jclass classzz , jfieldID fieldID);              它们与于实例方法的唯一区别在于第二个参数jclass classzz代表的是类引用,而不是类实例。

1.5、注册本地方法

 jint  RegisterNatives (JNIEnv *env, jclass clazz, const JNINativeMethod *methods,    jint  nMethods);                  功能:向 clazz 参数指定的类注册本地方法。methods 参数将指定 JNINativeMethod 结构的数组,其中包含本地方法的名称、    签名和函数指针。nMethods 参数将指定数组中的本地方法数。JNINativeMethod 结构定义如下所示:                     typedef struct {                                           char *name;                         char *signature;                             void *fnPtr;                        } JNINativeMethod;                       函数指针通常必须有下列签名:             ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);                                 参数: env:JNI 接口指针。                         clazz:Java 类对象。                         methods:类中本地方法和具体实现方法的映射指针。                            nMethods:类中的本地方法数。                    返回值:  成功时返回 "0";失败时返回负数。            抛出:  NoSuchMethodError:如果找不到指定的方法或方法不是本地方法。             jint  UnregisterNatives (JNIEnv *env, jclass clazz);                功能: 取消注册类的本地方法。类将返回到链接或注册了本地方法函数前的状态。      该函数不应在常规平台相关代码中使用。相反,它可以为某些程序提供一种重新加载和重新链接本地库的途径。               参数:  env:JNI 接口指针。                      clazz:Java 类对象。              返回值: 成功时返回“0”;失败时返回负数。    

1.6、类操作

  jclass DefineClass (JNIEnv *env, jobject loader,   const jbyte *buf , jsize bufLen);                      功能:从原始类数据的缓冲区中加载类。                  参数: env        JNI 接口指针。                        loader    分派给所定义的类的类加载器。                      buf        包含 .class 文件数据的缓冲区。                           bufLen  缓冲区长度。               返回值:返回 Java 类对象。如果出错则返回NULL。                 抛出: ClassFormatError      如果类数据指定的类无效。                             ClassCircularityError  如果类或接口是自身的超类或超接口。                           OutOfMemoryError  如果系统内存不足。                     jclass FindClass (JNIEnv *env, const char *name);                     功能: 该函数用于加载本地定义的类。它将搜索由CLASSPATH 环境变量为具有指定名称的类所指定的目录和 zip文件。                 参数:env    JNI 接口指针。                      name  类全名(即包名后跟类名,之间由"/"分隔).如果该名称以“[(数组签名字符)打头,则返回一个数组类。              返回值:返回类对象全名。如果找不到该类,则返回 NULL。                    抛出:   ClassFormatError          如果类数据指定的类无效。                               ClassCircularityError      如果类或接口是自身的超类或超接口。                              NoClassDefFoundError  如果找不到所请求的类或接口的定义。                          OutOfMemoryError       如果系统内存不足。                    jclass GetObjectClass (JNIEnv *env, jobject obj);      功能:通过对象获取这个类。该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL。          参数:  env   JNI 接口指针。                         obj   Java 类对象实例。           jclass GetSuperclass (JNIEnv *env, jclass clazz);               功能:获取父类或者说超类 。 如果 clazz 代表类class而非类 object,则该函数返回由 clazz 所指定的类的超类。 如果 clazz            指定类 object 或代表某个接口,则该函数返回NULL。                参数:  env   JNI 接口指针。                        clazz  Java 类对象。                 返回值:    由 clazz 所代表的类的超类或 NULL。                 jboolean IsAssignableFrom (JNIEnv *env, jclass clazz1,  jclass clazz2);               功能:确定 clazz1 的对象是否可安全地强制转换为clazz2。                参数:  env  JNI 接口指针。                        clazz1 第一个类参数。                           clazz2 第二个类参数。                   返回值:  下列某个情况为真时返回 JNI_TRUE:                             1、 第一及第二个类参数引用同一个 Java 类。                            2、 第一个类是第二个类的子类。                           3、 第二个类是第一个类的某个接口。 

2、方法签名
“()” 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
“(II)V” 表示 void Func(int, int);
1)常用类型签名
具体的每一个字符的对应关系如下:

V      void         voidZ      jboolean     booleanI      jint         intJ      jlong        longD      jdouble      doubleF      jfloat       floatB      jbyte        byteC      jchar        charS      jshort       short

2)数组签名
数组则以"["开始,用两个字符表示:

[I     jintArray      int[][F     jfloatArray    float[][B     jbyteArray    byte[][C     jcharArray    char[][S     jshortArray   short[][D     jdoubleArray double[][J     jlongArray     long[][Z     jbooleanArray boolean[]String[] [Ljava/lang/String;Object[] [Ljava/lang/Object;int[][] [[I

3)类签名
采用L+包名+类型+;的形式,只需要将其中的.替换为/即可。 例如java.lang.String,它的签名为Ljava/lang/String;,末尾的;也是一部分

Ljava/lang/String; String jstringLjava/net/Socket;  Socket jobjectjthrowable --> java.lang.Throwable

注:
对象的签名就是对象所属的类签名。
4)方法签名
采用L+包名+类型+;的形式,只需要将其中的.替换为/即可。 例如java.lang.String,它的签名为Ljava/lang/String;,末尾的;也是一部分

boolean fun(int a, double b, int[] c);-->(ID[I)Zvoid fun(int a, String s, int[] c);-->(ILjava/lang/String;[I)Vint fun();-->()Iint fun(float f); -->(F)I

注:
可以使用javap 生成方法签名
3、JNIEnv 介绍
1)JNIEnv概念
JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境
2)JNIEnv与JavaVM
JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;
JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;
3)作用
调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;
操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;
4)JNIEnv 在c和c++的区别

c风格:(*env)->FindClass(env,"com/example/jnisample/Prompt");c++风格:env->FindClass("com/example/jnisample/Prompt");

JNIEnv 在jni.h中定义

#if defined(__cplusplus)typedef _JNIEnv JNIEnv;typedef _JavaVM JavaVM;#elsetypedef const struct JNINativeInterface* JNIEnv;typedef const struct JNIInvokeInterface* JavaVM;#endifstruct JNINativeInterface {        ......很多方法    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,};struct _JNIEnv {         /* do not rename this; it does not seem to be entirely opaque */    const struct JNINativeInterface* functions;#if defined(__cplusplus)    jint GetVersion()    {      return functions->GetVersion(this); }.....其他方法}

可以看到JNINativeInterface 其实定义了很多方法,都是对Java的数据进行操作,而_JNIEnv则封装了一个JNINativeInterface的指针,并且声明与JNINativeInterface中一模一样的方法,并且都是通过JNINativeInterface的指针来调方法,其实就是对JNINativeInterface做了一层封装。
JNIEnv,指代了Java本地接口环境,是一个JNI接口指针,指向了本地方法的一个函数表。 jobject与jclass通常作为JNI函数的第二个参数,当所声明Native方法是静态方法时,对应参数jclass,因为静态方法不依赖对象实例,而依赖于类,所以参数中传递的是一个jclass类型。相反,如果声明的Native方法时非静态方法时,那么对应参数是jobject。

四、使用
1、新建javahello类
javahello.java

package com.testjnionload;public class javahello {         public static native String getJniHello();    static {             System.loadLibrary("testttJni");    }}

2、实现jni层程序 hello.cpp

#include <stdlib.h>#include <string.h>#include <stdio.h>#include <jni.h>#include <assert.h>JNIEXPORT jstring JNICALL native_getJniHello(JNIEnv *env, jclass clazz){      return env->NewStringUTF("hello!!! this is jni layer");}#define JNIREG_CLASS "com/testjnionload/javahello"//指定要注册的类static JNINativeMethod gMethods[] = {     {     "getJniHello", "()Ljava/lang/String;", (void*)native_getJniHello },//绑定};static int registerNativeMethods(JNIEnv* env, const char* className,        JNINativeMethod* gMethods, int numMethods){     jclass clazz;clazz = env->FindClass(className);if (clazz == NULL) {     return JNI_FALSE;}if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {     return JNI_FALSE;}return JNI_TRUE;}static int registerNatives(JNIEnv* env){     if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,                                 sizeof(gMethods) / sizeof(gMethods[0])))return JNI_FALSE;return JNI_TRUE;}JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){     JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {     return -1;}assert(env != NULL);if (!registerNatives(env)) {     //注册return -1;}return JNI_VERSION_1_4;}

3、编写Android.mk文件

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := testttJniLOCAL_SRC_FILES := hello.cppinclude $(BUILD_SHARED_LIBRARY)

4、配置
4.1 app build.gradle

 ndk{                 abiFilters "armeabi-v7a", "x86", "armeabi"            //abiFilters 'armeabi-v7a' //, 'armeabi','x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'        }        sourceSets.main {                 jniLibs.srcDir 'src/main/libs'            // jni.srcDirs = [] //disable automatic ndk-build call        }

5、在jni目录下使用ndk-build编译 生成so库
6、在MainActivity.java中调用

package com.testjnionload;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;public class MainActivity extends AppCompatActivity {         javahello jh;    @Override    protected void onCreate(Bundle savedInstanceState) {             super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        jh = new javahello();        Log.i("MainActivity-------",jh.getJniHello()+"");    }}

注:
1、FindClass调用时传入的java层类的路径并不需要相对jni中cpp文件的路径来配置 ,是直接在java文件夹路径下 即上面的com/testjnionload/javahello (类对应的包名)。否则会出现 java.lang.UnsatisfiedLinkError: JNI_ERR returned from JNI_OnLoad in "/data/app/com.testjni_onload-NZTsWCAr2eyG1BYwpngVQA==/lib/arm/libtestJni.so"错误

更多相关文章

  1. Android(安卓)数据库SQLite的使用简单Demo
  2. android完全退出程序
  3. Android(安卓)APK应用安装原理(1)-解析AndroidManifest原理-Pack
  4. Android(安卓)ButterKnife框架的使用方法
  5. android lru缓存 辅助类LruCache源码解析
  6. Android(安卓)中报错 W/System.err: android.os.NetworkOnMainTh
  7. Android中的AsyncTask原理
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. 使用JavaScript显示HTML文本框中剩余的字
  2. ArcGIS API For JavaScript 实现鼠标移入
  3. 角度连接2控制器通过1服务与ES6语法
  4. JavaScript数组中的唯一计数,按计数排序
  5. 如何访问远程节点。浏览器中的js应用程序
  6. 为什么jquery click事件在plunker中工作
  7. Gulp.js事件流合并顺序
  8. 优雅的JavaScript-BOM详解
  9. 单击它时,为什么下拉菜单不在我的导航栏中
  10. 用javascript 面向对象制作坦克大战(二)