理论部分:
android ndk就是Java里面jni技术,当然会略有点区别,但总体一致。android 项目要使用ndk开发主要有两大原因:
1,能够使用已有的类库(很多开源库c++/c为基础)在基础上进行开发,有些功能java无法实现(解码库)或者效率问题(这个比较少)
2,考虑源代码保护,c++/c编译成类库比Java更难破解
学习ndk开发应用程序首先掌握jni语法,可以参考jni手册或者oracle网站进行了解
首先看一下数据类型:
Java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
和Java类型一一对应,了解更多详细内容,自行查询jni手册
jni函数:看一个简单的函数
// 返回字符串"hello load jni" JNIEXPORT jstring JNICALL native_hello(JNIEnv* env,jobject thiz, jclass clazz) { return (*env)->NewStringUTF(env, "hello load jni."); }说一下参数含义:
其中 JENEnv* 代表的是java环境 , 通过这个环境可以调用java的方法,jobject 表示哪个对象调用了 这个c语言的方法, thiz就表示的是当前的对象
JNI方法注册到jvm有两种:
1,静态注册(方法名称为Java_packegeName_包含native方法声明class_native method name)
2,动态注册(通过JNI_OnLoad()实现,jvm启动时调用该方法,在该方法内注册方法签名即可)后面介绍
两种方法的区别如下:
静态:先由Java得到本地方法的声明,然后再通过JNI实现该声明方法。
动态:先通过JNI重载JNI_OnLoad()实现本地方法,然后直接在Java中调用本地方法。
ndk编译:
1,需要下载ndk开发包,解压配置环境变量,和sdk配置差不多
下载地址:http://developer.android.com/tools/sdk/ndk/index.html
2,下载cygwin软件在windows下模拟Linux环境编译器
下载地址:http://www.cygwin.com/
相关文章:http://blog.csdn.net/buildon/article/details/711536
http://www.cnblogs.com/playing/archive/2011/07/14/2106727.html
编译.so类库
相关文章:http://blog.csdn.net/android_panda/article/details/7607946
http://blog.csdn.net/wangkuifeng0118/article/details/7311724
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView(R.layout.activity_main); TextView tv = new TextView(this); tv.setText( stringFromJNI() ); setContentView(tv); } /* A native method that is implemented by the * 'hello-jni' native library, which is packaged * with this application. */ public native String stringFromJNI(); static { //导入的lib名去掉前面的lib System.loadLibrary("hello-jni"); }
2.build下项目,打开cmd,cd到该工程bin目录下,
D:\>cd D:\android\rayn\TestJni\bin
D:\android\rayn\TestJni\bin>javah -jni com.stay.jni.CallNative
然后在bin目录下会出现一个.h文件这是声明的头文件
3.我们要的是里面声明的方法,把我们要写的c文件改成相同的方法名就可以了。
4.在工程里建个jni目录,将改好的c文件和mk文件(随便copy一个,改下路径和lib名就好啦)copy进去
常见错误:
错误1:当输入cd &NDK时出现no such file or Director时
原因1:在配置目录下cygwin\home\Andy\下的.bash_profile时配置的ndk所在路径有错误(
正确格式NDK=/cygdrive/d/adt-bundle-windows-x86/android-ndk-r8e
export NDK)
原因2:没有在系统环境变量中配置ndk(与sdk路径配置一样)
错误2:native method not found
原因:.c或.cpp内的函数名与Javah 编译生成包名不一致所导致
关于ndk详细介绍,可参考这篇文章
http://blog.chinaunix.net/uid-10008293-id-2972294.html
http://blog.csdn.net/zhangjie201412/article/category/1086150
第一步:打开命令行切换目录到xx.java目录下 javac命令编译:
第二部编译头文件:当执行javah -jni xxx文件是会出现找不到文件情况
解决办法:将目录切换到项目中src下(例如项目目录E:\workspace4.2\HelloJavaJni\src\com\sample\jni)
即可在src目录下生成xx.h头文件
问题原因:环境变量的设置不正确,正确设置如下:path为"%JAVA_HOME%\bin"
ClassPath设置为".;%JAVA_HOME%\lib"
JAVA_HOME为"E:\Java\jdk1.6.0_13" (JDK的安装路径)
另外:需要先编译,后生成头文件:
下面介绍一下JNA技术:
JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。使用JNI调用共享类库(.dll/.so文件)是非常麻烦的事情,既需要编写java代码,又要编写C语言的代理方法,这其中需要很多数据类型的转换,是让人非常头痛。JNA框架就是为了解决这些问题和繁琐的事情而开发的,
它提供一组Java工具类用于在运行期动态访问系统本地共享类库而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,
JNA将自动实现Java接口到native function的映射,大大降低了Java调用本体共享库的开发难度。JNA与.NET平台上的P/Invoke机制一样简单和方便。
假设有一个动态链接库: Jna.dll。里面有这样一个函数
void sayHello(char * name){
printf("C Code Start...\n");
printf("Hello! Mr %s.\n",name);
printf("C Code End.\n");
}
为了调用这个函数,使用JNA,我们需要编写下面的JAVA代码:
public interface ICnblogsJna extends Library {
// 接口实例
ICnblogsJna INSTANCE = (ICnblogsJna) Native.loadLibrary("CnblogsJna",ICnblogsJna.class);
// 与C代码映射的函数
public void sayHello(String name);
}
接口内部需要一个公共静态常量INSTANCE, 通过这个常量,就可以获得这个接口的实例,从而使用接口的方法。也就是调用动态链接库CnblogsJna.dll中的sayHello函数了。
如果使用JNI,你需要使用System.loadLibrary方法,来加载我们专为JNI编写的动态链接库,这个动态链接库实际上是我们真正需要的动态链接库的代理。使用JNA是,
需要用JNA类库的Native类的loadLibrary函数,是直接把我们需要的动态链接库载入进来。使用JNA,我们不需要编写作为代理的动态链接库,不需要编写一行原生代码。
Native类的loadLibrary方法有两个参数:第一个参数是.dll或者.so文件的名字,但不带后缀名。这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。
第二个参数是本接口的Class类型,JNA通过这个Class类型,根据指定的dll/.so文件,动态创建接口的实例
// 调用动态链库中的sayHello函数
ICnblogsJna.INSTANCE.sayHello("jna sayhello test!");
由于跨平台和跨语言尤其自身无法克服的确定,所以尽量少跨平台、跨语言传递数据。如果必须这样做,也尽量使用简单的数据类型。如果有复杂的数据类型需要在Java和原生函数中传递,
那么我们就必须在Java中模拟这种复杂的原生类型。这将大大增加实现的难度,甚至无法实现。如果在Java和原生函数间存在大量的数据传递,一方面,会损失程序的性能
;另一方面会造成内存碎片,Java调用原生函数时,会把数据固定在内存中,这样原生函数才可以访问这些Java数据。这些数据,JVM的GC不能管理,就会造成内存碎片。