`

Android 开发 之 JNI入门 - NDK从入门到精通

 
阅读更多

NDK项目源码地址 :

--第一个JNI示例程序下载 :GitHub- https://github.com/han1202012/NDKHelloworld.git

--Java传递参数给C语言实例程序 : GitHub -https://github.com/han1202012/NDKParameterPassing.git

--C语言回调Java方法示例程序 : GitHub -https://github.com/han1202012/NDK_Callback.git

--分析Log框架层JNI源码所需的Android底层文件 : CSDN -http://download.csdn.net/detail/han1202012/6905507

.

作者 :万境绝尘

转载请注明出处 :http://blog.csdn.net/shulianghan/article/details/18964835

.

开发环境介绍 :

-- eclipse :adt-bundle-windows-x86-20130917

-- sdk : 版本 2.3.3

-- ndk :android-ndk-r9c-windows-x86.zip

-- cygwin : 所需组件binutils , gcc , gcc-mingw , gdb , make;

-- javah : jdk6.0自带工具

-- javap : jdk6.0自带工具



JNI 总结 :



Java 调用 C 流程 :

-- a. 定义 Native 方法 : 在shuliang.han.ndkparameterpassing.DataProvider.java 类中定义 Native 方法public native int add(int x, int y);

-- b. 生成方法签名 : 进入 AndroidProject/bin/classes 目录, 使用 javahshuliang.han.ndkparameterpassing.DataProvider 命令, 便生成了头文件, 该头文件引用了 jni.h, 以及定义好了 对应的 Native 方法, 生成JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add (JNIEnv *, jobject, jint, jint);

-- c. 编写 Android.mk 文件 :

LOCAL_PATH := $(call my-dir)  
  
include $(CLEAR_VARS)  
  
LOCAL_MODULE    := hello-jni  
LOCAL_SRC_FILES := hello-jni.c  
  
include $(BUILD_SHARED_LIBRARY)
-- d. 生成 动态库 so 文件 : 进入 Android.mk 所在目录, 在该目录执行 ndk 下的 ndk-build 命令;

-- e. Java代码加载动态库 : 在 Java 代码中调用该类的类前面, 在类的一开始, 不在方法中, 加入static{ System.loadLibrary("hello"); } ;




一. JNI介绍


1. JNI引入


JNI概念 : Java本地接口,Java Native Interface, 它是一个协议, 该协议用来沟通Java代码和外部的本地C/C++代码, 通过该协议 Java代码可以调用外部的本地代码, 外部的C/C++ 代码可以调用Java代码;


C和Java的侧重 :

-- C语言 : C语言中最重要的是 函数 function;

-- Java语言 : Java中最重要的是 JVM, class类, 以及class中的方法;


C与Java如何交流 :

-- JNI规范 : C语言与Java语言交流需要一个适配器, 中间件, 即 JNI, JNI提供了一种规范;

-- C语言中调用Java方法 : 可以让我们在C代码中找到Java代码class中的方法, 并且调用该方法;

-- Java语言中调用C语言方法 : 同时也可以在Java代码中, 将一个C语言的方法映射到Java的某个方法上;

-- JNI桥梁作用 : JNI提供了一个桥梁, 打通了C语言和Java语言之间的障碍;


JNI中的一些概念 :

-- native : Java语言中修饰本地方法的修饰符, 被该修饰符修饰的方法没有方法体;

-- Native方法 : 在Java语言中被native关键字修饰的方法是Native方法;

-- JNI层 : Java声明Native方法的部分;

-- JNI函数 : JNIEnv提供的函数, 这些函数在jni.h中进行定义;

-- JNI方法 : Native方法对应的JNI层实现的 C/C++方法, 即在jni目录中实现的那些C语言代码;


2. Android中的应用程序框架


正常情况下的Android框架 : 最顶层Android的应用程序代码, 上层的应用层应用框架层 主要是Java代码,中间有一层的Framework框架层代码是 C/C++代码, 通过Framework进行系统调用, 调用底层的库 和linux 内核;



使用JNI时的Android框架 : 绕过Framework提供的调用底层的代码, 直接调用自己写的C代码, 该代码最终会编译成为一个库, 这个库通过JNI提供的一个Stable的ABI 调用linux kernel;ABI是二进制程序接口 application binary interface.



纽带 : JNI是连接框架层 (Framework - C/C++)应用框架层(Application Framework - Java)的纽带;


JNI在Android中作用 : JNI可以调用本地代码库(即C/C++代码), 并通过 Dalvik虚拟机 与应用层应用框架层进行交互, Android中JNI代码主要位于应用层 和 应用框架层;

-- 应用层 : 该层是由JNI开发, 主要使用标准JNI编程模型;

-- 应用框架层 : 使用的是Android中自定义的一套JNI编程模型, 该自定义的JNI编程模型弥补了标准JNI编程模型的不足;


Android中JNI源码位置 : 在应用框架层中, 主要的JNI代码位于 framework/base目录下, 这些模块被编译成共享库之后放在 /system/lib 目录下;


NDK与JNI区别 :

-- NDK: NDK是Google开发的一套开发和编译工具集, 主要用于Android的JNI开发;

-- JNI : JNI是一套编程接口, 用来实现Java代码与本地的C/C++代码进行交互;


JNI编程步骤:

-- 声明native方法 : 在Java代码中声明 native method()方法;

-- 实现JNI的C/C++方法 : 在JNI层实现Java中声明的native方法, 这里使用javah工具生成带方法签名的头文件, 该JNI层的C/C++代码将被编译成动态库;

-- 加载动态库 : 在Java代码中的静态代码块中加载JNI编译后的动态共享库;

.


3. JNI作用


JNI作用 :

-- 扩展: JNI扩展了JVM能力, 驱动开发, 例如开发一个wifi驱动, 可以将手机设置为无限路由;

-- 高效 : 本地代码效率高, 游戏渲染, 音频视频处理等方面使用JNI调用本地代码, C语言可以灵活操作内存;

-- 复用 : 在文件压缩算法 7zip开源代码库, 机器视觉 openCV开放算法库 等方面可以复用C平台上的代码, 不必在开发一套完整的Java体系, 避免重复发明轮子;

-- 特殊 : 产品的核心技术一般也采用JNI开发, 不易破解;


Java语言执行流程 :

-- 编译字节码 : Java编译器编译 .java源文件, 获得.class 字节码文件;

-- 装载类库 : 使用类装载器装载平台上的Java类库, 并进行字节码验证;

-- Java虚拟机 : 将字节码加入到JVM中, Java解释器 和 即时编译器 同时处理字节码文件, 将处理后的结果放入运行时系统;

-- 调用JVM所在平台类库 : JVM处理字节码后, 转换成相应平台的操作, 调用本平台底层类库进行相关处理;



Java一次编译到处执行 : JVM在不同的操作系统都有实现, Java可以一次编译到处运行, 字节码文件一旦编译好了, 可以放在任何平台的虚拟机上运行;

.


二. NDK详解


1. 交叉编译库文件


C代码执行 : C代码被编译成库文件之后, 才能执行, 库文件分为动态库静态库 两种;

-- 动态库 : unix环境下.so 后缀的是动态库, windows环境下.dll 后缀的是动态库; 动态库可以依赖静态库加载一些可执行的C代码;

-- 静态库 :.a 后缀是静态库的扩展名;


库文件来源 : C代码 进行 编译 链接操作之后, 才会生成库文件, 不同类型的CPU 操作系统 生成的库文件是不一样;

-- CPU分类 : arm结构, 嵌入式设备处理器; x86结构, pc 服务器处理器; 不同的CPU指令集不同;

-- 交叉编译 :windows x86编译出来的库文件可以在arm平台运行的代码;

-- 交叉编译工具链 : Google提供的 NDK 就是交叉编译工具链, 可以在linux环境下编译出在arn平台下执行的二进制库文件;


NDK作用 : 是Google提供了交叉编译工具链, 能够在linux平台编译出在arm平台下执行的二进制库文件;


NDK版本介绍 : android-ndk-windows 是在windows系统中的cygwin使用的, android-ndk-linux 是在linux下使用的;


2. 部署NDK开发环境


(1) 下载Cygwin安装器


下载地址 :http://cygwin.com/setup-x86.exe , 这是下载器, 可以使用该下载器在线安装, 也可以将cygwin下载到本地之后, 在进行安装;


安装器使用 : Cygwin的下载, 在线安装, 卸载 等操作都有由该安装器进行;

-- 本地文件安装 : 选择安装文件所在的目录, 然后选择所要安装的安装包;

-- 在线安装 : 选择在线安装即可, 然后选择需要的安装包;

-- 卸载 : windows上使用其它软件例如360, 控制面板中是无法卸载Cygwin的, 只能通过安装器来卸载;


(2) 安装Cygin


双击安装器setup-x86.exe 下一步 :



选择安装方式 :

-- 在线安装 : 直接下载, 然后安装;

-- 下载安装文件 : 将安装文件下载下来, 可以随时安装, 注意安装文件也需要安装器来进行安装;

-- 从本地文件安装 : 即使用下载的安装文件进行安装;



选择Cygwin安装位置 :



选择下载好安装文件位置 : 之前我下了一个完全版的Cygwin, 包括了所有的Cygwin组件, 全部加起来有5.23G, 下载速度很快, 使用网易的镜像, 基本可以全速下载;



选择需要安装Cygwin组件 : 这里我们只需要以下组件 : binutils , gcc , gcc-mingw , gdb , make , 不用下全部的组件;



之后点击下一步等待完成安装即可;

.

安装完之后, 打开bash命令窗口, 可以设置下显示的字体, 使用 make -version 查看是否安装成功 :



(3) Cygwin目录介绍


以下是Cygwin安装目录的情况 : 该安装目录就是所模拟的linux 的根目录;


对应的linux目录 : 这两个目录进行对比发现, 两个目录是一样的, Cygwin的安装目录就是 linux根目录;



cygdrive目录 : 该目录是Cygwin模拟出来的windows目录结构, 进入该目录后, 会发现windows的盘符目录, 通过该目录可以访问windows中的文件;




(4) 下载NDK工具


从Google的Android开发者官网上下载该工具, 注意NDK工具分类 : 下载地址 -http://developer.android.com/tools/sdk/ndk/index.html-;

-- windows版本NDK:android-ndk-r9c-windows-x86.zip(32位),android-ndk-r9c-windows-x86_64.zip(64位) 该版本是用在windows上的Cygwin下, 不能直接在windows上直接运行;

-- linux版本NDK :android-ndk-r9c-linux-x86.tar.bz2(32位) , android-ndk-r9c-linux-x86_64.tar.bz2(64位) , 该版本直接在linux下执行即可;


在这里下载windows版本的NDK, 运行在Cygwin上;



(4) NDK环境介绍


NDK工具的文件结构 :



ndk-build脚本 : NDK build 脚本是 gun-make 的简单封装, gun-make 是编译C语言代码的工具, 该脚本执行的前提是linux环境下必须安装 make 程序;


NDK安装在Cygwin中: 将NDK压缩文件拷贝到Cygwin的根目录中, 解压 :android-ndk-r9c 目录就是NDK目录;

执行以下NDK目录下的 ndk-build 命令: ./ndk-build ;

执行结果:

Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
/android-ndk-r9c/build/core/build-local.mk:148: *** Android NDK: Aborting    。 停止。



三. 开发第一个NDK程序


1. 开发NDK程序流程


a. 创建Android工程:

首选创建一个Android工程, 在这个工程中进行JNI开发;


b. 声明native方法 :

注意方法名使用 native 修饰, 没有方法体 和 参数, eg :public native String helloFromJNI();


c. 创建C文件 :

在工程根目录下创建 jni 目录, 然后创建一个c语言源文件, 在文件中引入 include <jni.h> , C语言方法声明格式jstring Java_shuliang.han.ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env) , jstring 是 Java语言中的String类型, 方法名格式为 :Java_完整包名类名_方法名();

-- JNIEnv参数 : 代表的是Java环境, 通过这个环境可以调用Java里面的方法;

-- jobject参数 : 调用C语言方法的对象, thiz对象表示当前的对象, 即调用JNI方法所在的类;


d. 编写Android.mk文件 :

如何写 查看文档, NDK根目录下有一个documentation.html 文档, 点击该html文件就可以查看文档, 查看Android.mk File 文档, 下面是该文档给出的 Android.mk示例 :


LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)


--LOCAL_PATH : 代表mk文件所在的目录;

--include $(CLEAR_VARS) : 编译工具函数, 通过该函数可以进行一些初始化操作;

--LOCAL_MODULE : 编译后的 .so 后缀文件叫什么名字;

--LOCAL_SRC_FILES: 指定编译的源文件名称;

--include $(BUILD_SHARED_LIBRARY) : 告诉编译器需要生成动态库;


e. NDK编译生成动态库 :

进入cygdrive 找到windows目录下对应的文件, 编译完成之后, 会自动生成so文件并放在libs目录下, 之后就可以在Java中调用C语言方法了;


f. Java中加载动态库 :

在Java类中的静态代码块中使用System.LoadLibrary()方法加载编译好的 .so 动态库;



NDK平台版本 : NDK脚本随着 android-sdk 版本不同, 执行的脚本也是不同的, 不同平台会引用不同的头文件, 编译的时候一定注意 sdk 与 ndk 版本要一致;

so文件在内存中位置 : apk文件安装到手机上之后, .so动态库文件存在在 data/安装目录/libs 目录下;


2. 开发实例


按照上面的步骤进行开发


(1) 创建Android工程


Android工程版本 : 创建一个Android工程,minSdk 为 7 即 android-2.1, 编译使用的sdk为 10 即 android-2.3.3 ;
    <uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="10" />


NDK编译原则 : 编译NDK动态库是按照最小版本进行编译, 选择编译的平台的时候, 会选择 NDK 7 平台进行编译;



(2) 声明native方法


声明native方法, 注意该方法没有方法体 和 参数, 如下 :

	/*
	 * 声明一个native方法
	 * 这个方法在Java中是没有实现的, 没有方法体
	 * 该方法需要使用C语言编写
	 */
	public native String helloFromJNI();

.

作者:万境绝尘

转载请注明出处:http://blog.csdn.net/shulianghan/article/details/18964835

.

(3) 创建C文件


引入头文件: 首先要包含头文件 jni.h, 该头文件位置定义在android-ndk-r9c\platforms\android-5\arch-arm\usr\include目录下的 jni.h, 下面是该头文件中定义的一些方法, 包括本项目中使用的 NewString 方法;
jstring     (*NewString)(JNIEnv*, const jchar*, jsize);
jsize       (*GetStringLength)(JNIEnv*, jstring);
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
void        (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
jstring     (*NewStringUTF)(JNIEnv*, const char*);
jsize       (*GetStringUTFLength)(JNIEnv*, jstring);



调用Java类型 : C中调用Java中的String类型为 jstring;

C语言方法名规则 :Java_完整包名类名_方法名(JNIEnv *env, jobject thiz), 注意完整的类名包名中包名的点要用 _ 代替;

参数介绍 : C语言方法中有两个重要的参数,JNIEnv *env, jobject thiz ;
--JNIEnv参数 : 该参数代表Java环境, 通过这个环境可以调用Java中的方法;
--jobject参数 : 该参数代表调用jni方法的类, 在这里就是MainActivity;

调用jni.h中的NewStringUTF方法 : 该方法的作用是在C语言中创建一个Java语言中的String类型对象, jni.h中是这样定义的jstring (*NewStringUTF)(JNIEnv*, const char*),JNIEnv 结构体中包含了NewStringUTF 函数指针, 通过JNIEnv 就可以调用这个方法;

C语言文件源码 :
#include <jni.h>

/*
 * 方法名称规定 : Java_完整包名类名_方法名()
 * JNIEnv 指针
 *
 * 参数介绍 :
 * env : 代表Java环境, 通过这个环境可以调用Java中的方法
 * thiz : 代表调用JNI方法的对象, 即MainActivity对象
 */
jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)
{
	/*
	 * 调用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法
	 * jni.h 中定义的方法  jstring (*NewStringUTF)(JNIEnv*, const char*); 
	 */
	return (*env)->NewStringUTF(env, "hello world jni");
}


(4) 编写Android.mk文件



查询NDK文档 : NDK的文档在NDK工具根目录下, 点击documentation.html 文件, 就可以在浏览器中打开NDK文档;

上面的开发流程中详细的介绍了Android.mk 五个参数的详细用处, 这里直接给出源码 :
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello
LOCAL_SRC_FILES := hello.c

include $(BUILD_SHARED_LIBRARY)



(5) 编译NDK动态库



进入Cygwin相应目录 : 从Cygwin中的cygdrive 中进入windows的工程jni目录 ;


编译hello.c文件 : 注意Android.mk文件 与 hello.c 文件在同一目录中;


编译完成后的情况 : 编译完之后 会成成一个obj文件, 在obj文件中会生成 libhello.so, 系统会自动将该 so后缀文件放在libs目录下;



(6) Java中加载动态库


静态代码块中加载 : Java中在静态代码块中加载库文件, 调用System.loadLibrary("hello") 方法,注意 libs中的库文件名称为 libhello.so,我们加载的时候 将 lib 去掉, 只取hello 作为动态库名称, 这是规定的;
	//静态代码块加载C语言库文件
	static{
		System.loadLibrary("hello");
	}


(7) 其它源码


MainActivity源码 :
package shuliang.han.ndkhelloworld;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {

	//静态代码块加载C语言库文件
	static{
		System.loadLibrary("hello");
	}
	
	/*
	 * 声明一个native方法
	 * 这个方法在Java中是没有实现的, 没有方法体
	 * 该方法需要使用C语言编写
	 */
	public native String helloFromJNI();
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        System.out.println(helloFromJNI());
    }

    public void onClick(View view) {
    	//点击按钮显示从jni调用得到的字符串信息
    	Toast.makeText(getApplicationContext(), helloFromJNI(), 1).show();
	}
    
}

XML布局文件 :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/bt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="显示JNI返回的字符串" />

</RelativeLayout>


(8) 将源码上传到GitHub中



在上一篇博客http://blog.csdn.net/shulianghan/article/details/18812279中对GitHub用法进行了详解;

在GitHub上创建工程 :

项目地址
-- HTTP:https://github.com/han1202012/NDKHelloworld.git
-- SSH :git@github.com:han1202012/NDKHelloworld.git

生成的命令 :
touch README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin git@github.com:han1202012/NDKHelloworld.git
git push -u origin master

打开 Git Bash 命令行窗口 :
-- 从GitHub上克隆项目到本地 : git clone git@github.com:han1202012/NDKHelloworld.git , 注意克隆的时候直接在仓库根目录即可, 不用再创建项目根目录 ;


-- 添加文件 : git add ./* , 将目录中所有文件添加;

-- 查看状态 : git status ;

-- 提交缓存 : git commit -m '提交';

-- 提交到远程GitHub仓库 : git push -u origin master ;


GitHub项目 :



3. 项目讲解


(1) Android.mk文件讲解


Android.mk文件内容 :

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello
LOCAL_SRC_FILES := hello.c

include $(BUILD_SHARED_LIBRARY)

获取当前文件内容 : $(call my-dir) 是编译器中的宏方法, 调用该宏方法, 就会返回前的目录路径;
赋值符号 : " := " 是赋值符号, 第一句话 是 返回当前文件所在的当前目录, 并将这个目录路径赋值给 LOCAL_PATH;
初始化编译模块参数 : $(CLEAR_VARS) 作用是将编译模块的参数初始化, LOCAL_MODULE LOCAL_SRC_FILES 也是这样的参数;
指定编译模块 : LOCAL_MODULE := hello , 指定编译后的 so 文件名称, 编译好之后系统会在该名称前面加上 "lib", 后缀加上 ".so";
指定编译源文件 : LOCAL_SRC_FILES := hello.c 告诉编译系统源文件, 如果有多个文件那么就依次写在后面即可;
编译成静态库 :include $(BUILD_SHARED_LIBRARY), 作用是高速系统, 编译的结果编译成 .so 后缀的静态库;

静态库引入 : NDK的platform中有很多 ".a" 结尾的动态库, 我们编译动态库的时候, 可以将一些静态库引入进来;


(2) 自动生成方法签名



使用javah工具 : 在C中实现Java调用的jni方法, 方法的签名很复杂, 需要将完整的包名类名方法名都要使用 "_" 连接起来, 很麻烦, jdk提供的生成签名方法的工具;

遗留问题 : 目前查到的方法是 在bin目录下 执行 javah -jni 包名类名 命令, 但是执行不成功, 暂时没找到解决方案;
-- Android中会自动生成 .class文件吗, 没发现啊, PY人!


解决问题 : 在jni目录下存在classes目录, 但是这个目录在eclipse中不显示, 这里我们要注意;


在Cygwin中使用 javah 命令即可 :

生成的头文件 :shuliang_han_ndkparameterpassing_DataProvider.h;
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class shuliang_han_ndkparameterpassing_DataProvider */

#ifndef _Included_shuliang_han_ndkparameterpassing_DataProvider
#define _Included_shuliang_han_ndkparameterpassing_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     shuliang_han_ndkparameterpassing_DataProvider
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     shuliang_han_ndkparameterpassing_DataProvider
 * Method:    sayHelloInc
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc
  (JNIEnv *, jobject, jstring);

/*
 * Class:     shuliang_han_ndkparameterpassing_DataProvider
 * Method:    intMethod
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

.


(3) NDK开发中乱码问题


解决乱码思路 : C语言编译的时候用的是 ISO-8859-1 码表进行编码, 如果我们使用C语言jni开发, 需要进行转码操作;
--将ISO-8859-1转为UTF-8字符: String string = new String(str.getBytes("iso8859-1"), "UTF-8");


示例 :

添加中文jni调用 : 将jni中的hello.c 中返回的字符串修改为中文, 重新编译 .so 静态库文件;
-- 修改后的hello.c文件如下 : 只改变了返回的字符串, 添加了中文;
#include <jni.h>

/*
 * 方法名称规定 : Java_完整包名类名_方法名()
 * JNIEnv 指针
 *
 * 参数介绍 :
 * env : 代表Java环境, 通过这个环境可以调用Java中的方法
 * thiz : 代表调用JNI方法的对象, 即MainActivity对象
 */
jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)
{
	/*
	 * 调用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法
	 * jni.h 中定义的方法  jstring (*NewStringUTF)(JNIEnv*, const char*);
	 */
	return (*env)->NewStringUTF(env, "hello world jni 中文");
}

使用NDK重新编译hello.c文件 : 修改了C源码之后, 重新将该c文件编译成so文件;
-- 编译过程: 打开cygwin, 进入cygdrive/ 下对应windows中源码项目中的jni目录, 执行 /android-ndk-r9c/ndk-build 命令;



运行Android代码报错 : 因为jni中c文件有中文, 中文不能被识别;
01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0
01-31 14:36:04.803: W/dalvikvm(389):              string: 'hello world jni ????'
01-31 14:36:04.803: W/dalvikvm(389):              in Lshuliang/han/ndkhelloworld/MainActivity;.helloFromJNI ()Ljava/lang/String; (NewStringUTF)
01-31 14:36:04.834: I/dalvikvm(389): "main" prio=5 tid=1 NATIVE
01-31 14:36:04.834: I/dalvikvm(389):   | group="main" sCount=0 dsCount=0 obj=0x4001f1a8 self=0xce48
01-31 14:36:04.834: I/dalvikvm(389):   | sysTid=389 nice=0 sched=0/0 cgrp=default handle=-1345006528
01-31 14:36:04.844: I/dalvikvm(389):   | schedstat=( 257006717 305462830 51 )
01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.helloFromJNI(Native Method)
01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.onCreate(MainActivity.java:26)
01-31 14:36:04.844: I/dalvikvm(389):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.access$1500(ActivityThread.java:117)
01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Handler.dispatchMessage(Handler.java:99)
01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Looper.loop(Looper.java:123)
01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread.main(ActivityThread.java:3683)
01-31 14:36:04.864: I/dalvikvm(389):   at java.lang.reflect.Method.invokeNative(Native Method)
01-31 14:36:04.874: I/dalvikvm(389):   at java.lang.reflect.Method.invoke(Method.java:507)
01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
01-31 14:36:04.874: I/dalvikvm(389):   at dalvik.system.NativeStart.main(Native Method)
01-31 14:36:04.884: E/dalvikvm(389): VM aborting
.


4. JNIEnv 详解


JNIEnv作用 : JNIEnv 是一个指针,指向了一组JNI函数, 这些函数可以在jni.h中查询到,通过这些函数可以实现 Java层 与 JNI层的交互, 通过JNIEnv 调用JNI函数 可以访问java虚拟机, 操作java对象;

JNI线程相关性 : JNIEnv只在当前的线程有效,JNIEnv不能跨线程传递, 相同的Java线程调用本地方法, 所使用的JNIEnv是相同的, 一个Native方法不能被不同的Java线程调用;

JNIEnv结构体系 : JNIEnv指针指向一个线程相关的结构,线程相关结构指向一个指针数组,指针数组中的每个元素最终指向一个JNI函数.

(1) JNIEnv的C/C++声明


jni.h中声明JNIEnv : C语言中定义的JNIEnv 是 JNINativeInterface* , C++中定义的JNIEnv 是 _JNIEnv;
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)	//为了兼容C 和 C++两种代码 使用该 宏加以区分
typedef _JNIEnv JNIEnv;		//C++ 中的JNIEnv类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;//C语言中的JNIEnv类型
typedef const struct JNIInvokeInterface* JavaVM;
#endif

(2) C语言中的JNIEnv


关于JNIEnv指针调用解析 : C中JNIEnv就是 const struct JNINativeInterface*, JNIEnv * env等价于 JNINativeInterface** env, 因此要得到JNINativeInterface结构体中定义的函数指针, 就必须先获取到 JNINativeInterface的一级指针对象 即 *env , 该一级指针对象就是 JNINativeInterface* env, 然后通过该一级指针对象调用JNI函数 : (*env)->NewStringUTF(env, "hello");

在JNINativeInterface结构体中定义了一系列的关于Java操作的相关方法 :
/*
 * Table of interface function pointers.
 */
struct JNINativeInterface {
    void*       reserved0;
    void*       reserved1;
	
	... ...
	
    jboolean    (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
                        va_list);
    jboolean    (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,
                        jvalue*);
    jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
    jbyte       (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);
	
	... ...
	
    void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);
    jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);

    /* added in JNI 1.6 */
    jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
};

(3) C++中的JNIEnv


C++ 中的JNIEnv: C++ 中的JNIEnv 就是 _JNIEnv 结构体, 二者是等同的; 因此在调用 JNI函数的时候, 只需要使用 env->NewStringUTF(env, "hello")方法即可, 不用在进行*运算;

.
/*
 * C++ object wrapper.
 *
 * This is usually overlaid on a C struct whose first element is a
 * JNINativeInterface*.  We rely somewhat on compiler behavior.
 */
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); }

    jlong GetDirectBufferCapacity(jobject buf)
    { return functions->GetDirectBufferCapacity(this, buf); }

    /* added in JNI 1.6 */
    jobjectRefType GetObjectRefType(jobject obj)
    { return functions->GetObjectRefType(this, obj); }
#endif /*__cplusplus*/
};

5. JNI方法命名规则(标准JNI规范)


JNI实现的方法 与 Java中Native方法的映射关系 : 使用方法名进行映射, 可以使用 javah 工具进入 bin/classes 目录下执行命令, 即可生成头文件;

JNI方法参数介绍:
-- 参数① : 第一个参数是JNI接口指针 JNIEnv;
-- 参数② : 如果Native方法是非静态的, 那么第二个参数就是对Java对象的引用, 如果Native方法是静态的, 那么第二个参数就是对Java类的Class对象的引用;

JNI方法名规范 : 返回值 + Java前缀 + 全路径类名 + 方法名 + 参数① JNIEnv + 参数② jobject + 其它参数;
-- 注意分隔符 : Java前缀 与 类名 以及类名之间的包名 和 方法名之间 使用 "_" 进行分割;

声明 非静态 方法:
-- Native方法 : public int hello (String str, int i);
-- JNI方法: jint Java_shuliang_han_Hello_hello(JNIEnv * env, jobject obj, jstring str, jint i);

声明 静态 方法 :
-- Native方法 :public static int hello (String str, int i);
--JNI方法 : jint Java_shuliang_han_Hello_hello(JNIEnv * env, jobject clazz, jstring str, jint i);

两种规范 : 以上是Java的标准JNI规范, 在Android中还有一套自定义的规范, 该规范是Android应用框架层 和 框架层交互使用的JNI规范, 依靠方法注册 映射 Native方法 和 JNI方法;

6. JNI方法签名规则


JNI识别Java方法 :JNI依靠函数名方法签名 识别方法, 函数名是不能唯一识别一个方法的, 因为方法可以重载, 类型签名代表了 参数 和 返回值;
-- 签名规则 : (参数1类型签名参数2类型签名参数3类型签名参数N类型签名...)返回值类型签名, 注意参数列表中没有任何间隔;

Java类型 与 类型签名对照表 : 注意 boolean 与 long 不是大写首字母, 分别是 Z 与 J, 类是L全限定类名, 数组是[元素类型签名;
-- 类的签名规则 :L + 全限定名 + ;三部分, 全限定类名以 / 分割;
Java类型 类型签名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
L全限定类名
数组 [元素类型签名


eg. long function(int n, String str, int[] arr);
该方法的签名 :(ILjava/lang/String;[I)J
.

.

四. Java调用JNI法与日志打印



1. JNI数据类型



Java数据类型 C数据类型 JNI数据类型对比 : 32位 与 64位机器可能会有出入;

Java数据类型 C本地类型 JNI定义别名
int long jint/jsize
long __int64 jlong
byte signed char jbyte
boolean unsigned char jboolean
char unsigned short jchar
short short jshort
float float jfloat
double doyble jdouble
object'
_jobject jobject

数据类型表示方法 : int数组类型 jintArray , boolean数组 jbooleanArray ...

头文件定义类型 : 这些基本的数据类型在jni.h 中都有相应的定义 :
    jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
    jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
    jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
    jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
    jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
    jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
    jbyteArray    (*NewByteArray)(JNIEnv*, jsize);
    jcharArray    (*NewCharArray)(JNIEnv*, jsize);
    jshortArray   (*NewShortArray)(JNIEnv*, jsize);
    jintArray     (*NewIntArray)(JNIEnv*, jsize);
    jlongArray    (*NewLongArray)(JNIEnv*, jsize);
    jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);
    jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);


2. JNI在Java和C语言之间传递int类型



Java中定义的方法 :
	//将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider
	public native int add(int x, int y);

C语言中定义的方法 :
#include <jni.h>

//方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数
jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
{
	return x + y;
}

使用NDK工具变异该c类库 :
在cygwin中进入cygdrive, 然后进入windows中相应的目录, 执行/android-ndk-r9c/ndk-build 命令, 即可完成编译;



3. NDK中C代码使用LogCat



(1) 引入头文件


NDK中断点调试 : 断点调试在NDK中实现极其困难, 因此在这里我们一般都是打印日志;

引入头文件 : 在C代码中引入下面的头文件;
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

头文件介绍 : log.h 是关于调用 LogCat日志文件;
-- log.h头文件路径 :android-ndk-r9c\platforms\android-9\arch-arm\usr\include\android\log.h;
-- 主要方法 : __android_log_write, 下面有该方法的解析, 传入参数 日志等级 日志标签 日志内容;
-- 宏定义 :__android_log_write 方法太麻烦, 这里做出一个映射,LOGD(...) 输出debug级别的日志,LOGI(...) 输出Info级别的日志;
--LogCat日志级别 : verbose < debug < info < warn < error < assert;

使用到的log.h文件内容解析 :__android_log_write 方法中的日志等级参数就使用 枚举中的内容
/*
 * Android log priority values, in ascending priority order. 日志等级
 */
typedef enum android_LogPriority {
    ANDROID_LOG_UNKNOWN = 0,
    ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */
    ANDROID_LOG_VERBOSE,
    ANDROID_LOG_DEBUG,
    ANDROID_LOG_INFO,
    ANDROID_LOG_WARN,
    ANDROID_LOG_ERROR,
    ANDROID_LOG_FATAL,
    ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */
} android_LogPriority;

/*
 * Send a simple string to the log. 向LogCat中输出日志 
	参数介绍: 日志优先级 , 日志标签 , 日志内容
 */
int __android_log_write(int prio, const char *tag, const char *text);

C语言中输入输出函数占位符介绍 :
占位符 数据类型
%d int
%ld long int
%c char
%f float
&lf double
%x 十六进制
%O 八进制
%s 字符串

.
.

(2) Android.mk增加liblog.so动态库


在该make配置文件中, 增加一行 :LOCAL_LDLIBS += -llog , 该语句添加在LOCAL_SRC_FILES 语句下面一行;

完整的Android.mk文件 :
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := DataProvider
LOCAL_SRC_FILES := DataProvider.c
#增加log函数对应的函数库 liblog.so  libthread_db.a
LOCAL_LDLIBS += -llog -lthread_db 
include $(BUILD_SHARED_LIBRARY)

函数库位置 :android-ndk-r9c\platforms\android-9\arch-arm\usr\lib;
函数库截图 : 从该目录下的 liglog.so可以看出, 存在该库;

引入函数库方法 : 使用LOCAL_LDLIBS += -l函数库名, 注意函数库名不带lib前缀.so 后缀, 同时可以添加多个库, 使用 -l库1 -l库2 -库3 ;


(3) 编译执行


根据(1) 中的占位符, 编写打印日志代码:
	//Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中
	LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);

最终的包含打印日志的完整代码 : 注意, 这里有一处可能错误, 如果是32位机器, int类型占位符使用 %d 即可;
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)


//方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数
jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
{
	//Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中
	LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);
	return x + y;
}

重新编译C文件 : 执行/android-ndk-r9c/ndk-build命令;
-- 第一次编译 : 出现警告, long int占位符行不通, 注意区分机器位长, 64位 与 32位不同, 这样编译出现的结果就不会打印日志;

-- 第二次编译 : 将占位符改为 %d ;


执行按钮之后打印的日志 : 虽然有乱码, 不过显示出来了;





4. 字符串处理

.

Java中的String转为C语言中的char字符串 : 下面的工具方法可以在C程序中解决这个问题;
// java中的jstring, 转化为c的一个字符数组
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
	//声明了一个字符串变量 rtn
	char* rtn = NULL;
	//找到Java中的String的Class对象
	jclass clsstring = (*env)->FindClass(env, "java/lang/String");
	//创建一个Java中的字符串 "GB2312"
	jstring strencode = (*env)->NewStringUTF(env, "GB2312");
	/*
	 * 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组
	 * "(Ljava/lang/String;)[B" 方法前面解析 :
	 * -- Ljava/lang/String; 表示参数是String字符串
	 * -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组
	 */
	jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
			"(Ljava/lang/String;)[B");
	//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数
	jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
			strencode); // String .getByte("GB2312");
	//获取数组的长度
	jsize alen = (*env)->GetArrayLength(env, barr);
	//获取数组中的所有的元素 , 存放在 jbyte*数组中
	jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
	//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0'
	if (alen > 0) {
		rtn = (char*) malloc(alen + 1); //new  char[alen+1]; "\0"
		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
	(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存


	return rtn;
}

Jstring2CStr方法讲解 :
a. 获取Java中String类型的class对象 : 参数 : 上下文环境 env, String类完整路径 ;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
b.创建Java字符串 : 使用NewStringUTF 方法;
jstring strencode = (*env)->NewStringUTF(env, "GB2312");
c.获取String中的getBytes()方法 : 参数介绍 ① env 上下文环境 ② 完整的类路径 ③ 方法名 ④ 方法签名, 方法签名Ljava/lang/String; 代表参数是String字符串,[B 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组;
	jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
			"(Ljava/lang/String;)[B");
d.获取数组的长度 :
jsize alen = (*env)->GetArrayLength(env, barr);
e. 获取数组元素 :获取数组中的所有的元素 , 存放在 jbyte*数组中;
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
f.数组拷贝:将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0';
	if (alen > 0) {
		rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"
		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
g.释放内存 :
(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存

.

作者:万境绝尘

转载请注明出处:http://blog.csdn.net/shulianghan/article/details/18964835

.



C语言方法 : 注意调用Jstring2CStr方法之后要强转, 否则会出错, Jstring2CStr方法要定义在该方法的前面, C语言中的方法要先声明才能使用;
jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)  
{  
    char *p = (char*)Jstring2CStr(env, str);  
    //打印Java传递过来的数据  
    LOGI("Java JNI string parameter is : %s", p);  
      
    char *append = "append";  
      
    //strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面  
    return (*env)->NewStringUTF(env, strcat(p, append));  
}

-- 如果没有强转会出现下面的错误 : char *p = Jstring2CStr(env, str);


-- 将Jstring2CStr方法定义在主方法下面会出现下面错误 :


Java源码 :
case R.id.sayHelloInc:  
    Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();  
    break;  

编译之后运行结果 :





5.开发JNI程序流程


a. C语言类库接口 : 存在C语言类库, 调用接口为login_server(char* address, char* username, char* password);
b.
Java定义本地方法 : public native void LoginServer(String address, String user, String pwd);
c. C语言JNI代码 : Java_包名_类名_LoginServer(JNIEnv* env, jobject obj, jstring address, jstring user, jstring pwd){...调C接口};


注意跨语言字符串转换: JNI方法中, 要将Java的String字符串转为C中的char*字符串;


首先验证C码农提供的代码是否可用 : 验证该api是否可用, 在一个 int main() 函数中进行测试, 根据该测试代码查看方法执行相关的情况;

6.数组参数处理


模块讲解 : 在该模块中, Java语言传递一个int数组参数给C语言, C语言将这一组参数读取出来, 并且输出到Android的LogCat中, 这里涉及到了两个重要的JNI方法, 一个数获取数组长度方法, 一个是获取数组中每个元素的方法;


获取数组长度方法 : jni中定义 - jsize (*GetArrayLength)(JNIEnv*, jarray);
创建数组相关方法 :
jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);  
jbyteArray    (*NewByteArray)(JNIEnv*, jsize);  
jcharArray    (*NewCharArray)(JNIEnv*, jsize);  
jshortArray   (*NewShortArray)(JNIEnv*, jsize);  
jintArray     (*NewIntArray)(JNIEnv*, jsize);  
jlongArray    (*NewLongArray)(JNIEnv*, jsize);  
jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);  
jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);  

获取数组元素相关方法 :
jboolean*   (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);  
jbyte*      (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);  
jchar*      (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);  
jshort*     (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);  
jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);  
jlong*      (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);  
jfloat*     (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);  
jdouble*    (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*); 

C语言代码 :
jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)  
{  
    //获取arr大小  
    int len = (*env)->GetArrayLength(env, arr);  
      
    //在LogCat中打印出arr的大小  
    LOGI("the length of array is %d", len);  
      
    //如果长度为0, 返回arr  
    if(len == 0)  
        return arr;  
          
    //如果长度大于0, 那么获取数组中的每个元素  
    jint* p = (*env)->GetIntArrayElements(env, arr, 0);  
      
    //打印出数组中每个元素的值  
    int i = 0;  
    for(; i < len; i ++)  
    {  
        LOGI("arr[%d] = %d", i, *(p + i));  
    }  
      
    return arr;  
      
}  


Java代码 :
case R.id.intMethod:  
    int[] array = {1, 2, 3, 4, 5};  
    dataProvider.intMethod(array);  
    break;  

执行结果 : 上面的那种LogCat竟然启动失败, 只能将就着用这个了;


7. 本程序源码


XML布局文件 :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button 
        android:id="@+id/add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用 add 本地 方法"
        android:onClick="onClick"/>
    
    <Button 
        android:id="@+id/sayHelloInc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用 sayHelloInc 本地 方法"
        android:onClick="onClick"/>
    
    <Button 
        android:id="@+id/intMethod"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用 intMethod 本地 方法"
        android:onClick="onClick"/>

</LinearLayout>

Java源码 :
-- MainActivity源码 :
package shuliang.han.ndkparameterpassing;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {

	static{
		System.loadLibrary("DataProvider");
	}
	
	DataProvider dataProvider;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dataProvider = new DataProvider();
    }

    public void onClick(View view) {
    	
    	int id = view.getId();
    	
    	switch (id) {
			case R.id.add:
				int result = dataProvider.add(1, 2);
				Toast.makeText(getApplicationContext(), "the add result : " + result, Toast.LENGTH_LONG).show();
				break;
				
			case R.id.sayHelloInc:
				Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
				break;
				
			case R.id.intMethod:
				int[] array = {1, 2, 3, 4, 5};
				dataProvider.intMethod(array);
				break;
	
			default:
				break;
		}
	}
    
}
--DataProvider源码 :
package shuliang.han.ndkparameterpassing;

public class DataProvider {

	//将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider
	public native int add(int x, int y);
	
	//将Java字符串传递给C语言, C语言处理字符串之后, 将处理结果返回给java
	public native String sayHelloInc(String s);
	
	//将java中的int数组传递给C语言, C语言为每个元素加10, 返回给Java
	public native int[] intMethod(int[] nums); 
	
}

JNI相关源码 :
-- Android.mk源码 :
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := DataProvider
LOCAL_SRC_FILES := DataProvider.c
#增加log函数对应的log库
LOCAL_LDLIBS += -llog 

include $(BUILD_SHARED_LIBRARY)
--DataProvider.c 主程序源码 :
#include <jni.h>
#include <string.h>
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

// java中的jstring, 转化为c的一个字符数组
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
	//声明了一个字符串变量 rtn
	char* rtn = NULL;
	//找到Java中的String的Class对象
	jclass clsstring = (*env)->FindClass(env, "java/lang/String");
	//创建一个Java中的字符串 "GB2312"
	jstring strencode = (*env)->NewStringUTF(env, "GB2312");
	/*
	 * 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组
	 * "(Ljava/lang/String;)[B" 方法前面解析 :
	 * -- Ljava/lang/String; 表示参数是String字符串
	 * -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组
	 */
	jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
			"(Ljava/lang/String;)[B");
	//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数
	jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
			strencode); // String .getByte("GB2312");
	//获取数组的长度
	jsize alen = (*env)->GetArrayLength(env, barr);
	//获取数组中的所有的元素 , 存放在 jbyte*数组中
	jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
	//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '\0'
	if (alen > 0) {
		rtn = (char*) malloc(alen + 1); //new  char[alen+1]; "\0"
		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
	(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存


	return rtn;
}

//方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数
jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
{
	//Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中
	LOGI("JNI_log : x = %d , y = %d" , x , y);
	return x + y;
}

jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)
{
	char *p = (char*)Jstring2CStr(env, str);
	//打印Java传递过来的数据
	LOGI("Java JNI string parameter is : %s", p);
	
	char *append = "append";
	
	//strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面
	return (*env)->NewStringUTF(env, strcat(p, append));
}

jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)
{
	//获取arr大小
	int len = (*env)->GetArrayLength(env, arr);
	
	//在LogCat中打印出arr的大小
	LOGI("the length of array is %d", len);
	
	//如果长度为0, 返回arr
	if(len == 0)
		return arr;
		
	//如果长度大于0, 那么获取数组中的每个元素
	jint* p = (*env)->GetIntArrayElements(env, arr, 0);
	
	//打印出数组中每个元素的值
	int i = 0;
	for(; i < len; i ++)
	{
		LOGI("arr[%d] = %d", i, *(p + i));
	}
	
	return arr;
	
}


.



8. 上传代码到GitHub



创建新项目 : han1202012/NDKParameterPassing ;
-- SSH地址 : git@github.com:han1202012/NDKParameterPassing.git ;
-- HTTP地址 : https://github.com/han1202012/NDKParameterPassing.git ;


五. C语言代码回调Java方法

.

C语言回调Java方法场景 :
-- 复用方法 : 使用Java对象, 复用Java中的方法;
-- 激活Java : C程序后台运行, 该后台程序一直运行, 某个时间出发后需要启动Java服务, 激活Android中的某个界面, 例如使用Intent启动一个Activity;


1. C代码回调Java方法的流程


(1) 找到java对应的Class


创建一个char*数组, 然后使用jni.h中提供的FindClass方法获取jclass返回值;
		//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
		char* classname = "shulaing/han/ndk_callback/DataProvider";


		jclass dpclazz = (*env)->FindClass(env, classname);

(2) 找到要调用的方法的methodID


使用jni.h中提供的GetMethodID方法, 获取jmethodID, 传入参数 ①JNIEnv指针 ②Class对象 ③ 方法名 ④方法签名, 在这里方法名和方法签名确定一个方法, 方法签名就是方法的返回值 与 参数的唯一标示;
		//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
		jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");

找到静态方法 : 如果方法是静态的, 就使用GetStaticMethod方法获取
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetStaticMethodID(this, clazz, name, sig); }



(3) 在C语言中调用相应方法


普通方法 : CallTypeMethod , 其中的Type随着返回值类型的不同而改变;
参数介绍 : ① JNIEnv指针 ②调用该native方法的对象 ③方法的methodID ④⑤... 后面是可变参数, 这些参数是
    jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);

    jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
    jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
    jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
    jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
    jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
    jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
    jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
    jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
    jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
    jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
    jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
    jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
    void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);

静态方法 : CallStaticTypeMethod, 其中的Type随着返回值类型不同而改变;



.

2. 一些基本代码编写


Java代码 : 定义一个callCcode本地方法, 以及三个Java方法, 在jni中使用本地方法调用Java中的方法;
package shulaing.han.ndk_callback;

public class DataProvider {

	public native void callCcode();
	
	//C调用java中空方法 shulaing.han.ndk_callback.DataProvider

	public void helloFromJava(){
		System.out.println("hello from java");
	}
	
	//C调用java中的带两个int参数的方法
	public int Add(int x,int y){
		return x + y;
	}
	
	//C调用java中参数为string的方法
	public void printString(String s){
		System.out.println(s);
	}
	
}

生成头文件 : 进入 bin/classed目录, 使用 javah shulaing.han.ndk_callback.DataProvider 命令, 可以在bin/classes下生成头文件;


头文件内容 : 文件名 :shulaing_han_ndk_callback_DataProvider.h ;
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class shulaing_han_ndk_callback_DataProvider */

#ifndef _Included_shulaing_han_ndk_callback_DataProvider
#define _Included_shulaing_han_ndk_callback_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     shulaing_han_ndk_callback_DataProvider
 * Method:    callCcode
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

编写Android.mk文件 : 注意将LogCat日志输出系统动态库加入;
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := jni
LOCAL_SRC_FILES := jni.c
#增加log函数对应的log库
LOCAL_LDLIBS += -llog 

include $(BUILD_SHARED_LIBRARY)

编写jni的C代码 : 注意加入LogCat相关导入的包;
#include "shulaing_han_ndk_callback_DataProvider.h"
#include <string.h>
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)


3. C中回调Java的void返回值方法


使用JNIEnv指针获取Class对象 : 在jni.h文件中找到 -jclass (*FindClass)(JNIEnv*, const char*);
-- 参数介绍 : 第二个参数是类的路径字符串, 如"/shuliang/han/ndk_callback/DataProvider" ;

获取Java类中定义的method方法 : 在jni.h中找到方法 -jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
-- 参数介绍 : 第二个参数是 Java类的Class对象, 第三个参数是方法名, 第四个参数是Java方法的签名;

方法签名生成工具 : javap , 使用javap -s 命令即可生成方法签名;


进入bin/classed目录下 : 执行 javap -sshulaing.han.ndk_callback.DataProvider 命令, 即可显示出每个方法的签名;
$ javap -s shulaing.han.ndk_callback.DataProvider
Compiled from "DataProvider.java"
public class shulaing.han.ndk_callback.DataProvider extends java.lang.Object{
public shulaing.han.ndk_callback.DataProvider();
  Signature: ()V
public native void callCcode();
  Signature: ()V
public void helloFromJava();
  Signature: ()V
public int Add(int, int);
  Signature: (II)I
public void printString(java.lang.String);
  Signature: (Ljava/lang/String;)V
}
截图 :



方法签名介绍 :
-- 返回值null, 参数null :void helloFromJava() 方法的签名是 "()V", 括号里什么都没有代表参数为null, V代表返回值是void;
-- 返回值int, 参数两个int :int Add(int x,int y) 方法的签名是 "(II)I", 括号中II表示两个int类型参数, 右边括号外的I代表返回值是int类型;
-- 返回值null, 参数String :void printString(String s) 方法签名是 "(Ljava/lang/String;)V", 括号中的Ljava/lang/String; 表示参数是String类型, V表示返回值是void;

jni.h中定义的回调Java方法的相关函数 :
    jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);

    jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
    jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
    jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
    jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
    jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
    jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
    jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
    jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
    jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
    jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
    jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
    jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
    void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);

C语言代码 :
#include "shulaing_han_ndk_callback_DataProvider.h"
#include <string.h>
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  (JNIEnv * env, jobject obj)
{
	//调用DataProvider对象中的helloFromJava()方法
	//获取到某个对象, 获取对象中的方法, 调用获取到的方法
	LOGI("in code");
	//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
	char* classname = "shulaing/han/ndk_callback/DataProvider";


	jclass dpclazz = (*env)->FindClass(env, classname);
	if(dpclazz == 0)
		LOGI("class not find !!!");
	else
		LOGI("class find !!!");

	//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
	jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
	if(methodID == 0)
			LOGI("method not find !!!");
		else
			LOGI("method find !!!");

	/*
	 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
	 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
	 */
	LOGI("before call method");
	(*env)->CallVoidMethod(env, obj, methodID);
	LOGI("after call method");

}

Java代码 :
--XML布局文件代码 :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/call_void_method"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="C语言回调Java中的空方法" />

</LinearLayout>
--MainActivity代码 :
package shulaing.han.ndk_callback;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

	static{
		System.loadLibrary("jni");
	}
	DataProvider dp;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		dp = new DataProvider();
	}

	public void onClick(View view) {
		int id = view.getId();
		switch (id) {
			case R.id.call_void_method:
				dp.callCcode();
				break;
	
			default:
				break;
		}
	}

}

执行结果 :


.

4. C代码回调Java中带String参数的方法


在DataProvider中添加两个native方法 :
	public native void callCcode();
	public native void callCcode1();
	public native void callCcode2();

进入bin/classes目录, 使用javah -jni shulaing.han.ndk_callback.DataProvider 命令生成头文件 :
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class shulaing_han_ndk_callback_DataProvider */

#ifndef _Included_shulaing_han_ndk_callback_DataProvider
#define _Included_shulaing_han_ndk_callback_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     shulaing_han_ndk_callback_DataProvider
 * Method:    callCcode
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  (JNIEnv *, jobject);

/*
 * Class:     shulaing_han_ndk_callback_DataProvider
 * Method:    callCcode1
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
  (JNIEnv *, jobject);

/*
 * Class:     shulaing_han_ndk_callback_DataProvider
 * Method:    callCcode2
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

jni C语言代码 : 这里只需要修改两处, 方法名, 获取方法id中的参数, 调用方法中最后加上一个Java参数;
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
  (JNIEnv *env, jobject obj)
{
	//调用DataProvider对象中的helloFromJava()方法
		//获取到某个对象, 获取对象中的方法, 调用获取到的方法
		LOGI("in code");
		//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
		char* classname = "shulaing/han/ndk_callback/DataProvider";


		jclass dpclazz = (*env)->FindClass(env, classname);
		if(dpclazz == 0)
			LOGI("class not find !!!");
		else
			LOGI("class find !!!");

		//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
		jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
		if(methodID == 0)
				LOGI("method not find !!!");
			else
				LOGI("method find !!!");

		/*
		 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
		 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
		 */
		LOGI("before call method");
		(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
		LOGI("after call method");
}

执行后的结果 :


5. C代码中回调带两个int类型的参数的方法


按照上面的流程, 不同之处就是jni中获取方法 和 方法id , 调用方法的jni函数不同 :
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
  (JNIEnv *env, jobject obj)
{
	//调用DataProvider对象中的helloFromJava()方法
		//获取到某个对象, 获取对象中的方法, 调用获取到的方法
		LOGI("in code");
		//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
		char* classname = "shulaing/han/ndk_callback/DataProvider";


		jclass dpclazz = (*env)->FindClass(env, classname);
		if(dpclazz == 0)
			LOGI("class not find !!!");
		else
			LOGI("class find !!!");

		//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
		jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
		if(methodID == 0)
				LOGI("method not find !!!");
			else
				LOGI("method find !!!");

		/*
		 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
		 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
		 */
		LOGI("before call method");
		(*env)->CallIntMethod(env, obj, methodID, 3, 5);
		LOGI("after call method");

}

Java代码 :
			case R.id.call_int_parameter_method:
				dp.callCcode2();
				break;
执行结果 :

.

作者:万境绝尘

转载请注明出处:http://blog.csdn.net/shulianghan/article/details/18964835

.


6. 完整源码


Java源码 :
-- DataProvider源码 :
package shulaing.han.ndk_callback;


public class DataProvider {

	public native void callCcode();
	public native void callCcode1();
	public native void callCcode2();
	
	//C调用java中空方法 shulaing.han.ndk_callback.DataProvider

	public void helloFromJava(){
		System.out.println("hello from java");
	}
	
	//C调用java中的带两个int参数的方法
	public int Add(int x,int y){
		System.out.println("the add result is : " + (x + y));
		return x + y;
	}
	
	//C调用java中参数为string的方法
	public void printString(String s){
		System.out.println("in java code :" + s);
	}
	
}
-- MainActivity源码 :
package shulaing.han.ndk_callback;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

	static{
		System.loadLibrary("jni");
	}
	DataProvider dp;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		dp = new DataProvider();
	}

	public void onClick(View view) {
		int id = view.getId();
		switch (id) {
			case R.id.call_void_method:
				dp.callCcode();
				break;
			
			case R.id.call_string_parameter_method:
				dp.callCcode1();
				break;
	
			case R.id.call_int_parameter_method:
				dp.callCcode2();
				break;
				
			default:
				break;
		}
	}

}

XML布局文件源码 :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/call_void_method"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="C语言回调Java中的空方法" />
    
    <Button
        android:id="@+id/call_string_parameter_method"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="C语言回调Java中的String参数方法" />
    
    <Button
        android:id="@+id/call_int_parameter_method"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="C语言回调Java中的int参数方法" />

</LinearLayout>


jni源码 :
-- 头文件源码 :
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class shulaing_han_ndk_callback_DataProvider */

#ifndef _Included_shulaing_han_ndk_callback_DataProvider
#define _Included_shulaing_han_ndk_callback_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     shulaing_han_ndk_callback_DataProvider
 * Method:    callCcode
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  (JNIEnv *, jobject);

/*
 * Class:     shulaing_han_ndk_callback_DataProvider
 * Method:    callCcode1
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
  (JNIEnv *, jobject);

/*
 * Class:     shulaing_han_ndk_callback_DataProvider
 * Method:    callCcode2
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
-- Android.mk源码 :
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := jni
LOCAL_SRC_FILES := jni.c
#增加log函数对应的log库
LOCAL_LDLIBS += -llog 

include $(BUILD_SHARED_LIBRARY)
-- jni主程序源码 :
#include "shulaing_han_ndk_callback_DataProvider.h"
#include "first.h"
#include <string.h>
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
  (JNIEnv * env, jobject obj)
{
	//调用DataProvider对象中的helloFromJava()方法
	//获取到某个对象, 获取对象中的方法, 调用获取到的方法
	LOGI("in code");
	//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
	char* classname = "shulaing/han/ndk_callback/DataProvider";


	jclass dpclazz = (*env)->FindClass(env, classname);
	if(dpclazz == 0)
		LOGI("class not find !!!");
	else
		LOGI("class find !!!");

	//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
	jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
	if(methodID == 0)
			LOGI("method not find !!!");
		else
			LOGI("method find !!!");

	/*
	 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
	 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
	 */
	LOGI("before call method");
	(*env)->CallVoidMethod(env, obj, methodID);
	LOGI("after call method");

}

JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
  (JNIEnv *env, jobject obj)
{
	//调用DataProvider对象中的helloFromJava()方法
		//获取到某个对象, 获取对象中的方法, 调用获取到的方法
		LOGI("in code");
		//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
		char* classname = "shulaing/han/ndk_callback/DataProvider";


		jclass dpclazz = (*env)->FindClass(env, classname);
		if(dpclazz == 0)
			LOGI("class not find !!!");
		else
			LOGI("class find !!!");

		//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
		jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
		if(methodID == 0)
				LOGI("method not find !!!");
			else
				LOGI("method find !!!");

		/*
		 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
		 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
		 */
		LOGI("before call method");
		(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
		LOGI("after call method");
}

/*
 * 实际开发的情况
 * C代码工程师给我们 first.h first.c , 我们只需要将first.h引入, 然后就可以使用其中的方法了
 */
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
  (JNIEnv *env, jobject obj)
{
	//调用DataProvider对象中的helloFromJava()方法
		//获取到某个对象, 获取对象中的方法, 调用获取到的方法
		LOGI("in code");
		//DataProvider完整类名 shulaing.han.ndk_callback.DataProvider
		char* classname = "shulaing/han/ndk_callback/DataProvider";


		jclass dpclazz = (*env)->FindClass(env, classname);
		if(dpclazz == 0)
			LOGI("class not find !!!");
		else
			LOGI("class find !!!");

		//参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method
		jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
		if(methodID == 0)
				LOGI("method not find !!!");
			else
				LOGI("method find !!!");

		/*
		 * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
		 * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列
		 */
		LOGI("before call method");
		(*env)->CallIntMethod(env, obj, methodID, 3, 5);
		LOGI("after call method");

}

7. 将程序上传到GitHub中

GitHub地址 :
-- SSH :git@github.com:han1202012/NDK_Callback.git
-- HTTP :https://github.com/han1202012/NDK_Callback.git
.


.

六. 实际开发中的环境


这里举一个简单的小例子 :
-- 在实际开发中, C工程师会给我们c文件如下 : first.h first.c, 一个C主程序, 一个头文件, 我们只需要将这个头文件引入到jni中的C代码中即可, 在我们自定义生成的签名函数中调用 first.h中的方法;

first.h源码 :
#ifndef FIRST_H
#define FIRST_H

extern int first(int  x, int  y);

#endif /* FIRST_H */

first.c源码 :
#include "first.h"

int  first(int  x, int  y)
{
    return x + y;
}

在签名函数中, 直接调用 first()方法即可
;

.

七 分析Log日志系统框架的JNI代码



在这里分析日志输出函数 : Log.i(TAG, "log"), 分析该日志系统的JNI层源码结构;

这里使用下载的Android2.3.3源码进行分析 : 在http://blog.csdn.net/shulianghan/article/details/17350401中介绍了如何使用repo 和 git 下载Android源码 和 kernel 源码;

LogJNI调用层次 : android.util.Log.java 中的接口 是通过JNI调用 本地库 并最终调用内核驱动程序 Logger 将日志信息写到 内核空间中.

分析的源码文件 : "\" 代表Android源代码的本目录;
-- Java代码 :\frameworks\base\core\java\android\util\Log.java
-- JNI层实现代码 :\frameworks\base\core\jni\android_util_Log.cpp
下面的是Android自定义的JNI规范相关的源码 :
-- JNI规范头文件 :\dalvik\libnativehelper\include\nativehelper\jni.h
-- JNI帮助文件 : ①\dalvik\libnativehelper\include\nativehelper\JNIHelp.h ②\dalvik\libnativehelper\JNIHelp.c
-- JNI运行时文件 : \frameworks\base\core\jni\AndroidRuntime.cpp

这里将上面几个文件上传到CSDN资源中, 便于查看 :http://download.csdn.net/detail/han1202012/6905507;

1. 分析Log.java源码


Log.java分析 : 在Log.java文件中,定义了 isLoggable 和 println_native 两个Native方法, 在Java方法中, 只需要事先声明native方法, 不用实现方法体, 可以直接调用;
Log.java在Android源码中的位置 :\frameworks\base\core\java\android\util\Log.java

Log.java内容 :
package android.util;

import com.android.internal.os.RuntimeInit;

import java.io.PrintWriter;
import java.io.StringWriter;
public final class Log {

	... ...
	
	//打印日志
    public static int d(String tag, String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    }

    //打印日志和异常
    public static int d(String tag, String msg, Throwable tr) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
    }

    //打印日志
    public static int i(String tag, String msg) {
        return println_native(LOG_ID_MAIN, INFO, tag, msg);
    }

	... ...
	
    //声明native方法
    public static native boolean isLoggable(String tag, int level);

	... ...
  
    /** @hide */ public static final int LOG_ID_MAIN = 0;
    /** @hide */ public static final int LOG_ID_RADIO = 1;
    /** @hide */ public static final int LOG_ID_EVENTS = 2;
    /** @hide */ public static final int LOG_ID_SYSTEM = 3;

	//声明native方法
    /** @hide */ public static native int println_native(int bufID,
            int priority, String tag, String msg);
}

2. 分析Log系统JNI层源码


JNI层方法: JNI层方法根据一定规则与Java层声明的Native方法进行映射, 然后可以通过JNIEnv指针提供的JNI函数对Java层进行操作;
Log系统的JNI层文件是 : android_util_Log.cpp, 该文件路径 :\frameworks\base\core\jni\android_util_Log.cpp代码如下 :
#define LOG_NAMESPACE "log.tag."
#define LOG_TAG "Log_println"

#include <assert.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/String8.h>

#include "jni.h"
#include "utils/misc.h"
#include "android_runtime/AndroidRuntime.h"

... ...

namespace android {

struct levels_t {
    jint verbose;
    jint debug;
    jint info;
    jint warn;
    jint error;
    jint assert;
};
static levels_t levels;

static int toLevel(const char* value) 
{
    switch (value[0]) {
        case 'V': return levels.verbose;
        case 'D': return levels.debug;
        case 'I': return levels.info;
        case 'W': return levels.warn;
        case 'E': return levels.error;
        case 'A': return levels.assert;
        case 'S': return -1; // SUPPRESS
    }
    return levels.info;
}

/*
	实现Java层声明的 isLoggable 方法, 注意方法名不符合标准JNI规范
	标准的JNI规范方法名应该是 Java_包名_类名_方法名
	其中传入了JNIEnv 和 jobject 参数, JNIEnv参数是Java运行环境, 可以与JVM进行交互
	jobject参数是包含Native方法的Java类对象
	该方法中可以通过JNIEnv调用本地库进行函数处理, 最后返回给Java层函数
*/
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
#ifndef HAVE_ANDROID_OS
    return false;
#else /* HAVE_ANDROID_OS */
    int len;
    char key[PROPERTY_KEY_MAX];
    char buf[PROPERTY_VALUE_MAX];

    if (tag == NULL) {
        return false;
    }
    
    jboolean result = false;
    
	//调用了JNI函数
    const char* chars = env->GetStringUTFChars(tag, NULL);

    if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
        jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
        char buf2[200];
        snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
                chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));

        // release the chars!
        env->ReleaseStringUTFChars(tag, chars);

        env->ThrowNew(clazz, buf2);
        return false;
    } else {
        strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
        strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
    }
    
    env->ReleaseStringUTFChars(tag, chars);

    len = property_get(key, buf, "");
    int logLevel = toLevel(buf);
    return (logLevel >= 0 && level >= logLevel) ? true : false;
#endif /* HAVE_ANDROID_OS */
}

/*
 * In class android.util.Log:
 *  public static native int println_native(int buffer, int priority, String tag, String msg)
 */
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
    const char* tag = NULL;
    const char* msg = NULL;

    if (msgObj == NULL) {
        jclass npeClazz;

        npeClazz = env->FindClass("java/lang/NullPointerException");
        assert(npeClazz != NULL);

        env->ThrowNew(npeClazz, "println needs a message");
        return -1;
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
        jclass npeClazz;

        npeClazz = env->FindClass("java/lang/NullPointerException");
        assert(npeClazz != NULL);

        env->ThrowNew(npeClazz, "bad bufID");
        return -1;
    }

    if (tagObj != NULL)
        tag = env->GetStringUTFChars(tagObj, NULL);	//调用JNI函数
    msg = env->GetStringUTFChars(msgObj, NULL);

    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

    if (tag != NULL)
        env->ReleaseStringUTFChars(tagObj, tag);	//调用JNI函数释放资源
    env->ReleaseStringUTFChars(msgObj, msg);	//调用JNI函数释放资源

    return res;
}

/*
 * JNI registration. JNI方法注册
 */
static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};

int register_android_util_Log(JNIEnv* env)
{
    jclass clazz = env->FindClass("android/util/Log");

    if (clazz == NULL) {
        LOGE("Can't find android/util/Log");
        return -1;
    }
    
    levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
    levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
    levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
    levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
    levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
    levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
                
    return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}

}; // namespace android

3. 声明JNI 与 Native 方法的映射关系


标准JNI规范 : 在标准的JNI规范中, Java中的Native方法 与 JNI层方法 是通过方法名的对应关系进行映射的, 我们通过 javah 工具生成JNI层头文件, 头文件中定义了规范的JNI层方法名, 这个方法名就与Java Native方法对应;

Android自定义规范 : 在\dalvik\libnativehelper\include\nativehelper\jni.h 中定义了这样的映射关系 :
typedef struct {
    const char* name;		//Java层Native函数方法名
    const char* signature;	//Java层Native函数的签名
    void*       fnPtr;		//JNI层实现的方法
} JNINativeMethod;
.
JNINativeMethod类型数据 : 在android_util_Log.cpp 中定义了一个该类型的数组 :
/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};

JNINativeMethod结构体作用 : JNINativeMethod是一个结构体类型, 声明了Native方法 与 JNI方法 的映射关系;
-- 解析上面的数组中的元素 :
--- Native方法 : "isLoggable" 是Java中声明的Native方法;
--- 方法签名 : "(Ljava/lang/String;I)Z" 表示该方法的签名, 参数是String类型 和 int类型, Z 表示 boolean类型;
--- JNI方法 : (void*) android_util_Log_isLoggable 表示JNI层实现的方法指针;

4. 注册JNI方法到虚拟机中

映射关系体现到虚拟机中 : 在android_util_Log.cpp 中存在这样的方法 :
int register_android_util_Log(JNIEnv* env)
{
    jclass clazz = env->FindClass("android/util/Log");

    if (clazz == NULL) {
        LOGE("Can't find android/util/Log");
        return -1;
    }
    
    levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
    levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
    levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
    levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
    levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
    levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
                
    return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}

}; // namespace android

核心方法 : 该函数调用了AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods)) 方法注册JNI方法;

register_android_util_Log调用时机 : 该函数是在Android系统启动的时候, 通过AndroidRuntime.cpp中的register_jni_proocs方法执行, 执行该方法的时候会将 Native方法 与 JNI方法 的函数映射关系注册给 Dalvik 虚拟机;


5. 解析registerNativeMethod函数


该函数定义在AndroidRuntime.cpp中 : 该文件的路径在\frameworks\base\core\jni\AndroidRuntime.cpp ;
/*
 * Register native methods using JNI.
 */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

registerNativeMethods 方法只是对jniRegisterNativeMethods 方法的封装, 在JNIHelp.h中找到该方法的声明:
/*
 * Register one or more native methods with a particular class.
 */
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods);

在JNIHelp.c 中找到该方法的实现 : 最终方法中调用了 JNIEnv 的RegisterNatives 函数, 将gMethods中存放的JNINativeMethod结构体(存放Native方法 与 JNI方法关联信息) 传递到java虚拟机;
/*
 * Register native JNI-callable methods.
 *
 * "className" looks like "java/lang/String".
 */
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    LOGV("Registering %s natives\n", className);
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'\n", className);
        return -1;
    }

    int result = 0;
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'\n", className);
        result = -1;
    }

    (*env)->DeleteLocalRef(env, clazz);
    return result;
}


6. JNI的规范


Android中JNI存在两种规范 : 一种是标准的JNI规范, 多在应用层使用; 另一种是Android中自定义的规范, 多使用在应用框架层;
-- JNI标准规范: 遵守JNI标准规函数命名方式, JNI中方法命名为 Java_包名_类名_方法名 , 可以使用javah生成签名头文件, 靠这种方式实现 Native方法 与 JNI方法之间的映射关系, 即应用直接与框架层进行交互, 这种规范常用与应用开发;
-- 函数注册规范 : 这是Android自定义的一种规范, 应用框架层采用该规范, 即应用框架层 与 框架层 进行交互, 底层源码开发多使用该规范;

.

作者:万境绝尘

转载请注明出处:http://blog.csdn.net/shulianghan/article/details/18964835

.

分享到:
评论

相关推荐

    海尔智能电视刷机数据 U49A5 机编DH1W80A0305 务必确认机编一致 强制刷机 整机USB升级主程序

    务必确认机身编号与文件名机编一致,如不一致,请勿下载 机身编号一般在机子背面的贴纸上 升级方法: 1、下载数据,压缩包解压,将“Haier638Upgrade.bin”文件拷贝到U盘根目录下(U盘要求使用FAT32格式,建议4G-8G的品牌U盘,刷机成功率会高) 2、电视关机拔下电源,插入U盘,按住机身按键板上的“菜单”键不放,插电开机,直到LED灯开始闪表示升级正在进行,升级成功后机器会自动重起。 3、重启之后,重新交流上电,升级完成。 注意: 1、升级到结束,大约需要8-30分钟,中途绝对不能断电 2、升级重启第一次进入系统,请等完全正常进入开机桌面之后,才能拨下U盘

    c语言c++项目源代码_c语言对自己电脑系统测试.rar

    **C语言电脑系统测试项目** 本项目是一个基于C语言的实用工具,旨在为您的电脑系统提供全面的性能测试与评估。通过执行一系列精心设计的测试用例,该工具能够评估您的电脑在处理器速度、内存管理、磁盘性能以及网络连接等方面的表现。 项目的核心功能包括: 1. **处理器性能测试**:通过执行复杂的算法和计算任务,评估处理器的运算速度和效率。 2. **内存测试**:检查内存分配、读写速度和稳定性,确保系统的内存管理达到最佳状态。 3. **磁盘性能测试**:评估硬盘或固态硬盘的读写速度、I/O性能和文件系统效率。 4. **网络性能测试**:测量网络连接的速度和稳定性,包括上传和下载速度以及延迟。 此外,该项目还提供了详细的测试报告功能,帮助您全面了解系统的优势和潜在瓶颈。测试结果以直观易懂的图表和数字形式呈现,便于分析和解读。 此项目采用模块化设计,方便您进行二次开发和定制,以满足特定需求。无论是硬件评测爱好者还是系统管理员,都能从该项目中受益良多。

    pypy3.8-v7.3.6rc3-s390x.tar.bz2

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    强化学习的Q-Learn算法ppt资源

    强化学习的主要算法:包括Q-learning、SARSA、DQN、A3C、TRPO、PPO和SAC等。这些算法各有特点,适用于不同的场景和任务。例如,Q-learning和SARSA是基于值函数的强化学习算法,旨在学习最优策略以最大化累积奖励;而DQN则是深度强化学习算法,使用神经网络来估计值函数,并通过反向传播算法更新网络参数。 强化学习在多个领域具有广泛应用。在自动驾驶系统中,强化学习可以帮助车辆感知周围环境并做出决策,实现自主行驶。在医疗领域,强化学习可以用于辅助医生进行病例分析、诊断和治疗方案制定,提高医疗服务的准确性和效率。此外,强化学习还在智能物流和仓储管理、金融投资决策等领域发挥着重要作用。

    工作汇报 年终总结28.pptx

    封面 标题:基于物联网的智能家居系统年度总结 报告人信息:[姓名]、[职位/角色]、[所属机构/公司] 日期:[具体日期] 目录 引言 年度工作回顾 系统进展与亮点 技术创新与应用 市场反馈与用户评价 存在问题与挑战 未来展望与计划 结束语与感谢 一、引言 简要介绍智能家居系统的重要性和发展趋势 回顾本年度的工作目标和重点 二、年度工作回顾 系统建设与维护 完成的项目与里程碑 系统稳定性与可靠性提升 团队建设与培训 团队成员构成与职责 培训与技能提升活动 合作伙伴与资源整合 与供应商、合作伙伴的合作情况 资源整合与利用 三、系统进展与亮点 功能扩展与优化 新增功能介绍与效果评估 现有功能的优化与改进 用户体验提升 界面设计与交互优化 用户反馈与改进措施 四、技术创新与应用 物联网技术的应用 传感器与通信技术的升级 大数据分析与应用 智能家居的智能化管理 自动化控制与节能策略 安全防护与预警系统 五、市场反馈与用户评价 市场反馈分析 市场需求与竞争态势 市场占有率与增长趋势 用户评价总结 用户满意度调查结果

    tensorflow-2.6.2-cp36-cp36m-manylinux2010-x86-64.whl

    numpy安装

    pypy3.8-v7.3.6rc2-aarch64.tar.bz2

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    pypy2.7-v7.3.12-s390x.tar.bz2

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    进制转换器,支持对有符号数进行转换

    进制转换器,支持对有符号数进行转换。可以在10进制数、16进制数、2进制数之间相互转换。 亮点:16进制数/2进制数可转换为有符号数整型。

    SV660N系列伺服通讯手册-CN-C01.PDF

    伺服

    pypy3.9-v7.3.9-src.tar.bz2

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    全国企业家活动日ppt模板x.pptx

    全国企业家活动日ppt模板x.pptx

    pypy2.7-v7.3.3-s390x.tar.bz2

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    2024生态环境保护主题活动宣传服务项目方案ss.pptx

    2024生态环境保护主题活动宣传服务项目方案ss.pptx

    国际知名家居品牌整合营销全案ss.pptx

    国际知名家居品牌整合营销全案ss.pptx

    c语言c++项目源代码_c&c++课程设计KTV歌曲系统,学生档案管理系统,个人收支系统,职工管理系统等.rar

    **C/C++课程设计项目合集** 本次提供的C/C++课程设计项目包括:KTV歌曲系统、学生档案管理系统、个人收支系统以及职工管理系统。这些项目不仅适用于课程设计,还可作为实际应用的初步探索。每个项目均经过精心设计和测试,确保代码质量和功能的完整性。 * **KTV歌曲系统**:实现了歌曲的增删改查、播放控制以及用户管理等功能,适用于各类KTV场所。 * **学生档案管理系统**:提供学生信息的录入、查询、修改和删除功能,帮助教育机构高效管理学生档案。 * **个人收支系统**:以用户为中心,记录日常收入和支出,并生成详细的统计报告,方便个人理财。 * **职工管理系统**:针对企业需求,对职工信息进行集中管理,支持多条件查询和报表生成。 这些项目采用C/C++编程语言,基于面向对象的编程思想,充分利用了现代软件工程的技术和方法。代码结构清晰,注释详细,易于理解和维护。同时,为了满足不同用户的需求,项目提供了灵活的定制选项,可以根据实际需求进行二次开发。

    iOS自动化脚本:用于构建iOS应用并部署到TestFlight或本地设备

    使用方法: 将脚本保存为iOSAppBuildAndDeploy.sh文件。 将YourProject.xcodeproj替换为你的Xcode项目文件路径。 将YourScheme替换为你的Xcode项目的Scheme名称。 创建一个ExportOptions.plist文件,并配置导出选项(例如,方法、团队ID等),然后指定其路径到EXPORT_OPTIONS_PLIST变量。 打开终端,导航到包含该脚本的目录。 给脚本执行权限:chmod +x iOSAppBuildAndDeploy.sh。 运行脚本:./iOSAppBuildAndDeploy.sh。 注意事项: 确保你的Mac上已经安装了Xcode和相应的命令行工具。 根据你的需求,你可能需要修改或扩展脚本,例如添加上传IPA到TestFlight或其他分发渠道的代码。 脚本中的ExportOptions.plist文件是Xcode构建过程中用于配置导出选项的关键文件。你需要根据你的应用分发需求来创建和配置这个文件。 如果脚本执行过程中遇到错误,请检查Xcode构建日志和脚本输出信息,以便定位问题。

    2000-2021年各省产业集聚度(就业密度)数据(含原始数据+计算结果).xlsx

    2000-2021年各省产业集聚度/就业密度数据 1、时间:2000-2021年 2、来源:人口就业统计年鉴、各省年鉴 3、指标:就业人数、行政区划面积、产业集聚度/就业密度 4、范围:31省 5、计算说明:产业集聚度/就业密度=就业人数/行政区划面积

    黄糊精和白糊精,全球前10强生产商排名及市场份额.pdf

    QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。 邮箱:market@qyresearch.com

    高级网络人才培训专家-X00070002 第29章 配置HDLC

    高级网络人才培训专家_X00070002 第29章 配置HDLC

Global site tag (gtag.js) - Google Analytics