基本概念
JNI
Java Native Interface,Java本地接口。
是为了方便Java调用C、C++等本地代码所封装的一层接口。
NDK
Android提供的一个工具集合,更加方便地在Android中实现JNI。
NDK提供了交叉编译器,开发人员可将JNI代码生成各指定CPU平台的动态库。
加载本地库
Java代码中通过
System.loadLibrary("library-name")
来导入本地库。注意此处的library-name是本地库的名字,不用包含后缀,Java将根据当前系统的类型自动扩展后缀名,如Linux扩展成.so,Windows扩展成.dll。
若要导入Linux下的libxxx.so,只要写
System.loadLibrary("xxx")
即可。
Native方法
-
定义Native方法有两种方式
函数命名规则来匹配和映射;
运行时动态注册(RegisterNatives)。
Java类中声明native方法
- 在开发Native方法前,先创建一个Java类,在此类中声明与Native方法对应的Java本地方法。
package com.daking.jni;
public class JNIUtil {
static {
System.loadLibrary("nativecode"); // 加载libnativecode.so
}
public static native String getLibName();
}
1. 函数命名规则来匹配和映射Native方法
- 匹配和映射Native方法的c函数命名规则为:
JNIEXPORT 返回类型 JNICALL Java_{packageName}_{className}_{functionName}(jni参数列表);
- 上面的Java类声明的native方法对应的c函数为:
JNIEXPORT jstring JNICALL Java_com_daking_jni_JNIUtil_getLibName(JNIEnv* env, jobject obj);
-
该c函数的特点是:
返回类型为jstring等JNI类型;
将{packageName}中的
.
全部改为_
。JNI方法的形参列表至少有以下两个参数:
-
JNIEnv* env
:JNI环境的引用,用env可调用所有JNI提供的函数;
-
jobject obj
:调用该函数的对象的引用。
-
2. 运行时动态注册Native方法
运行时动态注册的方式简化了函数名称,并且可以动态地更新映射关系。
首先,在C代码中实现各Native方法。
jstring getLibName(JNIEnv* env, jobject obj) {
return (*env)->NewStringUTF(env, "hello world!");
}
- 接着,创建JNINativeMethod数组。
static JNINativeMethod jniMethods[] = {
{"getLibName", "()Ljava/lang/String;", (void*)getLibName}
// 其他Java本地方法与C Native方法的映射...
};
-
此处的JNINativeMethod结构有三个成员:
- const char* name:Java中声明的native方法名
- const char* signature:native方法的签名
- void* fnPtr:c函数指针
最后,在JNI_OnLoad中调用
RegisterNatives()
注册各Native方法到JVM,建立映射关系。
static const char* className = "com/daking/jni/JNIUtil";
int JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
return JNI_ERR;
}
jclass cls = (*env)->FindClass(env, className);
if (cls == NULL)
return JNI_ERR;
int len = sizeof(jniMethods) / sizeof(jniMethods[0]);
(*env)->RegisterNatives(env, cls, jniMethods, len);
return JNI_VERSION_1_4;
}
NDK Sample Project
1. 创建Android项目
- Android项目中创建JNIUtil类,作为调用JNI的入口工具类。
package com.daking.jni;
public class JNIUtil {
static {
System.loadLibrary("nativecode");
}
public static native String getLibName();
}
2. 创建JNI项目
- JNI项目的目录结构如下:
JNIDemo
├── jni
│ ├── Android.mk
│ ├── Application.mk
│ ├── include
│ │ └── api.h
│ └── src
│ └── api.c
-
api.h
文件内容如下:
#include <jni.h>
#ifndef NATIVECODE_API_H
#define NATIVECODE_API_H
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL
Java_com_daking_jni_JNIUtil_getLibName(JNIEnv*, jobject);
#ifdef __cplusplus
}
#endif
#endif //NATIVECODE_API_H
-
api.c
文件内容如下:
#include "api.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL
Java_com_daking_recyclerdemo_jni_getLibName(JNIEnv* env, jobject thiz) {
return (*env)->NewStringUTF(env, "libnativecode.so");
}
-
Android.mk
文件内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := nativecode
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SRC_FILES := ./src/api.c
include $(BUILD_SHARED_LIBRARY)
-
Application.mk
文件内容如下:
APP_ABI := armeabi
- 在JNI项目中与jni子目录同级的路径下,执行
ndk-build
,生成so库。
3. Android项目引用so库
将JNI项目生成的so库放到
Android项目/app/src/main/jniLibs/<abi>
下即可。<abi>
有armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips、mips64。