Bootstrap

android jni (jni_onload方式)

一、简述
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-string
const 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, jboolean
NativeType * 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
// <type> 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
返回值为booleanCallBooleanMethod ( )	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         void
Z      jboolean     boolean
I      jint         int
J      jlong        long
D      jdouble      double
F      jfloat       float
B      jbyte        byte
C      jchar        char
S      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 jstring
Ljava/net/Socket;  Socket jobject
jthrowable --> java.lang.Throwable

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

boolean fun(int a, double b, int[] c);		-->		(ID[I)Z
void fun(int a, String s, int[] c);			-->		(ILjava/lang/String;[I)V
int fun();									-->		()I
int 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;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

struct 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 := testttJni
LOCAL_SRC_FILES := hello.cpp
include $(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"错误
在这里插入图片描述

;