Android JNI笔记
C语言不跨平台:
ARMv5——armeabi 第5代、第6代的ARM处理器,早期的手机用的比较多
ARMv7 ——armeabi-v7a 第7代及以上的 ARM 处理器。2011年以后的生产的大部分Android设备都使用它。
ARMv8——arm64- v8a 第8代、64位ARM处理器,很少设备,三星 Galaxy S6是其中之一。
x86——x86 平板、模拟器用得比较多。
MIPS ——mips
MIPS64——mips64
x86_64——x86_64 64位的平板。
JNI的命名规则
JNIExport jstring JNICALL Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz )
函数例子:
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s)
{
const char *str = (*env)->GetStringUTFChars(env, s, 0);
(*env)->ReleaseStringUTFChars(env, s, str);
return 10;
}
*env:一个接口指针, JNIEnv是一个线程相关的结构体,该结构体代表了Java在本线程的执行环境
obj:在本地方法中声明的对象引用
i和s:用于传递的参数
JNIEnv的作用
调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码
操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象
与JNIEnv相关的常用函数
创建Java中的对象
jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, ...):
jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args):
jobject NewObjectV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args):
创建Java类中的String对象
jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len):
创建类型为基本类型PrimitiveType的数组
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
指定一个长度然后返回相应的Java基本类型的数组
创建类型为elementClass的数组
jobjectArray NewObjectArray(JNIEnv *env, jsize length,
jclass elementClass, jobject initialElement);
获取数组中某个位置的元素
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
获取数组的长度
jsize GetArrayLength(JNIEnv *env, jarray array);
添加并编写Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ndkdemotest-jni
LOCAL_SRC_FILES := ndkdemotest.c
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH := $(call my-dir):每个Android.mk文件必须以定义开始。它用于在开发tree中查找源文件。宏my-dir则由Build System 提供。返回包含Android.mk目录路径。
include $(CLEAR_VARS) :CLEAR_VARS变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx。例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等等。但不是清理LOCAL_PATH。这个清理是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能便面相互影响。
LOCAL_MODULE := ndkdemotest-jni:LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。Build System 会自动添加适当的前缀和后缀。例如,demo,要生成动态库,则生成libdemo.so。但请注意:如果模块名字被定义为libabd,则生成libabc.so。不再添加前缀。
LOCAL_SRC_FILES := ndkdemotest.c:这行代码表示将要打包的C/C++源码。不必列出头文件,build System 会自动帮我们找出依赖文件。缺省的C++ 源码的扩展名为.cpp。
include (CLEAR_VARS)后的所有LOCAL_xxxxinx。并决定编译什么类型
BUILD_STATIC_LIBRARY:编译为静态库
BUILD_SHARED_LIBRARY:编译为动态库
BUILD_EXECUTABLE:编译为Native C 可执行程序
BUILD_PREBUILT:该模块已经预先编译
修改相应的配置文件
android {
compileSdkVersion 26
defaultConfig {
ndk{
moduleName "ndkdemotest-jni"
abiFilters "armeabi", "armeabi-v7a", "x86"
}
}
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
sourceSets.main {
jni.srcDirs = []
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
通过CMake工具
build.gradle里面的代码:
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
defaultConfig外面的 externalNativeBuild - cmake,指明了 CMakeList.txt 的路径;
defaultConfig 里面的 externalNativeBuild - cmake,主要填写 CMake 的命令参数。即由 arguments 中的参数最后转化成一个可执行的 CMake 的命令,可以在 app/externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt中查到。
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
cmake_minimum_required(VERSION 3.4.1):指定CMake的最小版本
add_library:创建一个静态或者动态库,并提供其关联的源文件路径,开发者可以定义多个库,CMake会自动去构建它们。Gradle可以自动将它们打包进APK中。
第一个参数——native-lib:是库的名称
第二个参数——SHARED:是库的类别,是动态的还是静态的
第三个参数——src/main/cpp/native-lib.cpp:是库的源文件的路径
find_library:找到一个预编译的库,并作为一个变量保存起来。由于CMake在搜索库路径的时候会包含系统库,并且CMake会检查它自己之前编译的库的名字,所以开发者需要保证开发者自行添加的库的名字的独特性。
第一个参数——log-lib:设置路径变量的名称
第一个参数—— log:指定NDK库的名子,这样CMake就可以找到这个库
target_link_libraries:指定CMake链接到目标库。开发者可以链接多个库,比如开发者可以在此定义库的构建脚本,并且预编译第三方库或者系统库。
第一个参数——native-lib:指定的目标库
第一个参数——${log-lib}:将目标库链接到NDK中的日志库,
注册native函数
静态注册native函数
public class JniDemo1{
static {
System.loadLibrary("samplelib_jni");
}
private native void nativeMethod();
}
动态注册native函数
#include <jni.h>
#include "Log4Android.h"
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#ifdef __cplusplus
extern "C" {
#endif
static const char *className = "com/gebilaolitou/jnidemo/JNIDemo2";
static void sayHello(JNIEnv *env, jobject, jlong handle) {
LOGI("JNI", "native: say hello ###");
}
static JNINativeMethod gJni_Methods_table[] = {
{"sayHello", "(J)V", (void*)sayHello},
};
static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
LOGI("JNI","Registering %s natives\n", className);
clazz = (env)->FindClass( className);
if (clazz == NULL) {
LOGE("JNI","Native registration unable to find class '%s'\n", className);
return -1;
}
int result = 0;
if ((env)->RegisterNatives(clazz, gJni_Methods_table, numMethods) < 0) {
LOGE("JNI","RegisterNatives failed for '%s'\n", className);
result = -1;
}
(env)->DeleteLocalRef(clazz);
return result;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGI("JNI", "enter jni_onload");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
jniRegisterNativeMethods(env, className, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
}
#endif
JNI中的签名
为什么JNI中突然多出了一个概念叫"签名"?
因为Java是支持函数重载的,也就是说,可以定义相同方法名,但是不同参数的方法,然后Java根据其不同的参数,找到其对应的实现的方法。这样是很好,所以说JNI肯定要支持的,那JNI要怎么支持那,如果仅仅是根据函数名,没有办法找到重载的函数的,所以为了解决这个问题,JNI就衍生了一个概念——"签名",即将参数类型和返回值类型的组合。如果拥有一个该函数的签名信息和这个函数的函数名,我们就可以顺序的找到对应的Java层中的函数了。
如果查看类中的方法的签名
javap -s -p MainActivity.class
Compiled from "MainActivity.java"
public class com.example.hellojni.MainActivity extends android.app.Activity {
static {};
Signature: ()V
public com.example.hellojni.MainActivity();
Signature: ()V
protected void onCreate(android.os.Bundle);
Signature: (Landroid/os/Bundle;)V
public boolean onCreateOptionsMenu(android.view.Menu);
Signature: (Landroid/view/Menu;)Z
public native java.lang.String stringFromJNI(); //native 方法
Signature: ()Ljava/lang/String; //签名
public native int max(int, int); //native 方法
Signature: (II)I //签名
}
JNI规范定义的函数签名信息
Z boolean
B byte
C char
S short
I int
J long
F float
D double
V void
[签名 数组
[i int[]
[Ljava/lang/Object String[]
native代码反调用Java层代码
获取Class对象
jclass jcl_string=env->FindClass("java/lang/String");
获取属性方法
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
构造一个对象
jobject NewObject(jclass clazz, jmethodID methodID, ...)
jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
obj = (*env)->NewObject(env, cls, mid);