• 【转】 Android 开发 之 JNI入门


    原文网址:http://blog.csdn.net/shulianghan/article/details/18964835

    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 目录, 使用 javah shuliang.han.ndkparameterpassing.DataProvider 命令, 便生成了头文件, 该头文件引用了 jni.h, 以及定义好了 对应的 Native 方法, 生成 JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add (JNIEnv *, jobject, jint, jint);  

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

    [plain] view plaincopy
     
    1. LOCAL_PATH := $(call my-dir)    
    2.     
    3. include $(CLEAR_VARS)    
    4.     
    5. LOCAL_MODULE    := hello-jni    
    6. LOCAL_SRC_FILES := hello-jni.c    
    7.     
    8. 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 ;

    执行结果 :

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <span style="font-family: 'Courier New';">Android NDK: Could not find application project directory !  
    2. Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.  
    3. /android-ndk-r9c/build/core/build-local.mk:148: *** Android NDK: Aborting    。 停止。</span>  

    三. 开发第一个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示例 : 

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := hello-jni  
    6. LOCAL_SRC_FILES := hello-jni.c  
    7.   
    8. 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 ;
    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <uses-sdk  
    2.     android:minSdkVersion="7"  
    3.     android:targetSdkVersion="10" />  


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

    (2) 声明native方法

     
    声明native方法, 注意该方法没有方法体 和 参数, 如下 :
     
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * 声明一个native方法 
    3.  * 这个方法在Java中是没有实现的, 没有方法体 
    4.  * 该方法需要使用C语言编写 
    5.  */  
    6. public native String helloFromJNI();  

    .

    作者 : 万境绝尘 

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

    .

    (3) 创建C文件

     
    引入头文件: 首先要包含头文件 jni.h, 该头文件位置定义在 android-ndk-r9cplatformsandroid-5arch-armusrinclude目录下的 jni.h, 下面是该头文件中定义的一些方法, 包括本项目中使用的 NewString 方法;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jstring     (*NewString)(JNIEnv*, const jchar*, jsize);  
    2. jsize       (*GetStringLength)(JNIEnv*, jstring);  
    3. const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);  
    4. void        (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);  
    5. jstring     (*NewStringUTF)(JNIEnv*, const char*);  
    6. 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语言文件源码 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <jni.h>  
    2.   
    3. /* 
    4.  * 方法名称规定 : Java_完整包名类名_方法名() 
    5.  * JNIEnv 指针 
    6.  * 
    7.  * 参数介绍 : 
    8.  * env : 代表Java环境, 通过这个环境可以调用Java中的方法 
    9.  * thiz : 代表调用JNI方法的对象, 即MainActivity对象 
    10.  */  
    11. jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)  
    12. {  
    13.     /* 
    14.      * 调用 android-ndk-r9cplatformsandroid-8arch-armusrinclude 中jni.h中的方法 
    15.      * jni.h 中定义的方法  jstring (*NewStringUTF)(JNIEnv*, const char*);  
    16.      */  
    17.     return (*env)->NewStringUTF(env, "hello world jni");  
    18. }  
     
     

    (4) 编写Android.mk文件

     
     
    查询NDK文档 : NDK的文档在NDK工具根目录下, 点击 documentation.html 文件, 就可以在浏览器中打开NDK文档;
     
    上面的开发流程中详细的介绍了Android.mk 五个参数的详细用处, 这里直接给出源码 : 
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := hello  
    6. LOCAL_SRC_FILES := hello.c  
    7.   
    8. 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 作为动态库名称, 这是规定的;
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. //静态代码块加载C语言库文件  
    2. static{  
    3.     System.loadLibrary("hello");  
    4. }  
     
     

    (7) 其它源码

     
    MainActivity源码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package shuliang.han.ndkhelloworld;  
    2.   
    3. import android.app.Activity;  
    4. import android.os.Bundle;  
    5. import android.view.View;  
    6. import android.widget.Toast;  
    7.   
    8. public class MainActivity extends Activity {  
    9.   
    10.     //静态代码块加载C语言库文件  
    11.     static{  
    12.         System.loadLibrary("hello");  
    13.     }  
    14.       
    15.     /* 
    16.      * 声明一个native方法 
    17.      * 这个方法在Java中是没有实现的, 没有方法体 
    18.      * 该方法需要使用C语言编写 
    19.      */  
    20.     public native String helloFromJNI();  
    21.       
    22.     @Override  
    23.     protected void onCreate(Bundle savedInstanceState) {  
    24.         super.onCreate(savedInstanceState);  
    25.         setContentView(R.layout.activity_main);  
    26.         System.out.println(helloFromJNI());  
    27.     }  
    28.   
    29.     public void onClick(View view) {  
    30.         //点击按钮显示从jni调用得到的字符串信息  
    31.         Toast.makeText(getApplicationContext(), helloFromJNI(), 1).show();  
    32.     }  
    33.       
    34. }  

    XML布局文件 : 
    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:paddingBottom="@dimen/activity_vertical_margin"  
    6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
    7.     android:paddingRight="@dimen/activity_horizontal_margin"  
    8.     android:paddingTop="@dimen/activity_vertical_margin"  
    9.     tools:context=".MainActivity" >  
    10.   
    11.     <Button  
    12.         android:id="@+id/bt"  
    13.         android:layout_width="wrap_content"  
    14.         android:layout_height="wrap_content"  
    15.         android:onClick="onClick"  
    16.         android:text="显示JNI返回的字符串" />  
    17.   
    18. </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
     
    生成的命令 : 
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. touch README.md  
    2. git init  
    3. git add README.md  
    4. git commit -m "first commit"  
    5. git remote add origin git@github.com:han1202012/NDKHelloworld.git  
    6. 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文件内容 : 
     
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := hello  
    6. LOCAL_SRC_FILES := hello.c  
    7.   
    8. 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;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>  
    3. /* Header for class shuliang_han_ndkparameterpassing_DataProvider */  
    4.   
    5. #ifndef _Included_shuliang_han_ndkparameterpassing_DataProvider  
    6. #define _Included_shuliang_han_ndkparameterpassing_DataProvider  
    7. #ifdef __cplusplus  
    8. extern "C" {  
    9. #endif  
    10. /* 
    11.  * Class:     shuliang_han_ndkparameterpassing_DataProvider 
    12.  * Method:    add 
    13.  * Signature: (II)I 
    14.  */  
    15. JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add  
    16.   (JNIEnv *, jobject, jint, jint);  
    17.   
    18. /* 
    19.  * Class:     shuliang_han_ndkparameterpassing_DataProvider 
    20.  * Method:    sayHelloInc 
    21.  * Signature: (Ljava/lang/String;)Ljava/lang/String; 
    22.  */  
    23. JNIEXPORT jstring JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc  
    24.   (JNIEnv *, jobject, jstring);  
    25.   
    26. /* 
    27.  * Class:     shuliang_han_ndkparameterpassing_DataProvider 
    28.  * Method:    intMethod 
    29.  * Signature: ([I)[I 
    30.  */  
    31. JNIEXPORT jintArray JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod  
    32.   (JNIEnv *, jobject, jintArray);  
    33.   
    34. #ifdef __cplusplus  
    35. }  
    36. #endif  
    37. #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文件如下 : 只改变了返回的字符串, 添加了中文;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <jni.h>  
    2.   
    3. /* 
    4.  * 方法名称规定 : Java_完整包名类名_方法名() 
    5.  * JNIEnv 指针 
    6.  * 
    7.  * 参数介绍 : 
    8.  * env : 代表Java环境, 通过这个环境可以调用Java中的方法 
    9.  * thiz : 代表调用JNI方法的对象, 即MainActivity对象 
    10.  */  
    11. jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)  
    12. {  
    13.     /* 
    14.      * 调用 android-ndk-r9cplatformsandroid-8arch-armusrinclude 中jni.h中的方法 
    15.      * jni.h 中定义的方法  jstring (*NewStringUTF)(JNIEnv*, const char*); 
    16.      */  
    17.     return (*env)->NewStringUTF(env, "hello world jni 中文");  
    18. }  

    使用NDK重新编译hello.c文件 : 修改了C源码之后, 重新将该c文件编译成so文件;
    -- 编译过程: 打开cygwin, 进入cygdrive/ 下对应windows中源码项目中的jni目录, 执行 /android-ndk-r9c/ndk-build 命令;
     
     
    运行Android代码报错 : 因为jni中c文件有中文, 中文不能被识别;
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. 01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0  
    2. 01-31 14:36:04.803: W/dalvikvm(389):              string: 'hello world jni ????'  
    3. 01-31 14:36:04.803: W/dalvikvm(389):              in Lshuliang/han/ndkhelloworld/MainActivity;.helloFromJNI ()Ljava/lang/String; (NewStringUTF)  
    4. 01-31 14:36:04.834: I/dalvikvm(389): "main" prio=5 tid=1 NATIVE  
    5. 01-31 14:36:04.834: I/dalvikvm(389):   | group="main" sCount=0 dsCount=0 obj=0x4001f1a8 self=0xce48  
    6. 01-31 14:36:04.834: I/dalvikvm(389):   | sysTid=389 nice=0 sched=0/0 cgrp=default handle=-1345006528  
    7. 01-31 14:36:04.844: I/dalvikvm(389):   | schedstat=( 257006717 305462830 51 )  
    8. 01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.helloFromJNI(Native Method)  
    9. 01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.onCreate(MainActivity.java:26)  
    10. 01-31 14:36:04.844: I/dalvikvm(389):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  
    11. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)  
    12. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)  
    13. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.access$1500(ActivityThread.java:117)  
    14. 01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)  
    15. 01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Handler.dispatchMessage(Handler.java:99)  
    16. 01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Looper.loop(Looper.java:123)  
    17. 01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread.main(ActivityThread.java:3683)  
    18. 01-31 14:36:04.864: I/dalvikvm(389):   at java.lang.reflect.Method.invokeNative(Native Method)  
    19. 01-31 14:36:04.874: I/dalvikvm(389):   at java.lang.reflect.Method.invoke(Method.java:507)  
    20. 01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)  
    21. 01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)  
    22. 01-31 14:36:04.874: I/dalvikvm(389):   at dalvik.system.NativeStart.main(Native Method)  
    23. 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;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. struct _JNIEnv;  
    2. struct _JavaVM;  
    3. typedef const struct JNINativeInterface* C_JNIEnv;  
    4.   
    5. #if defined(__cplusplus)    //为了兼容C 和 C++两种代码 使用该 宏加以区分  
    6. typedef _JNIEnv JNIEnv;     //C++ 中的JNIEnv类型  
    7. typedef _JavaVM JavaVM;  
    8. #else  
    9. typedef const struct JNINativeInterface* JNIEnv;//C语言中的JNIEnv类型  
    10. typedef const struct JNIInvokeInterface* JavaVM;  
    11. #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操作的相关方法 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * Table of interface function pointers. 
    3.  */  
    4. struct JNINativeInterface {  
    5.     void*       reserved0;  
    6.     void*       reserved1;  
    7.       
    8.     ... ...  
    9.       
    10.     jboolean    (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,  
    11.                         va_list);  
    12.     jboolean    (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,  
    13.                         jvalue*);  
    14.     jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);  
    15.     jbyte       (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);  
    16.       
    17.     ... ...  
    18.       
    19.     void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);  
    20.     jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);  
    21.   
    22.     /* added in JNI 1.6 */  
    23.     jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);  
    24. };  

    (3) C++中的JNIEnv

     
    C++ 中的JNIEnv: C++ 中的JNIEnv 就是 _JNIEnv 结构体, 二者是等同的; 因此在调用 JNI函数的时候, 只需要使用 env->NewStringUTF(env, "hello")方法即可, 不用在进行*运算;
     
    .
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * C++ object wrapper. 
    3.  * 
    4.  * This is usually overlaid on a C struct whose first element is a 
    5.  * JNINativeInterface*.  We rely somewhat on compiler behavior. 
    6.  */  
    7. struct _JNIEnv {  
    8.     /* do not rename this; it does not seem to be entirely opaque */  
    9.     const struct JNINativeInterface* functions;  
    10.   
    11. #if defined(__cplusplus)  
    12.   
    13.     jint GetVersion()  
    14.     { return functions->GetVersion(this); }  
    15.   
    16.     jlong GetDirectBufferCapacity(jobject buf)  
    17.     { return functions->GetDirectBufferCapacity(this, buf); }  
    18.   
    19.     /* added in JNI 1.6 */  
    20.     jobjectRefType GetObjectRefType(jobject obj)  
    21.     { return functions->GetObjectRefType(this, obj); }  
    22. #endif /*__cplusplus*/  
    23. };  

    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 中都有相应的定义 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    2. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
    3. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    4. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    5. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
    6. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    7. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    8. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
    9. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    10. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    11. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
    12. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    13. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    14. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
    15. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    16. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    17. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
    18. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    19. jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);  
    20. jbyteArray    (*NewByteArray)(JNIEnv*, jsize);  
    21. jcharArray    (*NewCharArray)(JNIEnv*, jsize);  
    22. jshortArray   (*NewShortArray)(JNIEnv*, jsize);  
    23. jintArray     (*NewIntArray)(JNIEnv*, jsize);  
    24. jlongArray    (*NewLongArray)(JNIEnv*, jsize);  
    25. jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);  
    26. jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);  


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

     
     
    Java中定义的方法 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. //将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider  
    2. public native int add(int x, int y);  

    C语言中定义的方法 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <jni.h>  
    2.   
    3. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数  
    4. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
    5. {  
    6.     return x + y;  
    7. }  

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

    3. NDK中C代码使用LogCat

     
     

    (1) 引入头文件

     
    NDK中断点调试 : 断点调试在NDK中实现极其困难, 因此在这里我们一般都是打印日志;
     
    引入头文件 : 在C代码中引入下面的头文件;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <android/log.h>  
    2. #define LOG_TAG "System.out"  
    3. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
    4. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  

    头文件介绍 : log.h 是关于调用 LogCat日志文件;
    -- log.h头文件路径 : android-ndk-r9cplatformsandroid-9arch-armusrincludeandroidlog.h;
    -- 主要方法 :  __android_log_write, 下面有该方法的解析, 传入参数 日志等级 日志标签 日志内容;
    -- 宏定义 : __android_log_write 方法太麻烦, 这里做出一个映射, LOGD(...) 输出debug级别的日志, LOGI(...) 输出Info级别的日志;
    --LogCat日志级别 : verbose < debug < info < warn < error < assert;
     
    使用到的log.h文件内容解析 : __android_log_write 方法中的日志等级参数就使用 枚举中的内容 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * Android log priority values, in ascending priority order. 日志等级 
    3.  */  
    4. typedef enum android_LogPriority {  
    5.     ANDROID_LOG_UNKNOWN = 0,  
    6.     ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */  
    7.     ANDROID_LOG_VERBOSE,  
    8.     ANDROID_LOG_DEBUG,  
    9.     ANDROID_LOG_INFO,  
    10.     ANDROID_LOG_WARN,  
    11.     ANDROID_LOG_ERROR,  
    12.     ANDROID_LOG_FATAL,  
    13.     ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */  
    14. } android_LogPriority;  
    15.   
    16. /* 
    17.  * Send a simple string to the log. 向LogCat中输出日志  
    18.     参数介绍: 日志优先级 , 日志标签 , 日志内容 
    19.  */  
    20. 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文件 : 
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := DataProvider  
    6. LOCAL_SRC_FILES := DataProvider.c  
    7. #增加log函数对应的函数库 liblog.so  libthread_db.a  
    8. LOCAL_LDLIBS += -llog -lthread_db   
    9. include $(BUILD_SHARED_LIBRARY)  
    函数库位置 : android-ndk-r9cplatformsandroid-9arch-armusrlib;
    函数库截图 : 从该目录下的 liglog.so可以看出, 存在该库;
    引入函数库方法 : 使用 LOCAL_LDLIBS += -l函数库名, 注意函数库名不带lib前缀 和.so 后缀, 同时可以添加多个库, 使用 -l库1 -l库2 -库3 ;
     
     

    (3) 编译执行

     
    根据(1) 中的占位符, 编写打印日志代码
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中  
    2. LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);  

    最终的包含打印日志的完整代码 : 注意, 这里有一处可能错误, 如果是32位机器, int类型占位符使用 %d 即可;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <jni.h>  
    2. #include <android/log.h>  
    3. #define LOG_TAG "System.out"  
    4. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
    5. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
    6.   
    7.   
    8. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数  
    9. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
    10. {  
    11.     //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中  
    12.     LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);  
    13.     return x + y;  
    14. }  

    重新编译C文件 : 执行 /android-ndk-r9c/ndk-build命令;
    -- 第一次编译 : 出现警告, long int占位符行不通, 注意区分机器位长, 64位 与 32位不同, 这样编译出现的结果就不会打印日志;
    -- 第二次编译 : 将占位符改为 %d ;
     
    执行按钮之后打印的日志 : 虽然有乱码, 不过显示出来了;
     
     
     

    4. 字符串处理

    .
     
    Java中的String转为C语言中的char字符串 : 下面的工具方法可以在C程序中解决这个问题;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. // java中的jstring, 转化为c的一个字符数组  
    2. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
    3. <span style="white-space:pre">  </span>//声明了一个字符串变量 rtn  
    4. <span style="white-space:pre">  </span>char* rtn = NULL;  
    5. <span style="white-space:pre">  </span>//找到Java中的String的Class对象  
    6. <span style="white-space:pre">  </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
    7. <span style="white-space:pre">  </span>//创建一个Java中的字符串 "GB2312"  
    8. <span style="white-space:pre">  </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
    9. <span style="white-space:pre">  </span>/* 
    10. <span style="white-space:pre">  </span> * 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组 
    11. <span style="white-space:pre">  </span> * "(Ljava/lang/String;)[B" 方法前面解析 : 
    12. <span style="white-space:pre">  </span> * -- Ljava/lang/String; 表示参数是String字符串 
    13. <span style="white-space:pre">  </span> * -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组 
    14. <span style="white-space:pre">  </span> */  
    15. <span style="white-space:pre">  </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
    16. <span style="white-space:pre">          </span>"(Ljava/lang/String;)[B");  
    17. <span style="white-space:pre">  </span>//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数  
    18. <span style="white-space:pre">  </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,  
    19. <span style="white-space:pre">          </span>strencode); // String .getByte("GB2312");  
    20. <span style="white-space:pre">  </span>//获取数组的长度  
    21. <span style="white-space:pre">  </span>jsize alen = (*env)->GetArrayLength(env, barr);  
    22. <span style="white-space:pre">  </span>//获取数组中的所有的元素 , 存放在 jbyte*数组中  
    23. <span style="white-space:pre">  </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
    24. <span style="white-space:pre">  </span>//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 ''  
    25. <span style="white-space:pre">  </span>if (alen > 0) {  
    26. <span style="white-space:pre">      </span>rtn = (char*) malloc(alen + 1); //new   char[alen+1]; ""  
    27. <span style="white-space:pre">      </span>memcpy(rtn, ba, alen);  
    28. <span style="white-space:pre">      </span>rtn[alen] = 0;  
    29. <span style="white-space:pre">  </span>}  
    30. <span style="white-space:pre">  </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存  
    31.   
    32.   
    33. <span style="white-space:pre">  </span>return rtn;  
    34. }  
     
    Jstring2CStr方法讲解 : 
    a. 获取Java中String类型的class对象 : 参数 : 上下文环境 env, String类完整路径 ;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
    b.创建Java字符串 : 使用 NewStringUTF 方法;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
    c.获取String中的getBytes()方法 : 参数介绍 ① env 上下文环境 ② 完整的类路径 ③ 方法名 ④ 方法签名, 方法签名 Ljava/lang/String; 代表参数是String字符串, [B  中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
    2.         "(Ljava/lang/String;)[B");  
    d. 获取数组的长度 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jsize alen = (*env)->GetArrayLength(env, barr);  
    e. 获取数组元素 : 获取数组中的所有的元素 , 存放在 jbyte*数组中;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
    f.数组拷贝: 将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 '';
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. if (alen > 0) {  
    2.     rtn = (char*) malloc(alen + 1); //new   char[alen+1]; ""  
    3.     memcpy(rtn, ba, alen);  
    4.     rtn[alen] = 0;  
    5. }  
    g.释放内存 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存  
     

    .

    作者 : 万境绝尘 

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

    .


    C语言方法 : 注意调用Jstring2CStr方法之后要强转, 否则会出错, Jstring2CStr方法要定义在该方法的前面, C语言中的方法要先声明才能使用;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)    
    2. {    
    3.     char *p = (char*)Jstring2CStr(env, str);    
    4.     //打印Java传递过来的数据    
    5.     LOGI("Java JNI string parameter is : %s", p);    
    6.         
    7.     char *append = "append";    
    8.         
    9.     //strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面    
    10.     return (*env)->NewStringUTF(env, strcat(p, append));    
    11. }  

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


    -- 将Jstring2CStr方法定义在主方法下面会出现下面错误 : 
     
    Java源码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. case R.id.sayHelloInc:    
    2.     Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();    
    3.     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);
    创建数组相关方法 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);    
    2. jbyteArray    (*NewByteArray)(JNIEnv*, jsize);    
    3. jcharArray    (*NewCharArray)(JNIEnv*, jsize);    
    4. jshortArray   (*NewShortArray)(JNIEnv*, jsize);    
    5. jintArray     (*NewIntArray)(JNIEnv*, jsize);    
    6. jlongArray    (*NewLongArray)(JNIEnv*, jsize);    
    7. jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);    
    8. jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);    

    获取数组元素相关方法 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jboolean*   (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);    
    2. jbyte*      (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);    
    3. jchar*      (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);    
    4. jshort*     (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);    
    5. jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);    
    6. jlong*      (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);    
    7. jfloat*     (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);    
    8. jdouble*    (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);   

    C语言代码 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)    
    2. {    
    3.     //获取arr大小    
    4.     int len = (*env)->GetArrayLength(env, arr);    
    5.         
    6.     //在LogCat中打印出arr的大小    
    7.     LOGI("the length of array is %d", len);    
    8.         
    9.     //如果长度为0, 返回arr    
    10.     if(len == 0)    
    11.         return arr;    
    12.             
    13.     //如果长度大于0, 那么获取数组中的每个元素    
    14.     jint* p = (*env)->GetIntArrayElements(env, arr, 0);    
    15.         
    16.     //打印出数组中每个元素的值    
    17.     int i = 0;    
    18.     for(; i < len; i ++)    
    19.     {    
    20.         LOGI("arr[%d] = %d", i, *(p + i));    
    21.     }    
    22.         
    23.     return arr;    
    24.         
    25. }    


    Java代码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. case R.id.intMethod:    
    2.     int[] array = {1, 2, 3, 4, 5};    
    3.     dataProvider.intMethod(array);    
    4.     break;    

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

    7. 本程序源码

     
    XML布局文件 : 
    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:orientation="vertical"  
    4.     android:layout_width="match_parent"  
    5.     android:layout_height="match_parent" >  
    6.   
    7.     <Button   
    8.         android:id="@+id/add"  
    9.         android:layout_width="wrap_content"  
    10.         android:layout_height="wrap_content"  
    11.         android:text="调用 add 本地 方法"  
    12.         android:onClick="onClick"/>  
    13.       
    14.     <Button   
    15.         android:id="@+id/sayHelloInc"  
    16.         android:layout_width="wrap_content"  
    17.         android:layout_height="wrap_content"  
    18.         android:text="调用 sayHelloInc 本地 方法"  
    19.         android:onClick="onClick"/>  
    20.       
    21.     <Button   
    22.         android:id="@+id/intMethod"  
    23.         android:layout_width="wrap_content"  
    24.         android:layout_height="wrap_content"  
    25.         android:text="调用 intMethod 本地 方法"  
    26.         android:onClick="onClick"/>  
    27.   
    28. </LinearLayout>  

    Java源码 : 
    -- MainActivity源码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package shuliang.han.ndkparameterpassing;  
    2.   
    3. import android.app.Activity;  
    4. import android.os.Bundle;  
    5. import android.view.View;  
    6. import android.widget.Toast;  
    7.   
    8. public class MainActivity extends Activity {  
    9.   
    10.     static{  
    11.         System.loadLibrary("DataProvider");  
    12.     }  
    13.       
    14.     DataProvider dataProvider;  
    15.     @Override  
    16.     public void onCreate(Bundle savedInstanceState) {  
    17.         super.onCreate(savedInstanceState);  
    18.         setContentView(R.layout.activity_main);  
    19.         dataProvider = new DataProvider();  
    20.     }  
    21.   
    22.     public void onClick(View view) {  
    23.           
    24.         int id = view.getId();  
    25.           
    26.         switch (id) {  
    27.             case R.id.add:  
    28.                 int result = dataProvider.add(1, 2);  
    29.                 Toast.makeText(getApplicationContext(), "the add result : " + result, Toast.LENGTH_LONG).show();  
    30.                 break;  
    31.                   
    32.             case R.id.sayHelloInc:  
    33.                 Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();  
    34.                 break;  
    35.                   
    36.             case R.id.intMethod:  
    37.                 int[] array = {1, 2, 3, 4, 5};  
    38.                 dataProvider.intMethod(array);  
    39.                 break;  
    40.       
    41.             default:  
    42.                 break;  
    43.         }  
    44.     }  
    45.       
    46. }  
    --DataProvider源码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package shuliang.han.ndkparameterpassing;  
    2.   
    3. public class DataProvider {  
    4.   
    5.     //将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider  
    6.     public native int add(int x, int y);  
    7.       
    8.     //将Java字符串传递给C语言, C语言处理字符串之后, 将处理结果返回给java  
    9.     public native String sayHelloInc(String s);  
    10.       
    11.     //将java中的int数组传递给C语言, C语言为每个元素加10, 返回给Java  
    12.     public native int[] intMethod(int[] nums);   
    13.       
    14. }  

    JNI相关源码 : 
    -- Android.mk源码 : 
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := DataProvider  
    6. LOCAL_SRC_FILES := DataProvider.c  
    7. #增加log函数对应的log库  
    8. LOCAL_LDLIBS += -llog   
    9.   
    10. include $(BUILD_SHARED_LIBRARY)  
    --DataProvider.c 主程序源码 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <jni.h>  
    2. #include <string.h>  
    3. #include <android/log.h>  
    4. #define LOG_TAG "System.out"  
    5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
    6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
    7.   
    8. // java中的jstring, 转化为c的一个字符数组  
    9. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
    10. <span style="white-space:pre">  </span>//声明了一个字符串变量 rtn  
    11. <span style="white-space:pre">  </span>char* rtn = NULL;  
    12. <span style="white-space:pre">  </span>//找到Java中的String的Class对象  
    13. <span style="white-space:pre">  </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
    14. <span style="white-space:pre">  </span>//创建一个Java中的字符串 "GB2312"  
    15. <span style="white-space:pre">  </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
    16. <span style="white-space:pre">  </span>/* 
    17. <span style="white-space:pre">  </span> * 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组 
    18. <span style="white-space:pre">  </span> * "(Ljava/lang/String;)[B" 方法前面解析 : 
    19. <span style="white-space:pre">  </span> * -- Ljava/lang/String; 表示参数是String字符串 
    20. <span style="white-space:pre">  </span> * -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组 
    21. <span style="white-space:pre">  </span> */  
    22. <span style="white-space:pre">  </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
    23. <span style="white-space:pre">          </span>"(Ljava/lang/String;)[B");  
    24. <span style="white-space:pre">  </span>//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数  
    25. <span style="white-space:pre">  </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,  
    26. <span style="white-space:pre">          </span>strencode); // String .getByte("GB2312");  
    27. <span style="white-space:pre">  </span>//获取数组的长度  
    28. <span style="white-space:pre">  </span>jsize alen = (*env)->GetArrayLength(env, barr);  
    29. <span style="white-space:pre">  </span>//获取数组中的所有的元素 , 存放在 jbyte*数组中  
    30. <span style="white-space:pre">  </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
    31. <span style="white-space:pre">  </span>//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 ''  
    32. <span style="white-space:pre">  </span>if (alen > 0) {  
    33. <span style="white-space:pre">      </span>rtn = (char*) malloc(alen + 1); //new   char[alen+1]; ""  
    34. <span style="white-space:pre">      </span>memcpy(rtn, ba, alen);  
    35. <span style="white-space:pre">      </span>rtn[alen] = 0;  
    36. <span style="white-space:pre">  </span>}  
    37. <span style="white-space:pre">  </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存  
    38.   
    39.   
    40. <span style="white-space:pre">  </span>return rtn;  
    41. }  
    42.   
    43. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数  
    44. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
    45. {  
    46.     //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中  
    47.     LOGI("JNI_log : x = %d , y = %d" , x , y);  
    48.     return x + y;  
    49. }  
    50.   
    51. jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)  
    52. {  
    53.     char *p = (char*)Jstring2CStr(env, str);  
    54.     //打印Java传递过来的数据  
    55.     LOGI("Java JNI string parameter is : %s", p);  
    56.       
    57.     char *append = "append";  
    58.       
    59.     //strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面  
    60.     return (*env)->NewStringUTF(env, strcat(p, append));  
    61. }  
    62.   
    63. jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)  
    64. {  
    65.     //获取arr大小  
    66.     int len = (*env)->GetArrayLength(env, arr);  
    67.       
    68.     //在LogCat中打印出arr的大小  
    69.     LOGI("the length of array is %d", len);  
    70.       
    71.     //如果长度为0, 返回arr  
    72.     if(len == 0)  
    73.         return arr;  
    74.           
    75.     //如果长度大于0, 那么获取数组中的每个元素  
    76.     jint* p = (*env)->GetIntArrayElements(env, arr, 0);  
    77.       
    78.     //打印出数组中每个元素的值  
    79.     int i = 0;  
    80.     for(; i < len; i ++)  
    81.     {  
    82.         LOGI("arr[%d] = %d", i, *(p + i));  
    83.     }  
    84.       
    85.     return arr;  
    86.       
    87. }  


    .
     
     
     

    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返回值;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
    2. char* classname = "shulaing/han/ndk_callback/DataProvider";  
    3.   
    4.   
    5. jclass dpclazz = (*env)->FindClass(env, classname);  

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

     
    使用jni.h中提供的GetMethodID方法, 获取jmethodID, 传入参数 ①JNIEnv指针 ②Class对象 ③ 方法名 ④方法签名, 在这里方法名和方法签名确定一个方法, 方法签名就是方法的返回值 与 参数的唯一标示;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
    2. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");  
     
    找到静态方法 : 如果方法是静态的, 就使用GetStaticMethod方法获取 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)  
    2.     { return functions->GetStaticMethodID(this, clazz, name, sig); }  



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

     
    普通方法 : CallTypeMethod , 其中的Type随着返回值类型的不同而改变;
    参数介绍 : ① JNIEnv指针 ②调用该native方法的对象 ③方法的methodID ④⑤... 后面是可变参数, 这些参数是
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);  
    2.   
    3. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  
    4. jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    5. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    6. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
    7. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    8. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    9. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
    10. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    11. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    12. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
    13. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    14. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    15. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
    16. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    17. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    18. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
    19. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    20. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    21. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
    22. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    23. jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    24. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
    25. jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
    26. jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
    27. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
    28. jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
    29. jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
    30. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);  
    31. void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    32. void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  

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

    2. 一些基本代码编写

     
    Java代码 : 定义一个callCcode本地方法, 以及三个Java方法, 在jni中使用本地方法调用Java中的方法;
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package shulaing.han.ndk_callback;  
    2.   
    3. public class DataProvider {  
    4.   
    5.     public native void callCcode();  
    6.       
    7.     //C调用java中空方法 shulaing.han.ndk_callback.DataProvider  
    8.   
    9.     public void helloFromJava(){  
    10.         System.out.println("hello from java");  
    11.     }  
    12.       
    13.     //C调用java中的带两个int参数的方法  
    14.     public int Add(int x,int y){  
    15.         return x + y;  
    16.     }  
    17.       
    18.     //C调用java中参数为string的方法  
    19.     public void printString(String s){  
    20.         System.out.println(s);  
    21.     }  
    22.       
    23. }  

    生成头文件 : 进入 bin/classed目录, 使用 javah shulaing.han.ndk_callback.DataProvider 命令, 可以在bin/classes下生成头文件;
     
    头文件内容 : 文件名 : shulaing_han_ndk_callback_DataProvider.h ;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>  
    3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
    4.   
    5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
    6. #define _Included_shulaing_han_ndk_callback_DataProvider  
    7. #ifdef __cplusplus  
    8. extern "C" {  
    9. #endif  
    10. /* 
    11.  * Class:     shulaing_han_ndk_callback_DataProvider 
    12.  * Method:    callCcode 
    13.  * Signature: ()V 
    14.  */  
    15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
    16.   (JNIEnv *, jobject);  
    17.   
    18. #ifdef __cplusplus  
    19. }  
    20. #endif  
    21. #endif  

    编写Android.mk文件 : 注意将LogCat日志输出系统动态库加入;
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := jni  
    6. LOCAL_SRC_FILES := jni.c  
    7. #增加log函数对应的log库  
    8. LOCAL_LDLIBS += -llog   
    9.   
    10. include $(BUILD_SHARED_LIBRARY)  

    编写jni的C代码 : 注意加入LogCat相关导入的包;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include "shulaing_han_ndk_callback_DataProvider.h"  
    2. #include <string.h>  
    3. #include <android/log.h>  
    4. #define LOG_TAG "System.out"  
    5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
    6. #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 -s shulaing.han.ndk_callback.DataProvider 命令, 即可显示出每个方法的签名;
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. $ javap -s shulaing.han.ndk_callback.DataProvider  
    2. Compiled from "DataProvider.java"  
    3. public class shulaing.han.ndk_callback.DataProvider extends java.lang.Object{  
    4. public shulaing.han.ndk_callback.DataProvider();  
    5.   Signature: ()V  
    6. public native void callCcode();  
    7.   Signature: ()V  
    8. public void helloFromJava();  
    9.   Signature: ()V  
    10. public int Add(int, int);  
    11.   Signature: (II)I  
    12. public void printString(java.lang.String);  
    13.   Signature: (Ljava/lang/String;)V  
    14. }  
    截图 : 


     
     
    方法签名介绍 : 
    -- 返回值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方法的相关函数 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);  
    2.   
    3. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  
    4. jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    5. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    6. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
    7. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    8. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    9. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
    10. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    11. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    12. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
    13. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    14. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    15. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
    16. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    17. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    18. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
    19. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    20. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    21. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
    22. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    23. jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
    24. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
    25. jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
    26. jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
    27. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
    28. jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
    29. jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
    30. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);  
    31. void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
    32. void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
     
    C语言代码 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include "shulaing_han_ndk_callback_DataProvider.h"  
    2. #include <string.h>  
    3. #include <android/log.h>  
    4. #define LOG_TAG "System.out"  
    5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
    6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
    7.   
    8. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
    9.   (JNIEnv * env, jobject obj)  
    10. {  
    11.     //调用DataProvider对象中的helloFromJava()方法  
    12.     //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
    13.     LOGI("in code");  
    14.     //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
    15.     char* classname = "shulaing/han/ndk_callback/DataProvider";  
    16.   
    17.   
    18.     jclass dpclazz = (*env)->FindClass(env, classname);  
    19.     if(dpclazz == 0)  
    20.         LOGI("class not find !!!");  
    21.     else  
    22.         LOGI("class find !!!");  
    23.   
    24.     //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
    25.     jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");  
    26.     if(methodID == 0)  
    27.             LOGI("method not find !!!");  
    28.         else  
    29.             LOGI("method find !!!");  
    30.   
    31.     /* 
    32.      * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
    33.      * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
    34.      */  
    35.     LOGI("before call method");  
    36.     (*env)->CallVoidMethod(env, obj, methodID);  
    37.     LOGI("after call method");  
    38.   
    39. }  

    Java代码 : 
    --XML布局文件代码 : 
    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:orientation="vertical"  
    6.     android:paddingBottom="@dimen/activity_vertical_margin"  
    7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
    8.     android:paddingRight="@dimen/activity_horizontal_margin"  
    9.     android:paddingTop="@dimen/activity_vertical_margin"  
    10.     tools:context=".MainActivity" >  
    11.   
    12.     <Button  
    13.         android:id="@+id/call_void_method"  
    14.         android:layout_width="wrap_content"  
    15.         android:layout_height="wrap_content"  
    16.         android:onClick="onClick"  
    17.         android:text="C语言回调Java中的空方法" />  
    18.   
    19. </LinearLayout>  
    --MainActivity代码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package shulaing.han.ndk_callback;  
    2.   
    3. import android.app.Activity;  
    4. import android.os.Bundle;  
    5. import android.view.View;  
    6.   
    7. public class MainActivity extends Activity {  
    8.   
    9.     static{  
    10.         System.loadLibrary("jni");  
    11.     }  
    12.     DataProvider dp;  
    13.       
    14.     @Override  
    15.     protected void onCreate(Bundle savedInstanceState) {  
    16.         super.onCreate(savedInstanceState);  
    17.         setContentView(R.layout.activity_main);  
    18.         dp = new DataProvider();  
    19.     }  
    20.   
    21.     public void onClick(View view) {  
    22.         int id = view.getId();  
    23.         switch (id) {  
    24.             case R.id.call_void_method:  
    25.                 dp.callCcode();  
    26.                 break;  
    27.       
    28.             default:  
    29.                 break;  
    30.         }  
    31.     }  
    32.   
    33. }  
     
    执行结果 : 


    .
     
     

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

     
    在DataProvider中添加两个native方法 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public native void callCcode();  
    2. public native void callCcode1();  
    3. public native void callCcode2();  
    进入bin/classes目录, 使用 javah -jni shulaing.han.ndk_callback.DataProvider 命令生成头文件 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>  
    3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
    4.   
    5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
    6. #define _Included_shulaing_han_ndk_callback_DataProvider  
    7. #ifdef __cplusplus  
    8. extern "C" {  
    9. #endif  
    10. /* 
    11.  * Class:     shulaing_han_ndk_callback_DataProvider 
    12.  * Method:    callCcode 
    13.  * Signature: ()V 
    14.  */  
    15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
    16.   (JNIEnv *, jobject);  
    17.   
    18. /* 
    19.  * Class:     shulaing_han_ndk_callback_DataProvider 
    20.  * Method:    callCcode1 
    21.  * Signature: ()V 
    22.  */  
    23. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
    24.   (JNIEnv *, jobject);  
    25.   
    26. /* 
    27.  * Class:     shulaing_han_ndk_callback_DataProvider 
    28.  * Method:    callCcode2 
    29.  * Signature: ()V 
    30.  */  
    31. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
    32.   (JNIEnv *, jobject);  
    33.   
    34. #ifdef __cplusplus  
    35. }  
    36. #endif  
    37. #endif  
     
    jni C语言代码 : 这里只需要修改两处, 方法名, 获取方法id中的参数, 调用方法中最后加上一个Java参数;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
    2.   (JNIEnv *env, jobject obj)  
    3. {  
    4.     //调用DataProvider对象中的helloFromJava()方法  
    5.         //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
    6.         LOGI("in code");  
    7.         //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
    8.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
    9.   
    10.   
    11.         jclass dpclazz = (*env)->FindClass(env, classname);  
    12.         if(dpclazz == 0)  
    13.             LOGI("class not find !!!");  
    14.         else  
    15.             LOGI("class find !!!");  
    16.   
    17.         //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
    18.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");  
    19.         if(methodID == 0)  
    20.                 LOGI("method not find !!!");  
    21.             else  
    22.                 LOGI("method find !!!");  
    23.   
    24.         /* 
    25.          * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
    26.          * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
    27.          */  
    28.         LOGI("before call method");  
    29.         (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));  
    30.         LOGI("after call method");  
    31. }  

    执行后的结果 : 
     

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

     
    按照上面的流程, 不同之处就是jni中获取方法 和 方法id , 调用方法的jni函数不同 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
    2.   (JNIEnv *env, jobject obj)  
    3. {  
    4.     //调用DataProvider对象中的helloFromJava()方法  
    5.         //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
    6.         LOGI("in code");  
    7.         //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
    8.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
    9.   
    10.   
    11.         jclass dpclazz = (*env)->FindClass(env, classname);  
    12.         if(dpclazz == 0)  
    13.             LOGI("class not find !!!");  
    14.         else  
    15.             LOGI("class find !!!");  
    16.   
    17.         //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
    18.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");  
    19.         if(methodID == 0)  
    20.                 LOGI("method not find !!!");  
    21.             else  
    22.                 LOGI("method find !!!");  
    23.   
    24.         /* 
    25.          * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
    26.          * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
    27.          */  
    28.         LOGI("before call method");  
    29.         (*env)->CallIntMethod(env, obj, methodID, 3, 5);  
    30.         LOGI("after call method");  
    31.   
    32. }  

    Java代码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. case R.id.call_int_parameter_method:  
    2.     dp.callCcode2();  
    3.     break;  
    执行结果 : 

    .

    作者 : 万境绝尘 

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

    .

    6. 完整源码 

     
    Java源码 : 
    -- DataProvider源码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package shulaing.han.ndk_callback;  
    2.   
    3.   
    4. public class DataProvider {  
    5.   
    6.     public native void callCcode();  
    7.     public native void callCcode1();  
    8.     public native void callCcode2();  
    9.       
    10.     //C调用java中空方法 shulaing.han.ndk_callback.DataProvider  
    11.   
    12.     public void helloFromJava(){  
    13.         System.out.println("hello from java");  
    14.     }  
    15.       
    16.     //C调用java中的带两个int参数的方法  
    17.     public int Add(int x,int y){  
    18.         System.out.println("the add result is : " + (x + y));  
    19.         return x + y;  
    20.     }  
    21.       
    22.     //C调用java中参数为string的方法  
    23.     public void printString(String s){  
    24.         System.out.println("in java code :" + s);  
    25.     }  
    26.       
    27. }  
    -- MainActivity源码 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package shulaing.han.ndk_callback;  
    2.   
    3. import android.app.Activity;  
    4. import android.os.Bundle;  
    5. import android.view.View;  
    6.   
    7. public class MainActivity extends Activity {  
    8.   
    9.     static{  
    10.         System.loadLibrary("jni");  
    11.     }  
    12.     DataProvider dp;  
    13.       
    14.     @Override  
    15.     protected void onCreate(Bundle savedInstanceState) {  
    16.         super.onCreate(savedInstanceState);  
    17.         setContentView(R.layout.activity_main);  
    18.         dp = new DataProvider();  
    19.     }  
    20.   
    21.     public void onClick(View view) {  
    22.         int id = view.getId();  
    23.         switch (id) {  
    24.             case R.id.call_void_method:  
    25.                 dp.callCcode();  
    26.                 break;  
    27.               
    28.             case R.id.call_string_parameter_method:  
    29.                 dp.callCcode1();  
    30.                 break;  
    31.       
    32.             case R.id.call_int_parameter_method:  
    33.                 dp.callCcode2();  
    34.                 break;  
    35.                   
    36.             default:  
    37.                 break;  
    38.         }  
    39.     }  
    40.   
    41. }  
     
    XML布局文件源码 : 
    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:orientation="vertical"  
    6.     android:paddingBottom="@dimen/activity_vertical_margin"  
    7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
    8.     android:paddingRight="@dimen/activity_horizontal_margin"  
    9.     android:paddingTop="@dimen/activity_vertical_margin"  
    10.     tools:context=".MainActivity" >  
    11.   
    12.     <Button  
    13.         android:id="@+id/call_void_method"  
    14.         android:layout_width="wrap_content"  
    15.         android:layout_height="wrap_content"  
    16.         android:onClick="onClick"  
    17.         android:text="C语言回调Java中的空方法" />  
    18.       
    19.     <Button  
    20.         android:id="@+id/call_string_parameter_method"  
    21.         android:layout_width="wrap_content"  
    22.         android:layout_height="wrap_content"  
    23.         android:onClick="onClick"  
    24.         android:text="C语言回调Java中的String参数方法" />  
    25.       
    26.     <Button  
    27.         android:id="@+id/call_int_parameter_method"  
    28.         android:layout_width="wrap_content"  
    29.         android:layout_height="wrap_content"  
    30.         android:onClick="onClick"  
    31.         android:text="C语言回调Java中的int参数方法" />  
    32.   
    33. </LinearLayout>  
     
     
    jni源码 : 
    -- 头文件源码 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>  
    3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
    4.   
    5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
    6. #define _Included_shulaing_han_ndk_callback_DataProvider  
    7. #ifdef __cplusplus  
    8. extern "C" {  
    9. #endif  
    10. /* 
    11.  * Class:     shulaing_han_ndk_callback_DataProvider 
    12.  * Method:    callCcode 
    13.  * Signature: ()V 
    14.  */  
    15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
    16.   (JNIEnv *, jobject);  
    17.   
    18. /* 
    19.  * Class:     shulaing_han_ndk_callback_DataProvider 
    20.  * Method:    callCcode1 
    21.  * Signature: ()V 
    22.  */  
    23. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
    24.   (JNIEnv *, jobject);  
    25.   
    26. /* 
    27.  * Class:     shulaing_han_ndk_callback_DataProvider 
    28.  * Method:    callCcode2 
    29.  * Signature: ()V 
    30.  */  
    31. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
    32.   (JNIEnv *, jobject);  
    33.   
    34. #ifdef __cplusplus  
    35. }  
    36. #endif  
    37. #endif  
    -- Android.mk源码 :
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := jni  
    6. LOCAL_SRC_FILES := jni.c  
    7. #增加log函数对应的log库  
    8. LOCAL_LDLIBS += -llog   
    9.   
    10. include $(BUILD_SHARED_LIBRARY)  
    -- jni主程序源码 :  
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include "shulaing_han_ndk_callback_DataProvider.h"  
    2. #include "first.h"  
    3. #include <string.h>  
    4. #include <android/log.h>  
    5. #define LOG_TAG "System.out"  
    6. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
    7. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
    8.   
    9. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
    10.   (JNIEnv * env, jobject obj)  
    11. {  
    12.     //调用DataProvider对象中的helloFromJava()方法  
    13.     //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
    14.     LOGI("in code");  
    15.     //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
    16.     char* classname = "shulaing/han/ndk_callback/DataProvider";  
    17.   
    18.   
    19.     jclass dpclazz = (*env)->FindClass(env, classname);  
    20.     if(dpclazz == 0)  
    21.         LOGI("class not find !!!");  
    22.     else  
    23.         LOGI("class find !!!");  
    24.   
    25.     //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
    26.     jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");  
    27.     if(methodID == 0)  
    28.             LOGI("method not find !!!");  
    29.         else  
    30.             LOGI("method find !!!");  
    31.   
    32.     /* 
    33.      * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
    34.      * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
    35.      */  
    36.     LOGI("before call method");  
    37.     (*env)->CallVoidMethod(env, obj, methodID);  
    38.     LOGI("after call method");  
    39.   
    40. }  
    41.   
    42. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
    43.   (JNIEnv *env, jobject obj)  
    44. {  
    45.     //调用DataProvider对象中的helloFromJava()方法  
    46.         //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
    47.         LOGI("in code");  
    48.         //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
    49.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
    50.   
    51.   
    52.         jclass dpclazz = (*env)->FindClass(env, classname);  
    53.         if(dpclazz == 0)  
    54.             LOGI("class not find !!!");  
    55.         else  
    56.             LOGI("class find !!!");  
    57.   
    58.         //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
    59.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");  
    60.         if(methodID == 0)  
    61.                 LOGI("method not find !!!");  
    62.             else  
    63.                 LOGI("method find !!!");  
    64.   
    65.         /* 
    66.          * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
    67.          * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
    68.          */  
    69.         LOGI("before call method");  
    70.         (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));  
    71.         LOGI("after call method");  
    72. }  
    73.   
    74. /* 
    75.  * 实际开发的情况 
    76.  * C代码工程师给我们 first.h first.c , 我们只需要将first.h引入, 然后就可以使用其中的方法了 
    77.  */  
    78. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
    79.   (JNIEnv *env, jobject obj)  
    80. {  
    81.     //调用DataProvider对象中的helloFromJava()方法  
    82.         //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
    83.         LOGI("in code");  
    84.         //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
    85.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
    86.   
    87.   
    88.         jclass dpclazz = (*env)->FindClass(env, classname);  
    89.         if(dpclazz == 0)  
    90.             LOGI("class not find !!!");  
    91.         else  
    92.             LOGI("class find !!!");  
    93.   
    94.         //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
    95.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");  
    96.         if(methodID == 0)  
    97.                 LOGI("method not find !!!");  
    98.             else  
    99.                 LOGI("method find !!!");  
    100.   
    101.         /* 
    102.          * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
    103.          * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
    104.          */  
    105.         LOGI("before call method");  
    106.         (*env)->CallIntMethod(env, obj, methodID, 3, 5);  
    107.         LOGI("after call method");  
    108.   
    109. }  

    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源码 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #ifndef FIRST_H  
    2. #define FIRST_H  
    3.   
    4. extern int first(int  x, int  y);  
    5.   
    6. #endif /* FIRST_H */  

    first.c源码 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include "first.h"  
    2.   
    3. int  first(int  x, int  y)  
    4. {  
    5.     return x + y;  
    6. }  

    在签名函数中, 直接调用 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代码 : frameworksasecorejavaandroidutilLog.java
    -- JNI层实现代码 : frameworksasecorejniandroid_util_Log.cpp
    下面的是Android自定义的JNI规范相关的源码 : 
    -- JNI规范头文件 : dalviklibnativehelperinclude ativehelperjni.h
    -- JNI帮助文件 : ① dalviklibnativehelperinclude ativehelperJNIHelp.h  ② dalviklibnativehelperJNIHelp.c
    -- JNI运行时文件 :  frameworksasecorejniAndroidRuntime.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源码中的位置 : frameworksasecorejavaandroidutilLog.java
     
    Log.java内容 : 
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package android.util;  
    2.   
    3. import com.android.internal.os.RuntimeInit;  
    4.   
    5. import java.io.PrintWriter;  
    6. import java.io.StringWriter;  
    7. public final class Log {  
    8.   
    9.     ... ...  
    10.       
    11.     //打印日志  
    12.     public static int d(String tag, String msg) {  
    13.         return println_native(LOG_ID_MAIN, DEBUG, tag, msg);  
    14.     }  
    15.   
    16.     //打印日志和异常  
    17.     public static int d(String tag, String msg, Throwable tr) {  
    18.         return println_native(LOG_ID_MAIN, DEBUG, tag, msg + ' ' + getStackTraceString(tr));  
    19.     }  
    20.   
    21.     //打印日志  
    22.     public static int i(String tag, String msg) {  
    23.         return println_native(LOG_ID_MAIN, INFO, tag, msg);  
    24.     }  
    25.   
    26.     ... ...  
    27.       
    28.     //声明native方法  
    29.     public static native boolean isLoggable(String tag, int level);  
    30.   
    31.     ... ...  
    32.     
    33.     /** @hide */ public static final int LOG_ID_MAIN = 0;  
    34.     /** @hide */ public static final int LOG_ID_RADIO = 1;  
    35.     /** @hide */ public static final int LOG_ID_EVENTS = 2;  
    36.     /** @hide */ public static final int LOG_ID_SYSTEM = 3;  
    37.   
    38.     //声明native方法  
    39.     /** @hide */ public static native int println_native(int bufID,  
    40.             int priority, String tag, String msg);  
    41. }  

    2. 分析Log系统JNI层源码

     
    JNI层方法: JNI层方法根据一定规则与Java层声明的Native方法进行映射, 然后可以通过JNIEnv指针提供的JNI函数对Java层进行操作;
    Log系统的JNI层文件是 : android_util_Log.cpp, 该文件路径 :frameworksasecorejniandroid_util_Log.cpp 代码如下 :
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #define LOG_NAMESPACE "log.tag."  
    2. #define LOG_TAG "Log_println"  
    3.   
    4. #include <assert.h>  
    5. #include <cutils/properties.h>  
    6. #include <utils/Log.h>  
    7. #include <utils/String8.h>  
    8.   
    9. #include "jni.h"  
    10. #include "utils/misc.h"  
    11. #include "android_runtime/AndroidRuntime.h"  
    12.   
    13. ... ...  
    14.   
    15. namespace android {  
    16.   
    17. struct levels_t {  
    18.     jint verbose;  
    19.     jint debug;  
    20.     jint info;  
    21.     jint warn;  
    22.     jint error;  
    23.     jint assert;  
    24. };  
    25. static levels_t levels;  
    26.   
    27. static int toLevel(const char* value)   
    28. {  
    29.     switch (value[0]) {  
    30.         case 'V': return levels.verbose;  
    31.         case 'D': return levels.debug;  
    32.         case 'I': return levels.info;  
    33.         case 'W': return levels.warn;  
    34.         case 'E': return levels.error;  
    35.         case 'A': return levels.assert;  
    36.         case 'S': return -1; // SUPPRESS  
    37.     }  
    38.     return levels.info;  
    39. }  
    40.   
    41. /* 
    42.     实现Java层声明的 isLoggable 方法, 注意方法名不符合标准JNI规范 
    43.     标准的JNI规范方法名应该是 Java_包名_类名_方法名 
    44.     其中传入了JNIEnv 和 jobject 参数, JNIEnv参数是Java运行环境, 可以与JVM进行交互 
    45.     jobject参数是包含Native方法的Java类对象 
    46.     该方法中可以通过JNIEnv调用本地库进行函数处理, 最后返回给Java层函数 
    47. */  
    48. static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)  
    49. {  
    50. #ifndef HAVE_ANDROID_OS  
    51.     return false;  
    52. #else /* HAVE_ANDROID_OS */  
    53.     int len;  
    54.     char key[PROPERTY_KEY_MAX];  
    55.     char buf[PROPERTY_VALUE_MAX];  
    56.   
    57.     if (tag == NULL) {  
    58.         return false;  
    59.     }  
    60.       
    61.     jboolean result = false;  
    62.       
    63.     //调用了JNI函数  
    64.     const char* chars = env->GetStringUTFChars(tag, NULL);  
    65.   
    66.     if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {  
    67.         jclass clazz = env->FindClass("java/lang/IllegalArgumentException");  
    68.         char buf2[200];  
    69.         snprintf(buf2, sizeof(buf2), "Log tag "%s" exceeds limit of %d characters ",  
    70.                 chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));  
    71.   
    72.         // release the chars!  
    73.         env->ReleaseStringUTFChars(tag, chars);  
    74.   
    75.         env->ThrowNew(clazz, buf2);  
    76.         return false;  
    77.     } else {  
    78.         strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);  
    79.         strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);  
    80.     }  
    81.       
    82.     env->ReleaseStringUTFChars(tag, chars);  
    83.   
    84.     len = property_get(key, buf, "");  
    85.     int logLevel = toLevel(buf);  
    86.     return (logLevel >= 0 && level >= logLevel) ? true : false;  
    87. #endif /* HAVE_ANDROID_OS */  
    88. }  
    89.   
    90. /* 
    91.  * In class android.util.Log: 
    92.  *  public static native int println_native(int buffer, int priority, String tag, String msg) 
    93.  */  
    94. static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,  
    95.         jint bufID, jint priority, jstring tagObj, jstring msgObj)  
    96. {  
    97.     const char* tag = NULL;  
    98.     const char* msg = NULL;  
    99.   
    100.     if (msgObj == NULL) {  
    101.         jclass npeClazz;  
    102.   
    103.         npeClazz = env->FindClass("java/lang/NullPointerException");  
    104.         assert(npeClazz != NULL);  
    105.   
    106.         env->ThrowNew(npeClazz, "println needs a message");  
    107.         return -1;  
    108.     }  
    109.   
    110.     if (bufID < 0 || bufID >= LOG_ID_MAX) {  
    111.         jclass npeClazz;  
    112.   
    113.         npeClazz = env->FindClass("java/lang/NullPointerException");  
    114.         assert(npeClazz != NULL);  
    115.   
    116.         env->ThrowNew(npeClazz, "bad bufID");  
    117.         return -1;  
    118.     }  
    119.   
    120.     if (tagObj != NULL)  
    121.         tag = env->GetStringUTFChars(tagObj, NULL);  //调用JNI函数  
    122.     msg = env->GetStringUTFChars(msgObj, NULL);  
    123.   
    124.     int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);  
    125.   
    126.     if (tag != NULL)  
    127.         env->ReleaseStringUTFChars(tagObj, tag); //调用JNI函数释放资源  
    128.     env->ReleaseStringUTFChars(msgObj, msg); //调用JNI函数释放资源  
    129.   
    130.     return res;  
    131. }  
    132.   
    133. /* 
    134.  * JNI registration. JNI方法注册 
    135.  */  
    136. static JNINativeMethod gMethods[] = {  
    137.     /* name, signature, funcPtr */  
    138.     { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },  
    139.     { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },  
    140. };  
    141.   
    142. int register_android_util_Log(JNIEnv* env)  
    143. {  
    144.     jclass clazz = env->FindClass("android/util/Log");  
    145.   
    146.     if (clazz == NULL) {  
    147.         LOGE("Can't find android/util/Log");  
    148.         return -1;  
    149.     }  
    150.       
    151.     levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));  
    152.     levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));  
    153.     levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));  
    154.     levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));  
    155.     levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));  
    156.     levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));  
    157.                   
    158.     return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));  
    159. }  
    160.   
    161. }; // namespace android  

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

     
    标准JNI规范 : 在标准的JNI规范中, Java中的Native方法 与 JNI层方法 是通过方法名的对应关系进行映射的, 我们通过 javah 工具生成JNI层头文件, 头文件中定义了规范的JNI层方法名, 这个方法名就与Java Native方法对应;
     
    Android自定义规范 : 在 dalviklibnativehelperinclude ativehelperjni.h 中定义了这样的映射关系 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. typedef struct {  
    2.     const char* name;       //Java层Native函数方法名  
    3.     const char* signature;  //Java层Native函数的签名  
    4.     void*       fnPtr;      //JNI层实现的方法  
    5. } JNINativeMethod;  
    .
    JNINativeMethod类型数据 : 在android_util_Log.cpp 中定义了一个该类型的数组 :
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * JNI registration. 
    3.  */  
    4. static JNINativeMethod gMethods[] = {  
    5.     /* name, signature, funcPtr */  
    6.     { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },  
    7.     { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },  
    8. };  

    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 中存在这样的方法 : 
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. int register_android_util_Log(JNIEnv* env)  
    2. {  
    3.     jclass clazz = env->FindClass("android/util/Log");  
    4.   
    5.     if (clazz == NULL) {  
    6.         LOGE("Can't find android/util/Log");  
    7.         return -1;  
    8.     }  
    9.       
    10.     levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));  
    11.     levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));  
    12.     levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));  
    13.     levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));  
    14.     levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));  
    15.     levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));  
    16.                   
    17.     return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));  
    18. }  
    19.   
    20. }; // 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中 : 该文件的路径在 frameworksasecorejniAndroidRuntime.cpp ;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * Register native methods using JNI. 
    3.  */  
    4. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,  
    5.     const char* className, const JNINativeMethod* gMethods, int numMethods)  
    6. {  
    7.     return jniRegisterNativeMethods(env, className, gMethods, numMethods);  
    8. }  

    registerNativeMethods 方法只是对 jniRegisterNativeMethods 方法的封装, 在JNIHelp.h中找到该方法的声明
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * Register one or more native methods with a particular class. 
    3.  */  
    4. int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,  
    5.     const JNINativeMethod* gMethods, int numMethods);  
     
    在JNIHelp.c 中找到该方法的实现 : 最终方法中调用了 JNIEnv 的RegisterNatives 函数, 将gMethods中存放的JNINativeMethod结构体(存放Native方法 与 JNI方法关联信息) 传递到java虚拟机;
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * Register native JNI-callable methods. 
    3.  * 
    4.  * "className" looks like "java/lang/String". 
    5.  */  
    6. int jniRegisterNativeMethods(JNIEnv* env, const char* className,  
    7.     const JNINativeMethod* gMethods, int numMethods)  
    8. {  
    9.     jclass clazz;  
    10.   
    11.     LOGV("Registering %s natives ", className);  
    12.     clazz = (*env)->FindClass(env, className);  
    13.     if (clazz == NULL) {  
    14.         LOGE("Native registration unable to find class '%s' ", className);  
    15.         return -1;  
    16.     }  
    17.   
    18.     int result = 0;  
    19.     if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {  
    20.         LOGE("RegisterNatives failed for '%s' ", className);  
    21.         result = -1;  
    22.     }  
    23.   
    24.     (*env)->DeleteLocalRef(env, clazz);  
    25.     return result;  
    26. }  


    6. JNI的规范

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

    .

    作者 : 万境绝尘 

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

  • 相关阅读:
    C#事件解析
    VB 正则表达式应用
    vb.net 2进制、8进制、10进制、16进制...各种进制间的轻松转换
    .net 中的委托(delegate)的使用和原理
    什么是Color LUT/Color Map
    Java怀旧:About left/right shift, negative value representation and signextended(while shifting)
    Eclipse Tips
    Setup KGDB
    Java怀旧:foreach version loop, Changes to iteration variable doesn't take effect
    Java怀旧:break <label> & continue <label>,break <label>相当于简易的goto了,凑活用了
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4306255.html
Copyright © 2020-2023  润新知