细说JNI与NDK(一) 初体验

作者: zcwfeng | 来源:发表于2021-04-14 00:53 被阅读0次

    细说JNI与NDK专题目录:

    细说JNI与NDK(一) 初体验
    细说JNI与NDK(二)基本操作)
    细说JNI与NDK(三)ndk 配置说明
    细说JNI与NDK(四)动态和静态注册
    细说JNI与NDK(五)JNI 线程
    细说JNI与NDK(六)静态缓存,异常捕获,内置函数
    细说JNI与NDK(七)Parcel底层JNI思想与OpenCV简单对比

    简单概念关系

    JNI :ava Native Interface,即 Java 本地接口,JNI 是 Java 调用 Native 语言的一种特性,属于 Java 的,与 Android 无直接关系
    作用: 使得 Java 与 本地其他类型语言(如 C、C++)交互。
    实际中的驱动都是 C/C++开发的,通过 JNI,Java 可以调用 C/c++实现的驱动,从 而扩展 Java 虚拟机的能力。另外,在高效率的数学运算、游戏的实时渲染、音 视频的编码和解码等方面,一般都是用 C 开发的

    (Java 代码 里调用 C/C++等语言代码 或 C/C++代码调用 Java 代码)

    JNI相当于桥梁

    Kotlin/Java <---> JNI <---> C/C++

    NDK Android 平台提供的 Native 开发工具集开发包 Native Development Kit。把 JNI,拿到 NDK 里面来并进行封装 (JNI,gcc,g++,...)

    环境

    jni.h 的引入,两个jni环境,一个是NDK对应的工具集中的jni.h。一个是JDK对应的环境中的jni.h

    我们引入使用的是NDK中的jni.h

    需要我们安装好本地Java环境。利用javah 手动生成说明原理。正常开发都是用Android Studio 自动快速生成,不可能在刀耕火种。

    我们先创建一个Activity,的native方法演示

    public class JavaJNIActivity extends AppCompatActivity {
    
        static {
            System.loadLibrary("native-lib");
        }
    
        public static final int A = 100;
    
        //Java 本地方法 实现:native层--->this
        public native String getStringPwd();
    
        //native static---->class
        public static native String getStringPwd2();
    
        //我们用native 修改java字段
        public String name = "David";
        public static int age = 0;
    
        // java交互
        public native void changeName();
    
        public static native void changeAge();
    
        public  native void callAddMethod();
    
        // 函数,native调用java
        public int add(int num1, int num2) {
            return num1 + num2;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_java_jni);
    
            changeName();
    
            TextView tv = findViewById(R.id.name_text);
            tv.setText(name);
    
            changeAge();
            TextView tva = findViewById(R.id.age_text);
            tva.setText(age + "");
    
            callAddMethod();
        }
    }
    

    用命令 javah 包名.类,生成头文件 top_zcwfeng_jni_JavaJNIActivity.h

    javah top.zcwfeng.jni.JavaJNIActivity

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class top_zcwfeng_jni_JavaJNIActivity */
    
    #ifndef _Included_top_zcwfeng_jni_JavaJNIActivity
    #define _Included_top_zcwfeng_jni_JavaJNIActivity
    #ifdef __cplusplus
    extern "C" {
    #endif
    #undef top_zcwfeng_jni_JavaJNIActivity_A
    #define top_zcwfeng_jni_JavaJNIActivity_A 100L
    /*
     * Class:     top_zcwfeng_jni_JavaJNIActivity
     * Method:    getStringPwd
     * Signature: ()Ljava/lang/String;
     */
    extern "C"
    JNIEXPORT jstring JNICALL Java_top_zcwfeng_jni_JavaJNIActivity_getStringPwd
      (JNIEnv *, jobject);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    定义了final int A 就会生成一个宏。具体看上述注释
    C++是可以重载的,所以用external C 声明用C的语法,不允许重载

    接下来我们需要实现函数 其他方法就不定义在.h ,只是演示javah 的用法,实际开发不怎么用,直接在cpp实现

    #include "top_zcwfeng_jni_JavaJNIActivity.h"
    #include <android/log.h>
    #define LOG_TAG "native_zcw"
    
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    #define LOGD(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    
    // 实现 native
    extern "C" JNIEXPORT jstring
    JNICALL Java_top_zcwfeng_jni_JavaJNIActivity_getStringPwd
            (JNIEnv *env, jobject jobj){
    
    }
    // static native
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_top_zcwfeng_jni_JavaJNIActivity_getStringPwd2(JNIEnv *env, jclass clazz) {
    }
    //--------------------------
    extern "C"
    JNIEXPORT void JNICALL
    Java_top_zcwfeng_jni_JavaJNIActivity_changeName(JNIEnv *env, jobject thiz) {
        // 获取class
        jclass jcs = env->GetObjectClass(thiz);
    //    jfieldID GetFieldID(jclass clazz, const char* name 方法名, const char* sig属性签名)
        jfieldID jfid = env->GetFieldID(jcs,"name","Ljava/lang/String;");
    //    jobject GetObjectField(jobject obj, jfieldID fieldID),用到了指针转换
        jstring j_str = static_cast<jstring>(env->GetObjectField(thiz, jfid));
        // 打印字符串
        char * c_str = const_cast<char *>(env->GetStringUTFChars(j_str, NULL));
        LOGD("native: %s",c_str);
        // 修改Tesla,必须写出jstring才能转换
        jstring jName = env->NewStringUTF("Tesla");
        env->SetObjectField(thiz,jfid,jName);
    
    }
    
    
    //--------------------------
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_top_zcwfeng_jni_JavaJNIActivity_changeAge(JNIEnv *env, jclass clazz) {
        const char * sig = "I";
    
        jfieldID jfid = env->GetStaticFieldID(clazz,"age",sig);
        jint age = env->GetStaticIntField(clazz,jfid);
        age += 10;
        //jint----int
        env->SetStaticIntField(clazz,jfid,age);
    
    }
    
    //--------------------------
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_top_zcwfeng_jni_JavaJNIActivity_callAddMethod(JNIEnv *env, jobject jobj) {
        // 自己得到class
        jclass javaJNIActivityClass = env->GetObjectClass(jobj);
    
    //        jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
        jmethodID jmid = env->GetMethodID(javaJNIActivityClass,"add","(II)I");
        // 调用Java 方法
        jint sum = env->CallIntMethod(jobj,jmid,3,4);
        LOGE("add sum result ->java:%d",sum);
    }
    
    

    至于为设么用external "C" 需要看Jni.h 源码

    jni.h 中我们能看到这部分代码

    #if defined(__cplusplus)
    typedef _JNIEnv JNIEnv;
    typedef _JavaVM JavaVM;
    #else
    typedef const struct JNINativeInterface* JNIEnv;
    typedef const struct JNIInvokeInterface* JavaVM;
    #endif
    

    else C的方式,JNINativeInterface 结构体指针

    struct JNINativeInterface {
        void*       reserved0;
        void*       reserved1;
        void*       reserved2;
        void*       reserved3;
    ......
    };
    

    JNINativeInterface 这里面所有的函数就是我们要用到的
    C++ 中的 _JNIEnv,最终调用的还是C中JNINativeInterface

    struct _JNIEnv {
        /* do not rename this; it does not seem to be entirely opaque */
        const struct JNINativeInterface* functions;
    ......
    

    无论是C/C++最终还是调用的C,所以必须用external "C"

    • JNIEXPORT // 标记该方法可以被外部调用
    • jstring // Java <---> native 转换用的
    • JNICALL // 代表是 JNI标记
    • Java_包名类名方法名 ,注意:我们的包名:" _" ---> native :" _1"
    • JNIEnv * env JNI:的桥梁环境 300多个函数,所以的JNI操作,必须靠他
    • jobject jobj 谁调用,就是谁的实例 MainActivity this
    • jclass clazz 谁调用,就是谁的class MainActivity.class
    • android 打印需要log---》#include <android/log.h>
    • 定义宏进行打印
    #define LOG_TAG "native_zcw"
    
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    #define LOGD(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    
    • jint----int 查看源码最终jint最终也是int
    • 代码演示
      ① Java-JNI 声明 static 和 非 static方法的区别
      ② 定义final int 会在C中定义宏方式体现
      ③ JNI在C,android 打印Log的方式
      ④ 用native 修改java字段
      ⑤ java通过Jni与native交互,java调用native实现方法。native调用java实现并传参

    JNI 常用签名规则

       签名规则 大写
    
       javap -s -p MainActivity     必须是.class
    
        Java的boolean  --- Z  注意点
        Java的byte  --- B
        Java的char  --- C
        Java的short  --- S
        Java的int  --- I
        Java的long  --- J     注意点
        Java的float  --- F
        Java的double  --- D
        Java的void  --- V
        Java的引用类型  --- Lxxx/xxx/xx/类名;
        Java的String  --- Ljava/lang/String;
        Java的array  int[]  --- [I         double[][][] --- [[[D
        int add(char c1, char c2) ---- (CC)I
        void a()     ===  ()V
    
        javap -s -p xxx.class    -s 输出xxxx.class的所有属性和方法的签名,   -p 忽略私有公开的所有属性方法全部输出
    
    

    现在Android Studio插件更加智能,我们知道规则就好,配置好环境签名是有提示的。

    2021-04-14 00.48.56.png

    关于宏看之前的文章

    相关文章

      网友评论

        本文标题:细说JNI与NDK(一) 初体验

        本文链接:https://www.haomeiwen.com/subject/bexxlltx.html