JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。
NDK是一系列工具的集合,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
JNI(Java Native Interface)是Java调用Native机制,是Java语言自己的特性。类似的还有微软.Net Framework上的p/invoke,可以让C#或Visual Basic.Net可以调用C/C++的API,所以说JNI和Android没有关系,在PC上开发Java的应用,如果运行在Windows平台使用JNI是是经常的,比如说读写Windows的注册表。
而NDK(Native SDK)是Google公司推出的帮助Android开发者通过C/C++本地语言编写应用的开发包,包含了C/C++的头文件、库文件、说明文档和示例代码,我们可以理解为Windows Platform SDK一样,是纯C/C++编写的,但是Android并不支持纯C/C++编写的应用,同时NDK提供的库和函数功能很有限,仅仅处理些算法效率敏感的问题
简单点说,用C语言生成一个库文件,在java中通过JNI机制调用这个库文件的函数。而JNI的过程比较复杂,生成.so需要大量操作,而NDK就是简化了这个过程。
关于交叉编译
public void start() throws IllegalStateException {
stayAwake(true);
_start();
}
private native void _start() throws IllegalStateException;
/**
* Stops playback after playback has been stopped or paused.
*
* @throws IllegalStateException if the internal player engine has not been
* initialized.
*/
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
}
private native void _stop() throws IllegalStateException;
安卓体系的四层,上面的应用层和框架层都是用Java写的,第三层的核心库是Java写的,别的很多库是用C\C++写的,底层的驱动什么也是C\C++
DEMO
1.先要建一个工程,在工程里建一个folder,里面加一个 .c的file
2.在布局里加一个button
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击调用本地C方法"
android:onClick="click"/>
3.mainactivity里响应,同时我们在这里要调用native的C函数,这个函数返回一个字符串,用toast弹出这个字符串
注意,Java里只是调用这个函数,函数的具体实现是在 .c里
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//toast返回的字符串
public void click(View v){
Toast.makeText(this, getStringFromC(), Toast.LENGTH_LONG).show();
}
//使用本地C语言写一个函数,负责返回一个字符串
private native String getStringFromC();
}
4.完成这个函数
4.1关于函数名,应该是 Java+包名+类名+函数名; 点都要换成下划线
4.2 关于返回值,这里要去头文件里看C和JAVA的对应列表
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
#include<jni.h>
jstring Java_com_example_myjnidemo_MainActivity_getStringFromC(JNIEnv* env,jobject thiz){
char* cstr = "helloword";
return cstr;
}
注意参数是必须的,JNI规范里的规定,都是在jni.h里定义
后者是一个指向任一类型的指针,就是指的MainActivity的引用
前者是一个结构体的指针,那么参数里传的是一个指针的指针,是一个二级指针
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
4.3一个*找到指针,二级*找到结构体,找到结构体后,直接点,就能调用其中的函数,不过需要上面的两个参数
这里要返回string值,调用JNI里的方法,NewStringUTF,第二个参数应该是指向需要被转换成string的字符串数组的指针,并且可以加一个const,常量化,防止指针被修改
#include<jni.h>
jstring Java_com_example_myjnidemo_MainActivity_getStringFromC(JNIEnv* env,jobject thiz){
char* cstr = "helloword";
//调用JNI里的方法,将字符数组转换成string
jstring jstr = (*(*env)).NewStringUTF(env,cstr);
return jstr;
}
在Java中,如果是无参,这里只需要传那两个参数即可;如果是有参,那么这里还要在后面再传参数
4.4 Android.mk 文件,放到c原文件同文件夹下,里面要表明源文件和目标文件,用来编译
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#该模块编译后需要生成的lib库的名称
LOCAL_MODULE :=helloworld
#编译该模块需要用到的src源文件
LOCAL_SRC_FILES :=helloworld.c
include $(BUILD_SHARED_LIBRARY)
4.5 编译
交叉编译,变异成 .so库,需要使用 ndk-build.cmd来编译
右击C程序源文件,properties,找到文件的文件夹路径
命令行
进入D盘
进入文件夹
cmd指令
lib库里生成 .so
4.6 java里加载类库,否则提示找不到本地方法
public class MainActivity extends Activity {
//传进来的要和mk里的module名相同
static{
System.loadLibrary("helloworld");
}
@Override
放在static里,随着类的加载自动调用
传进来的要和module里的定义相同
OK,可以跑起来了
另外,如果此时在C文件里做了修改,要重新利用命令行编译一下~
如果实在Linux下,直接生成了 .so ,拿来就可以用,无需自己编译
还有,可以让eclipse编译,而无需自己用命令行手动编译
这里需要在preference下的安卓选项台里去设置路径
有可能出现这里没有NDK选项的情况,需要更新一下SDK,版本问题,详情请看这篇文章 解决缺少NDK选项问题,点击以打开
右击工程名,点击add native support
他会自动生成jni文件夹和里面的 cpp文件和mk文件,如果是想用C的话,改下文件名和mk里的源文件名
这时候还要添加c\c++的path and symbols
右击工程名,preference,c\c++ general,path and symbols,add,file system -NDK里的platforms下的对应includes
OK,可以写C了,写完函数之后就可以直接跑了,无需手动编译