JNI介绍
JNI概念
Java本地接口,Java Native Interface, 它是一个协议, 该协议用来沟通Java代码和外部的本地C/C++代码, 通过该协议 Java代码可以调用外部的本地代码, 外部的C/C++ 代码可以调用Java代码;
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编译后的动态共享库;
NDK 工具下载
下载地址
https://developer.android.com/ndk/downloads/index.html
Mac下执行
$ chmod a+x android-ndk-r10c-darwin-x86_64.bin
$ ./android-ndk-r10c-darwin-x86_64.bin
创建android工程
声明native方法
native 方法在java中是没有实现的,需要在jni使用c实现
package usbprinter;
/**
* Created by xuyushi on 15/7/15.
*/
public class usbprinter
{
public native byte[] read();
}
利用脚本生成头文件
jni目录中添加 xxxx.sh脚本文件
#!/bin/sh
javah -o usbprinter.h -jni -classpath ../src/main/java/ usbprinter.usbprinter
javah -o 生成头文件名.h -jni -classpath 目录名 包名.类名
给脚本添加可执行权限
$ chmod 777 xxxx.sh
执行脚本
./xxxx.sh
执行完毕后会在jni目录下生成 .h文件,如下
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class usbprinter_usbprinter */
#ifndef _Included_usbprinter_usbprinter
#define _Included_usbprinter_usbprinter
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: usbprinter_usbprinter
* Method: read
* Signature: ()[B
*/
JNIEXPORT jbyteArray JNICALL Java_usbprinter_usbprinter_read
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
编写 .c 实现文件
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "usbprinter.h"
#define BUFF_LENTH (1024*10)
JNIEXPORT jbyteArray JNICALL Java_usbprinter_usbprinter_read
(JNIEnv *env, jobject this)
{
//具体实现代码
}
- JNIEnv参数 : 代表的是Java环境, 通过这个环境可以调用Java里面的方法;
- jobject参数 : 调用C语言方法的对象, thiz对象表示当前的对象, 即调用JNI方法所在的类;
编写Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
TARGET_PLATFORM := android-19
LOCAL_MODULE := j
LOCAL_SRC_FILES := usbprinter.c
LOCAL_LDLIBS := -llog
NDK_APP_DST_DIR := ../src/main/jniLibs/$(TARGET_ARCH_ABI)
include $(BUILD_SHARED_LIBRARY)
````
1. LOCAL_PATH := $(call my-dir)
一个Android.mk file首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。
22. include $( CLEAR_VARS)
CLEAR_VARS 由编译系统提供,指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES, 等等...),除LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。
3. LOCAL_MODULE := HcSyncml
LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包 含任何空格。注意编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'HcSyncml'的共享库模块,将会生成'libHcSyncml.so'文件。
4. LOCAL_C_INCLUDES :=$(LOCAL_PATH)/extra_inc$(LOCAL_PATH)/main_inc
LOCAL_C_INCLUDES 中加入所需要包含的头文件路径
5. LOCAL_SRC_FILES
LOCAL_SRC_FILES中加入源文件路径(需要编译的文件),多个文件用 ‘\’ 隔开
6. LOCAL_LDLIBS+= -L$(SYSROOT)/usr/lib –llog
表示允许打印Log
## 编写Application.mk
```java
APP_ABI := armeabi armeabi-v7a x86
编译
NDK解压后的文件目录/ndk-build
$ /Users/xuyushi/Downloads/android-ndk-r10e/ndk-build
会在jniLibs目录下,生成响应的.so文件
Java中加载动态库
public class usbprinter
{
public native byte[] read();
//静态代码块加载C语言库文件
static {
try {
Log.e("JNI", "load usbprinter");
System.loadLibrary("usbprinter");
}catch(UnsatisfiedLinkError e){
Log.e("JNI"," load fail " + e.toString());
}
}
}
操作过程中遇到的问题
问题1
Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk
c文件中没有导入jni.h的头文件.
问题2
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'
c代码实现的方法没有写形参的名字.
问题3
jni/CommonError.c: In function 'Java_com_ycy_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
调用native方法, 没有加载.so文件.
问题4
java.lang.UnsatisfiedLinkError: Couldn't load libcommonerror.so: findLibrary returned null
当前生成的arm平台下的.so文件, 运行在了x86的平台模拟器下.
问题5
java.lang.UnsatisfiedLinkError: Couldn't load libcommonerror.so: findLibrary returned null
解决方案: 在jni的目录下, 创建一个Application.mk, 内容如下:
生成所有的机器码.
APP_ABI := all生成单个平台的机器码
APP_ABI := x86 armeabi