zoukankan      html  css  js  c++  java
  • 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头文件,打开头文件,里面也可以看到对应的签名,它能够自动生成,如果实在

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

  • 相关阅读:
    Redisson分布式锁学习总结:公平锁 RedissonFairLock#lock 获取锁源码分析
    Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析
    Redisson分布式锁学习总结:公平锁 RedissonFairLock#unLock 释放锁源码分析
    npm更改为淘宝镜像
    博客园统计阅读量
    自动下载MarkDown格式会议论文的程序
    修改linux ll 命令的日期显示格式
    Canal 实战 | 第一篇:SpringBoot 整合 Canal + RabbitMQ 实现监听 MySQL 数据库同步更新 Redis 缓存
    Log4j2 Jndi 漏洞原理解析、复盘
    一个菜鸡技术人员,很另类的总结
  • 原文地址:https://www.cnblogs.com/scarecrow-blog/p/9146127.html
Copyright © 2011-2022 走看看