介绍一下Android中JNI开发的入门以及在Eclipse中使用相关工具进行开发:
一、基本概念
1、什么是JNI:java native interface( java与本地语言的接口规范 )
2、应用JNI的场景:调用C代码去操作硬件、对计算效率要求非常高的场合、对安全性要求比较高的场合
3、交叉编译:在一个平台(ios,window,linux / intel,arm,mips)上,编译出另一个平台可以运行的应用。
4、NDK: ( native develop kits ) 本地语言开发工具集,是谷歌公司给我们提供
5、CDT:eclipse中运行C语言的插件
二、学习JNI
1、学习JNI的要求:
* 熟悉java语言 * 熟悉 C语言 * 掌握JNI调用的规范
2、c语言和java语言有关数据类型的比较:
##JAVA中的基本数据类型int 占用 4 个字节byte 占用 1 个字节long 占用 8 个字节float 占用 4 个字节doublt 占用 8 个字节boolean 占用 1 个字节char 占用 2 个字节short 占用 2 个字节## C语言中的数据类型int 类型占用字节数:4float 类型占用字节数:4double 类型占用字节数:8short 类型占用字节数:2char 类型占用字节数:1long 类型占用字节数:4
比较结果:
* 在java语言和C语言中 int ,short,float,double 这些数据类型是一样的。* java语言中的byte 和boolean 可以用C语言中的char 代替* java语言中的char 类型要用C语言中的short 代替* java语言中long 要用C语言中long long 代替* java语言中的boolean 用C语言中的char 代替,如果为1 表示真,0表示假
三:NDK的目录结构
-- docs : ndk 自带的帮助文档
-- build : 编译时所需要的工具
* .sh 文件是linux 系统是的可运行脚本,类似于windows中bat 批处理命令
-- platforms : 不同的android版本与同的CPU之间,编译时,所需要的文件
* jni.h 位置在:android-ndk-r9b\platforms\android-xx\arch-arm\usr\include
-- prebuilt : 对不同的CPU进行预编译,时使用
-- samples : 提供的一些小例子
-- sources : NDK的源码
-- tests: ndk 自带的一些测试用例
-- toolchains: 不同平台编译所需要的工具链
--ndk-build.cmd :在ndk目录结构的根目录下边。在widnows中可以执行的命令, 对本地代码进行编译(编译生成.so文件)。
在命令行中使用ndk-build命令需要做一下必要的配置:需要将ndk的目录路径配置在电脑的环境变量中去,这样可以在任意路径下使用该命令。ndk-build命令会将项目中写好的c文件编译成.so文件,注意需要进入.c文件的目录路径使用该命令。
四、JNI的使用步骤
1、在java类中声明native 方法
/*** 通过关键字 native 声明一个本地方法* @return*/private native String helloJni();
2、在C代码中实现native方法
1)在当前项目中创建文件夹名称固定为jni ,并在该文件夹中新建一个.c的文件。
注:如果在eclipse中可以通过以下的方法来快捷生成对应的文件a、先关联NDK:
b、利用eclipse的工具来替我们生成对应的文件:
具体的过程如下:(注意这样必须是设置NDK的路径后才可以使用)右键项目->Android Tools -> Add Native Support -> 写入名称(我这里写的是helloJNI)->FinishEclipse会替我们生成jni文件夹其中包括两个文件:Android.mk和helloJNI.cpp(这里生成的c++文件,这里将后缀名改为.c,同时需要将Android.mk中的helloJNI.cpp也改为helloJNI.c)
2)声明与native方法对应的C方法
/*** 1、 查询jni.h 头文件,找到与java中String 类型匹配的c语言的类型 jstring ,就是函数返回值的类型* 2、声明函数名:* Java_包名_类名_方法名(注意包名中的点都要换成下划线)
* 3、添加二个固定的参数:* JNIEnv* env -- jni开发环境的引用
* jobject -- 是调用该函数的java对象的引用
*/jstring Java_zz_itcast_jnihello1_MainActivity_helloJni(JNIEnv* env,jobject obj){}
注:这里的方法名称非常容易写错,因此建议使用命令javah来自动生成具体的方法为: 如果是java1.6 javah命令操作的是class 文件,应在\bin\classes\ 目录执行 如果是java1.7 javah命令操作的是java 文件, 应在 \src\ 目录中执行 步骤: 根据自己的java版本进入项目中对应的目录,打开命令行,使用命令javah+空格+类的全名 这里我使用的命令为:javah com.example.myjnitest.MainActivity 命令完成后会生成一个com_example_myjnitest_MainActivity.h的文件,将其打开内容如下:
#include <jni.h> /* Header for class com_example_myjnitest_MainActivity */ #ifndef _Included_com_example_myjnitest_MainActivity #define _Included_com_example_myjnitest_MainActivity #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_myjnitest_MainActivity * Method: helloJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_myjnitest_MainActivity_helloJNI (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
这里的方法名就自动生成了,我们将JNIEXPORT jstring JNICALL Java_com_example_myjnitest_MainActivity_helloJNI(JNIEnv * env, jobject obj)拷贝出来放到我们的.c文件中就是对应的方法名称,这里要注意加上方法签名中形参的名称
3)实现该C方法,并返回对应的数据类型
char * msg = "hello i am from c"; // 声明一个要返回的字符串// 查询 jni.h中 JNINativeInterface 结构体:// jstring (*NewStringUTF)(JNIEnv*, const char*); NewStringUTF是一个指向返回jstring类型的函数的指针return (*env)->NewStringUTF(env,msg);//这里查看jni.h可以知道*env表示JNINativeInterface 这个结构体,调用它的方法//NewStringUTF,也可以写成(*(*env)).NewStringUTF(env,msg);
4)注意:在C文件中 添加 #include <jni.h>
3、将C代码编成可执行的二进制文件
1、在jni目录中添加 Android.mk 的配置文件,( 该配置文件会告诉ndk,我们要编译哪个模块,以及哪些源文件。如果在Eclipse中生成.c文件的话就会同时生 成这个文件,就不需要再次重写了),内容参照docs文档(NDK中)
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := helloJNI // 我们定义的模块名 最终编译的 so 文件,以此命名 LOCAL_SRC_FILES := helloJNI.c // 该模块包含的c文件 如果有多个文件,以空格间隔 include $(BUILD_SHARED_LIBRARY)
2、在.c文件和Android.mk同级目录中,执行ndk-build 命令(命令为:ndk-build)
*注意:ndk-build.cmd文件是ndk 安装目录根目录中的文件,应将该文件的目录,添加至系统环境变量PATH ,中以方便在其他目录中调用
3、注意,这里如果想要编译成支持所有系统的so文件需要在项目jni目录下新建Application.mk文件其中的内容为:APP_ABI := all
这里如果在Eclipse中使用了NDK集成开发环境就不要我们手动的执行第2步中的ndk-build命令了,因为运行时Eclipse会帮我们进行编译的
4、在JAVA类中,加载编译生成的so文件
static{ // 加载so 文件 System.loadLibrary("helloJNI "); }完成以上所有步骤后,就可以在java类方法中调用native方法了。
下边是我的文件具体代码:MainActivity.java文件的内容:package com.example.myjnitest; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void btnClick(View v){ Toast.makeText(this, helloJNI(), 1).show();//调用native方法 } private native String helloJNI(); static{ //静态代码库,加载我们的so文件 System.loadLibrary("helloJNI"); } }
activity_main.xml文件的内容:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="btnClick" android:text="点击调用C代码" /> </LinearLayout>
helloJNI.c的内容:注意:如果如下代码在项目中报错是因为没有关联源码(我们需要关联一下代码)关联的代码的步骤:
选中项目:快捷键Alt+Enter -> C/C++ General -> Paths and Symbols -> 在Includes选项卡中选中add(将对于ndk路径中的代码关联) ->ok
我的关联路径为:D:\Programandroid-ndk-r9b\platforms\android-19\arch-arm\usr\include其中platforms以后的路径可以根据需要选不同的版本及不同的 系统的代码。如果还是报错,在确定代码无误的情况下,将Problems下的错误delete掉后直接运行,这个错误是由于Eclipse自身的问题导致的,我们不必考虑#include <jni.h> #include<stdlib.h> #include<jni.h> // 实现 java中声明的native 方法 // private native String helloJni(); /** * 1\ 查询jni.h 头文件,找到与java中String 类型匹配的c语言的类型 jstring ,就是函数返回值的类型 * 2\声明函数名: * Java_包名_类名_方法名 * 3\添加二个固定的参数: * JNIEnv* env -- jni开发环境的引用 * jobject -- 是调用该函数的java对象的引用 */ JNIEXPORT jstring JNICALL Java_com_example_myjnitest_MainActivity_helloJNI(JNIEnv* env,jobject obj){ char * msg = "hello i am from c"; // 查询 JNINativeInterface 结构体中内容: // jstring (*NewStringUTF)(JNIEnv*, const char*); //return (*(*env)).NewStringUTF(env,msg); return (*env)->NewStringUTF(env,msg); };
Android.mk的内容:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := helloJNI LOCAL_SRC_FILES := helloJNI.c include $(BUILD_SHARED_LIBRARY)
Application.mk 的内容:APP_ABI := all
下面附上我的例子地址:有需要的可以点击免费下载:点击打开链接