JNI简介
2017-01-16 本文已影响216人
dafaycoding
JNI和NDK?
- JNI (Java Native Interface)是一套编程接口,用来实现java代码和其他语言(c、C++或汇编)进行交互。这里需要注意的是JNI是JAVA语言自己的特性,也就是说JNI和Android没有关系。在Windows下面用JAVA做开发也经常会用到JNI,例如:读写系统注册表等。
- NDK(Native Development Kit)是Google提供的一套工具集,可以让你其他语言(C、C++或汇编)开发 Android的 JNI。NDK可以编译多平台的so,开发人员只需要简单修改 mk 文件说明需要的平台,不需要改动任何代码,NDK就可以帮你编译出所需的so。
用JNI做应用开发难度要比JAVA难很多,门槛也要高很多,如果你对C/C++把握的不好应用还会出现难以发现的Bug!所以通常在对性能要求比较高才会使用。游戏引擎就是一个对性能要求极高的例子。另外就是如果你想把核心的一些算法或处理逻辑保护起来,选用JNI也是一个不错的方案。
为什么用JNI?
- 扩展了jvm的功能, wifi热点共享功能, wifi使用到硬件, 硬件需要驱动程序, c语言开发驱动程序.
- c/c++代码执行效率高. c可以直接操控内存. c指针可以直接根据某个地址操控内存. 执行的效率和速度提升.
- c语言的开源库(opengl-es, opencv, ffmpeg, 7zip, coscos2d-x, Unity3d)
- 车载电脑.
JNI编译特点
-
java特点: 一处编译, 到处运行. 跨平台
-
windows下可执行的二进制文件格式: exe
-
linux下可执行的二进制文件格式: elf
-
在windows系统下编译出linux系统下可执行的文件.
-
在一个平台下编译另一个平台下可执行的文件需要用到: 交叉编译.
-
交叉编译: cgywin模拟linux操作系统.
-
NDK(native develop kits) 本地开发工具集. 内部包含了交叉工具链
1、eclipse下jni开发流程简介
第一个JNI的HelloWorld程序。
1. 在MainActivity中声明一个native方法:
public native String sayHello();
2. 在工程的根目录下创建一个jni的文件夹, 并在里边创建一个Hello.c的文件.
3. 在Hello.c文件中实现MainActivity中声明的native方法.
jstring Java_com_example_jnihelloworld_MainActivity_sayHello(JNIEnv* env, jobject obj) {
// jstring (*NewStringUTF)(JNIEnv*, const char*);
char* text = "Hello from c!!!!";
return (**env).NewStringUTF(env, text);
}
4. 在jni的目录下创建一个Android.mk文件.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := example
LOCAL_SRC_FILES := Hello.c
include $(BUILD_SHARED_LIBRARY)
5. 在工程的根目录下执行ndk-build命令, 编译.so文件.
6. 在调用native方法前, 加载.so的库文件.
System.loadLibrary("example");
7. 在java代码中调用native方法, 工程会自动去找.so文件中对应实现的代码.
-
native方法名的问题: 当方法中有下划线时, 在c语言中方法的声明需要在_后面加个1: _1
-
javah 生成带有native方法的头文件.
- 使用方式: javah -jni com.example.jnihelloworld.MainActivity
- JDK1.7 需要在工程的src目录下执行上面命令.
- JDK1.6 需要在工程的bin/classes目录下执行上面命令.
- 使用方式: javah -jni com.example.jnihelloworld.MainActivity
-
Android.mk文件
# $(call )调用工具链中某个方法, 获取当前的目录. LOCAL_PATH := $(call my-dir) # 清空上一次编译工具的配置, 不会清空LOCAL_PATH的属性. include $(CLEAR_VARS) # 定义生成出来的链接库文件的名字 # 前面追加lib关键字, 如果已经加了lib关键字, 就不会在追加. # 后面追加.so后缀名. 不允许追加扩展名. LOCAL_MODULE := example # 根据指定的源文件, 去编译出.so的库文件. 当多个源文件时, 把名字以空格隔开就可以了. LOCAL_SRC_FILES := Hello1.c # 指定当前编译出来的链接库文件是什么类型. # 动态链接库: BUILD_SHARED_LIBRARY .so 文件小 # 静态链接库: BUILD_STATIC_LIBRARY .a 文件大 include $(BUILD_SHARED_LIBRARY)
- 简便开发流程 刚开始配置稍微点, 后面不需要在配置, 修改完c文件之后, 直接右键运行。*
1. 在java代码中声明native方法.
2. 在window -> preferences -> Android -> NDK 把ndk的根目录配置进去.
右键工程 Android Tools -> Add Native Support 写进去一个函数库的名字.
3. 使用javah命令生成.h的头文件, 把头文件拷贝到工程的jni目录下.
4. 实现c代码: 把生成的.h头文件引入进来(使用双引号方式引入).
5. 处理错误和代码提示: 右键工程 -> Properties -> C/C++ General -> Paths and Symbols -> Includes -> Add 把android-ndk-r9\platforms\android-9\arch-arm\usr\include配置进去.
6. 把c代码对应native方法实现了.
7. 在java代码中加载.so库文件, 调用native方法.
-
在c代码中使用android的logcat日志.
-
Android.mk文件增加以下内容
LOCAL_LDLIBS += -llog
-
C代码中增加以下内容
#include <android/log.h> #define LOG_TAG "System.out" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-
-
生成类的方法签名
javap -s com.example.callbackjava.JNI
jni开发常见错误:
1. 在jni目录下没有发现Android.mk文件
Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk
2. c文件中没有导入jni.h的头文件.
jni/CommonError.c:4:1: error: unknown type name 'JNIEXPORT'
jni/CommonError.c:4:19: error: expected '=', ',', ';', 'asm' or '__attribute__'
before 'JNICALL'
jni/CommonError.c:4:19: error: unknown type name 'JNICALL'
3. c代码实现的方法没有写形参的名字.
jni/CommonError.c: In function 'Java_com_example_commonerrordemo_MainActivity_
sayHelloInC':
jni/CommonError.c:6:3: error: parameter name omitted
jni/CommonError.c:6:3: error: parameter name omitted
jni/CommonError.c:8:13: error: 'env' undeclared (first use in this function)
jni/CommonError.c:8:13: note: each undeclared identifier is reported only once f
or each function it appears in
4. 调用native方法, 没有加载.so文件.
No implementation found for native Lcom/example/commonerrordemo/MainActivity;.sayHelloInC ()Ljava/lang/String;
5. 加载.so文件时, 名字写错.
java.lang.UnsatisfiedLinkError: Couldn't load libcommonerror.so: findLibrary returned null
6. 当前生成的arm平台下的.so文件, 运行在了x86的平台模拟器下.
java.lang.UnsatisfiedLinkError: Couldn't load libcommonerror.so: findLibrary returned null
- 解决方案: 在jni的目录下, 创建一个Application.mk, 内容如下:
# 生成所有的机器码.
APP_ABI := all
# 生成单个平台的机器码
APP_ABI := x86 armeabi
2、Elcipse下jni程序迁移到AndroidStudio
1. 设置NDK路径
选择File>Project Structure>SDK Location(快捷键:Cmd+;),指定NDK的路径。
2. 把jni文件下内容拷贝到AndroidStudio项目下面
3. AndroidStudio项目的gradle下添加下面配置
externalNativeBuild{
ndkBuild{
path file("src/main/jni/Android.mk");
}
}
如图所示
图2-13、AndroidStudio下jni开发流程简介
- 添加本地方法
public static native String hello_jni();
- 加载so文件
static {
System.loadLibrary("hello_jni"); // 注意没有前缀lib和后缀.so
}
- 利用javah命令生成JAVA所对应的JNI头文件,1、打开终端,2、将目录定位到java目录下,3、通过javah产生头文件。
- 将com_example_idea_jnitest_1_HomeActivity.h拷贝一个将扩展名改为.c,在.c中完成业务逻辑处理相关代码:
JNIEXPORT jstring JNICALL Java_com_example_idea_jnitest_11_HomeActivity_helloJni
(JNIEnv * env, jobject obj) {
return (*env)->NewStringUTF(env,"调用c代码 返回 hello!");
}
- 修改Module中Build.gradle文件,在defaultConfig段落中加入ndk编译配置。
ndk {
moduleName "hello_jni"
}
图1-2
- 此时编译会出错
Error: NDK integration is deprecated in the current plugin. Consider
trying the new experimental plugin. For details, see
http://tools.android.com/tech-docs/new-build-system/gradle-
experimental. Set "android.useDeprecatedNdk=true" in
gradle.properties to continue using the current NDK integration.
解决:
提示已经告诉我们需要在gradle.properties设置android.useDeprecatedNdk=true,设置好后点击同步按钮。
图1-3
参考:
Android Studio JNI开发入门教程
Android JNI编程—NDK编译
使用Android Studio进行JNI开发 - Mac篇