Android Jni 基础笔记

记录一下Jni的实际开发中会遇到的一些问题。

首先说说Jni的两种加载方式,分别是动态注册和静态注册。
静态注册:静态注册就是根据Java的函数名和Jni的函数名,按照一定的关系进行联系。Jni层的命名方式必须遵循一定的规则。
动态注册:在Jni层,把一个函数映射表注册给Java虚拟机。这样Jvm就可以通过函数列表来找到对应的函数。就可以不必通过函数名来查找需要调用的函数了。
1
2
3
4
5
6
//Java与JNI通过JNINativeMethod的结构来建立联系,它在jni.h中被定义,其结构内容如下:
typedef  struct  {
const  char * name;    //Java中函数的名字。
const  char * signature;   //描述了函数的参数和返回值
void * fnPtr;       //函数指针,指向C函数
} JNINativeMethod;

静态注册的步骤:

1. 先在Java上声明jni函数。如在MainActivity类中声明public native void helloWorld(),包名是com.example.test。

2. 新建C或Cpp文件,按照规定的命名方式去命名Jni层的函数。Java_包名_类名_函数名()。这个名字可以用javah命令快速生成。要注意:C++要用如下格式,否则会找不到函数

1
2
3
4
5
6
7
#ifdef __cplusplus
extern  "C"  {
#endif
     //sourc...
#ifdef __cplusplus
}
#endif

javah使用方法说明:

-classpath <路径> 用于装入类的路径
-d <目录>             输出目录
-jni 生成 JNI样式的头文件(默认)

在cmd中或者cygwin中,输入javah -class *.class -d 输出目录 -jni 要被生成的包名加类名。

例如 按步骤1的声明,当前在项目目录helloFromC。编译后就会在bin目录下按包名生成class,helloFromC\bin\classes\com\example\test\MainActivity.class。javah就是javah -class bin\classes\ -d .\ -jni com.example.test.MainActivity。但有时会出现,错误: 无法访问android.app.Activity。网上有人说是环境变量问题,但怎么看我的环境变量都没问题。最后用了一个说法,在src目录下直接 javah com.example.test.MainActivity

3.  编写Android.mk,编译

1
2
3
4
5
6
7
8
9
10
11
LOCAL_PATH := $(call my-dir)
 
include  $(CLEAR_VARS)
 
LOCAL_MODULE    := libcoep_jni
LOCAL_SRC_FILES := coep_jni.cpp
  
LOCAL_SHARED_LIBRARIES := \
     liblog \
                         
include  $(BUILD_SHARED_LIBRARY)

4. 最后,会编译出在MainActivity中加入如下语句,加载编译后生成的库。如果在Android中用mm来编译会在out目录下生成编译出来的静态库

1
2
3
static {
     System.loadLibrary( "coep_jni" );
}  


动态注册的步骤:

其实动态注册也是很简单的

一. 像普通的C编程一样把逻辑函数都用jni形式写好,命名是随意的。

如,普通C函数为:

void funcA(){}

int funcB(int arg){}

Jni的要在参数列表里加上 (JNIEnv *env, jobject obj, ...),上面的会变成,注意返回值和形参:

void funcA(JNIEnv *env, jobject obj,){}

jint funcB(JNIEnv *env, jobject obj, int arg){}

jint funcC(JNIEnv *env, jobject thiz, jstring jName,   jstring arg) {}

二. 把这些函数加入到Jni的函数数组中,让Jni函数与Java函数对应

1
2
3
4
5
6
7
8
static  JNINativeMethod gMethods[] = {
  
      { "funcA_native" "()V" , ( void  *)funcA},
  
     { "funcB_native" "(I)I" , ( void  *)funcB},
     { "funcC_native" "(Ljava/lang/String;Ljava/lang/String;)I" , ( void  *)funcC}
  
};

JNINativeMethod的定义为:

1
2
3
4
5
typedef  struct  {   
const  char * name;           //Java中函数的名字 
const  char * signature;      //用字符串描述的函数的参数和返回值 
void * fnPtr;                //指向C函数的函数指针 
} JNINativeMethod;

其中数组的第二个子元素为signature,signature意为签名,即为函数签名。数据类型与签名关系如下。注意,类的签名后要加分号,如上面的funcC:

字符 Java类型 C类型

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


数组则以"["开始,用两个字符表示

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


三, 注册上面的Jni函数数组,与指定的类进行绑定

1
2
3
4
static  int  registerNatives(JNIEnv *env){
     const  char  *kClassName =  "package_name/class_name"   ;

    return  jniRegisterNativeMethods(env, kClassName, gMethods, NELEM(gMethods));

}

四. 加载

在调用System.loadLibrary(“xxx”);时会先调用JNI_OnLoad会自动把Jni函数注册在虚拟机中,并建立Jni与上层函数的联系。如果你的Jni里面没有实现JNI_Onload时会使用默认的JNI_Onload并使用的时默认的Jni版本JNI 1.1。

如果使用动态注册,我们就要实现我们自己的JNI_Onload,并使用最新版本的Jni。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm,  void  *reserved){
     JNIEnv *env = NULL;
     jint result = -1;
     
     LOGI( "JNI_OnLoad begin" );
     
     /*通过虚拟机获取当前的env*/
     if (vm->GetEnv(( void  **)&env, JNI_VERSION_1_4) != JNI_OK){
         printf ( "Error GetEnv\n" );
         return  -1; 
     }  
     
     assert (env != NULL);
     
     /*调用我们刚才的registerNatives*/
     if (registerNatives(env) < 0){
         printf ( "register_android_test_hello error.\n" );
         return  -1; 
     }
     
     /*返回要使用的Jni版本,表示onload成功*/
     result = JNI_VERSION_1_4;
     LOGI( "JNI_OnLoad finish" );
     
     return  result;
}


Jni的Log系统

在使用jni的时候如果使用printf()。并不会在eclipse里的log框里打印,这样就不方便开发了。如果要在eclipse的log框里显示,就要使用android的log系统。

首先我们要在Android.mk里包含库:LOCAL_SHARED_LIBRARIES := \liblog \

首先我们要在源码里包含头文件:#include <android/log.h>。

android/log.h这个头文件包含了最原始的打印函数,有兴趣的可以去看看。我们用其中的__android_log_print(); 但这还不行,我们还要做一个宏定义

1
2
3
4
5
static  const  char  *TAG =  "YoutTag" ;
  
#define LOGI(fmt, args...)  __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
#define LOGD(fmt, args...)  __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...)  __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

__android_log_print的第一个参数就表时了Log的等级。

如果你的Jni在系统中,还可以使用Jni自带的一个Log工具。注意,这些工具有可能在上层App中不能用,在系统里的Jni可以用。

头文件是 #include "utils/Log.h"

主要是"utils/Log.h"中的#include <cutils/log.h>

里面定义了很多

LOGE,LOGW,LOGI,LOGD,LOGV

LOGE_IF,LOGW_IF,LOGI_IF,LOGD_IF,LOGV_IF

只要定义了他们需要的TAG就可直接使用,#define LOG_TAG "XXXX"

注意,使用方法还是和C的printf那样使用。如:LOGI( "xxxx  %d", i );


Jni的自身提供的工具

JNI提供了很多快速开发的工具,在JNIHelp.h中定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值