细说JNI与NDK(一) 初体验

时间:2021-7-3 作者:qvyue

细说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 
/* 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 
#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(env->GetObjectField(thiz, jfid));
    // 打印字符串
    char * c_str = const_cast(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
  • 定义宏进行打印
#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插件更加智能,我们知道规则就好,配置好环境签名是有提示的。

细说JNI与NDK(一) 初体验
2021-04-14 00.48.56.png

关于宏看之前的文章

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。