jni 学习

2015/6/15 posted in  Android

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