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) < 0){
        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 -1;
    }
    assert(env != NULL);
    //指定类的路径,通过FindClass 方法来找到对应的类
    const char* className  = "com/example/scarecrow/dynamicregisterjni/Demo";
    //开始注册函数 registerNatives -》registerNativeMethods -》env->RegisterNatives
    if(!registerNativeMethods(env,className,getMethods, 1)){
        return -1;
    }
    //返回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头文件,打开头文件,里面也可以看到对应的签名,它能够自动生成,如果实在

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

原文地址:https://www.cnblogs.com/scarecrow-blog/p/9146127.html