Android 动态注册JNI函数

时间:2023-03-09 04:17:55
Android 动态注册JNI函数

1.JNI函数注册方式

在Android开发中,由于种种原因我们需要调用C/C++代码,在这个时候我们就需要使用jni了,

jni在使用时要对定义的函数进行注册,这样java才能通过native关键字定义的方法找到对应的C/C++函数

注册函数的方法有两种: 静态注册和动态注册。由于静态注册已经在上篇博客中介绍过了,这里重点介绍一下动态注册。

动态注册

由于静态注册每次添加新函数后要重新生成头文件,而且函数名又长,操作起来非常麻烦

我们可以用动态注册来避免这些麻烦 jni中提供了RegisterNatives方法来注册函数

并且我们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad 的函数

在这个函数中一般是做一些初始化设定和指定jni版本 我们可以在这个方法里面注册函数

现在我们不需要头文件,只需要 C/C++ 源文件,.mk文件也和静态注册的一样

注册函数源码

//
// Created by yuany on 6/5/18.
//
#include <jni.h>
#include "android/log.h"
#include <cassert> #define LOG_TAG "C_TAG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) //native method
jstring myDynamicJNI (JNIEnv *env, jclass jobj)
{
LOGD("hello.length");
return env->NewStringUTF("This is my first dynamic JNI test");
} /*需要注册的函数列表,放在JNINativeMethod 类型的数组中,
以后如果需要增加函数,只需在这里添加就行了
参数:
1.java代码中用native关键字声明的函数名字符串
2.签名(传进来参数类型和返回值类型的说明)
3.C/C++中对应函数的函数名(地址)
*/
static JNINativeMethod getMethods[] = {
{"sayHello","()Ljava/lang/String;",(void*)myDynamicJNI},
};
//此函数通过调用JNI中 RegisterNatives 方法来注册我们的函数
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){
LOGD("registerNativeMethods");
jclass clazz;
//找到声明native方法的类
clazz = env->FindClass(className);
if(clazz == NULL){
return JNI_FALSE;
}
LOGD("after Findclass");
//注册函数 参数:java类 所要注册的函数数组 注册函数的个数
if(env->RegisterNatives(clazz,getMethods,methodsNum) < ){
return JNI_FALSE;
}
LOGD("after RegisterNatives");
return JNI_TRUE;
} //回调函数 在这里面注册函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){ LOGD("JNI_OnLoad");
JNIEnv* env = NULL;
//判断虚拟机状态是否有问题
if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!= JNI_OK){
return -;
}
assert(env != NULL);
//指定类的路径,通过FindClass 方法来找到对应的类
const char* className = "com/example/scarecrow/dynamicregisterjni/Demo";
//开始注册函数 registerNatives -》registerNativeMethods -》env->RegisterNatives
if(!registerNativeMethods(env,className,getMethods, )){
return -;
}
//返回jni 的版本
return JNI_VERSION_1_6;
}

上面的代码就能实现动态注册jni了 以后要增加函数只需在java文件中声明native方法,在C/C++文件中实现,

并在getMethods数组添加一个元素并指明对应关系,通过ndk-build 生成so库就可以运行了。其中jni版本可以在jni.h头文件中去查看支持哪些版本,一般定义在文件最后几行。

其他文件代码

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"> <TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>

MainActivity.java

package com.example.scarecrow.dynamicregisterjni;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView; public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getName(); @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView text = findViewById(R.id.text);
Log.d(TAG, "Before");
text.setText(Demo.sayHello());
Log.d(TAG, "after");
}
}

Demo.java

package com.example.scarecrow.dynamicregisterjni;

import android.util.Log;

public class Demo {
public static final String TAG = "Demo"; static {
System.loadLibrary("JniTest");
Log.d(TAG, "dynamic lib loaded");
} public static native String sayHello();
}

app/build.gradle

apply plugin: 'com.android.application'

android {
compileSdkVersion 27
defaultConfig {
applicationId "com.example.scarecrow.dynamicregisterjni"
minSdkVersion 24
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
moduleName "JniTest"
ldLibs "log", "z", "m"
abiFilters "armeabi", "armeabi-v7a", "x86"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
ndkBuild {
path 'Android.mk'
}
}
} dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

app/Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS) LOCAL_MODULE := JniTest
LOCAL_LDFLAGS := -Wl,--build-id
LOCAL_LDLIBS := \
-llog \
-lz \
-lm \ LOCAL_SRC_FILES := \
/home/yuany/workspace/DynamicRegisterJNI/app/src/main/jni/JniTest.cpp \ LOCAL_C_INCLUDES += /home/yuany/workspace/DynamicRegisterJNI/app/src/main/jni
LOCAL_C_INCLUDES += /home/yuany/workspace/DynamicRegisterJNI/app/src/debug/jni include $(BUILD_SHARED_LIBRARY)

local.properties

## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Thu Jun 07 11:01:17 CST 2018
ndk.dir=/home/yuany/Android/Sdk/ndk-bundle
sdk.dir=/home/yuany/Android/Sdk

C/C++ - java - jni 对应参数

动态注册中 JNINativeMethod 结构体中第二个参数需注意

括号内代表传入参数的签名符号,如果为空括号内什么都不用写,括号外代表返回参数的签名符号,为空的话要写大写的V,对应关系见下表

参数对应表'
签名符号 JNI java
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
[Z jbooleanArray boolean[]
[I jintArray int[]
[J jlongArray long[]
[D jdoubleArray double[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
L完整包名加类名; jobject class

举个例子:

传入的java参数有两个 分别是 int 和 long[] 函数返回值为 String 即java函数的定义为:String getString(int a ,long[] b)

签名就应该是 "(I[J)Ljava/lang/String;"

如果有内部类 则用 $ 来分隔 如: Landroid/os/FileUtils$FileStatus; (这个我自己没有试过)

用静态注册方式注册函数时,会生成.h头文件,打开头文件,里面也可以看到对应的签名,它能够自动生成,如果实在

不知道怎么写签名,就生成头文件自己打开看一下就知道了。