美文网首页
JNI学习笔记

JNI学习笔记

作者: c枫_撸码的日子 | 来源:发表于2018-10-16 20:21 被阅读0次

    一、前言

    最近做FM短天线客制化,硬件工程师要求插入耳机时,拉低FM短天线的en使能脚[GPIO 21脚],拔出耳机时,拉高GPIO 21。
    我想到2种实现方式,
    一是底层创建节点文件,上层通过节点文件来操作底层的GPIO脚
    二是底层提供JNI接口,上层直接调用即可。
    第一种方式已经实现了,现在趁着空闲时间,学习JNI技术,然后自己实现第二种方式。
    废话不多说,开始学习、记录、然后撸码。

    二、JNI的基础知识

    网上关于这一块的资源很多,因此主要记录一些细节问题。
    交叉编译
    指的是在一个平台上(比如:CPU架构为X86,操作系统为Windows)编译出另一个平台上(比如:CPU架构为ARM,操作系统为Linux)可以运行的本地代码。
    Google提供的NDK工具就可以完成交叉编译的工作。

    Android系统支持的cpu架构

    1.ARM(ARMV5,ARMV7,ARMV8,ARM64-V8A等)
    2.X86
    3.MIPS
    

    CPU平台:arm、x86、mipis
    操作系统平台:Windows、Linux、Mac os、Unix

    c函数的命名规则

    本地函数命名规则: Java_包名_类名_方法名(JNIEnv* env,jobject thiz)
    
    jstring Java_com_alpaca_jnidemo_JNI_sayHelloFromJNI(JNIEnv* env ,jobject j) 
    
    jstring Java_com_alpaca_jnidemo_JNI_sayHelloFrom_1JNI(JNIEnv* env ,jobject j) 
    

    注意,如果方法名要命名成sayHelloFrom_JNI(正常命名sayHelloFromJNI),要在下划线前面加1:
    sayHelloFrom_1JNI()
    下划线前面加1:表示_JNI连接前面的sayHelloFrom,是一个完整的方法名sayHelloFrom_JNI()

    第一个JNI代码
    JNIHello.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <jni.h>
    
    jstring Java_com_alpaca_jnidemo_JNI_sayHelloFromJNI(JNIEnv* env ,jobject j)  {
      char *str = "Hello From JNI!";
      return (*env)->NewStringUTF(env,str);
                //(**env).NewStringUTF(env,str);
                 //env->NewStringUTF(str);
    }
    

    JNIEnv定义如下

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

    参数1:JNIEnv * 指的是 JNINativeInterface这个结构体的一级指针
    env又是JNIEnv的一级指针,那么env就是JNINativeInterface这个结构体的二级指针
    结构体JNINativeInterface定义了大量函数指针,供JNI开发使用
    调用方式:(*env).func 或者 (**env)->func

    参数2:jobject thiz 指的是调用当前native方法的java对象(和java中的this有点类似)
    NewStringUTF方法定义在jni.h中,如下

    jstring     (*NewStringUTF)(JNIEnv*, const char*);
    jstring NewStringUTF(const char* bytes)
    { return functions->NewStringUTF(this, bytes); }
    

    JAVA和C数据类型对照表

    /* Primitive types that match up with Java equivalents. */
    typedef uint8_t  jboolean; /* unsigned 8 bits */
    typedef int8_t   jbyte;    /* signed 8 bits */
    typedef uint16_t jchar;    /* unsigned 16 bits */
    typedef int16_t  jshort;   /* signed 16 bits */
    typedef int32_t  jint;     /* signed 32 bits */
    typedef int64_t  jlong;    /* signed 64 bits */
    typedef float    jfloat;   /* 32-bit IEEE 754 */
    typedef double   jdouble;  /* 64-bit IEEE 754 */
    
    /* "cardinal indices and sizes" */
    typedef jint     jsize;
    
    /*
     * Reference types, in C.
     */
    typedef void*           jobject;
    typedef jobject         jclass;
    typedef jobject         jstring;
    typedef jobject         jarray;
    typedef jarray          jobjectArray;
    typedef jarray          jbooleanArray;
    typedef jarray          jbyteArray;
    typedef jarray          jcharArray;
    typedef jarray          jshortArray;
    typedef jarray          jintArray;
    typedef jarray          jlongArray;
    typedef jarray          jfloatArray;
    typedef jarray          jdoubleArray;
    typedef jobject         jthrowable;
    typedef jobject         jweak;
    

    三、JAVA调用C函数

    1.JAVA传递数据给C【int参数】add(int a,int b)
    因为jnit类型就是int,因此不需要做转换,直接return 即可

    typedef int32_t      jint;     /* signed 32 bits */
    

    extern "C"
    JNIEXPORT jint JNICALL
    Java_com_alpaca_jnihello_JNIUtils_add(
          JNIEnv *env, jobject instance, jint a, jint b) {
        return a+b;
    }
    

    2.JAVA传递数据给C【String类型】

    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_alpaca_jnihello_JNIUtils_sayHelloFromJNI(
          JNIEnv *env, jobject instance, jstring s) {
        const char *str = env->GetStringUTFChars(s,NULL);//拿到String字符
        //做一些你想要的操作
        return env->NewStringUTF(str);//转成jstring返回
    }
    

    3.JAVA传递int数组给C【int[]】

    JNIEXPORT jintArray JNICALL
    Java_com_alpaca_jnihello_JNIUtils_passIntArray(
          JNIEnv *env, jobject instance, jintArray intArray) {
        //获取数组首地址
        jint *array = env->GetIntArrayElements(intArray, NULL);
        //获取数组长度
        int len = env->GetArrayLength(intArray);
    
        for (int i = 0; i <len ; ++i) {
            *(array+i) += 10;//每个元素+10
        }
        //释放内存
        env->ReleaseIntArrayElements(intArray, array, 0);
        return intArray;
    }
    

    四、C调用JAVA方法(通过反射机制)

    调用思想

    1.找到对应的类
    2.找到对应成员方法或者成员变量
    3.调用相关方法
    4.释放相关资源
    

    方法签名
    JNI调用Java函数的时候,JAVA可能会存在方法重载
    例如

    void add(int a,int b)
    void add(int a int b,int c)
    

    如果调用add,怎么确定是哪一个参数呢,这时候就要用到方法签名。
    方法签名的出现就是解决方法重载问题,确定我们应该调用哪个函数。
    方法签名规则

    基本类型(注意大小写)
    boolean    z;
    byte       B;
    char       C;
    short      s;
    int        I;
    long       J;
    float      F;
    double     D;
    void       V;
    
    类
    L+类全名(其中的.用/代替) + ;    比如
    java.lang.String 对应的是 Ljava/lang/String;(注意分号)
    
    数组类型
    格式:[+类型签名
    如果是数组,则在前面加[加类型签名,比如
    int[] ->  [I
    boolean[][] ->  [[Z
    java.lang.String []  ->  [java/lang/String;  
    

    方法签名可以通过命令获取
    进入相应的目录
    E:\AndroidStudioProjects\JNIHello\app\build\intermediates\classes\debug

    javap -s +包名+类名(不要后缀.java)
    例如:
    javap -s com.alpaca.jnidemo.JNI
    

    JNIUtils .java

    package com.alpaca.jnihello;
    
    public class JNIUtils {
        public  native String sayHelloFromJNI(String s);
    
        public native int add(int a,int b);
    
        public native int[] passIntArray(int[] array);
    
        public static native void callStaticFunction(int i);
    
        public static native Integer callStaticFunction(long i,String str);
    
        public static native String[] callStaticFunction(double i[],String str[]);
    }
    
    输入命令
    E:\AndroidStudioProjects\JNIHello\app\build\intermediates\classes\debug>
    javap -s com.alpaca.jnihello.JNIUtils
    得到结果
    public class com.alpaca.jnihello.JNIUtils {
      public com.alpaca.jnihello.JNIUtils();
        descriptor: ()V
    
      public native java.lang.String sayHelloFromJNI(java.lang.String);
        descriptor: (Ljava/lang/String;)Ljava/lang/String;
    
      public native int add(int, int);
        descriptor: (II)I
    
      public native int[] passIntArray(int[]);
        descriptor: ([I)[I
    
      public static native void callStaticFunction(int);
        descriptor: (I)V
    
      public static native java.lang.Integer callStaticFunction(long, 
                                                               java.lang.String);
        descriptor: (JLjava/lang/String;)Ljava/lang/Integer;
    
      public static native java.lang.String[] callStaticFunction(double[], 
                                                               java.lang.String[]);
        descriptor: ([D[Ljava/lang/String;)[Ljava/lang/String;
    }
    
    括号里面代表参数,后面接返回值
    

    1.JNI调用java中的静态方法
    JNIUtils.java文件

    package com.alpaca.jnihello;
    
    import android.app.Activity;
    import android.widget.Toast;
    
    public class JNIUtils {
        private static Activity mContext;
        private static String name = "abc";
        public void setContext(Activity mContext) {
            this.mContext = mContext;
        }
    
    //----------------------------------Java调用JNI方法----------------------------
        public  native String sayHelloFromJNI(String s);
    
        public native int add(int a,int b);
    
        public native int[] passIntArray(int[] array);
    
    //----------------------------------JNI调用Java方法----------------------------
      //静态方法
        public static void staticFunction(String s){
            Toast.makeText(mContext,s+name,Toast.LENGTH_SHORT).show();
        }
        //jni层静态方法
        public static native void callStaticFunction();
    
    
    }
    

    c文件

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_alpaca_jnihello_JNIUtils_callStaticFunction(JNIEnv *env, jclass type) {
        //找到对应的类
        jclass mClass = env->FindClass("com/alpaca/jnihello/JNIUtils");
        if (mClass == NULL)
            return;
        //找到方法
        jmethodID id = env->GetStaticMethodID(mClass,"staticFunction","
                                                          (Ljava/lang/String;)V");
        if(id == NULL)
            return;
        //找到成员变量
        jfieldID  jfd = env->GetStaticFieldID(mClass,"name","Ljava/lang/String;");
        if(jfd == NULL)
            return;
        //修改成员变量的值
        jstring name = env->NewStringUTF("tom");
        env->SetStaticObjectField(mClass,jfd,name);
    
        //获取jstring字符串
        jstring data = env->NewStringUTF("CALL JAVA STATIC FUNCTION");
        if(data == NULL)
            return;
        //调用方法
        env->CallStaticVoidMethod(mClass,id,data);
        //释放资源
        env->DeleteLocalRef(mClass);
        env->DeleteLocalRef(data);
        env->DeleteLocalRef(name);
    }
    

    2.JNI调用java中的实例方法
    难点在于创建对象
    JNIUtils.java文件

    package com.alpaca.jnihello;
    
    import android.app.Activity;
    import android.widget.Toast;
    
    public class JNIUtils {
        private static Activity mContext;
        private static String name = "abc";
        private String address = "beijing";
    
        public void setContext(Activity mContext) {
            this.mContext = mContext;
        }
    
    
        public  native String sayHelloFromJNI(String s);
    
        public native int add(int a,int b);
    
        public native int[] passIntArray(int[] array);
    
    //-----------------------JNI调用Java方法---------------
        //1.静态方法
        public static void staticFunction(String s){
            Toast.makeText(mContext,s+name,Toast.LENGTH_SHORT).show();
        }
        public static native void callStaticFunction();
    
        //2.实例方法
        public void function(String s){
            Toast.makeText(mContext,s+address,Toast.LENGTH_SHORT).show();
        }
        public static native void callInstanceFunction();
    
    
    }
    
    

    c文件

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_alpaca_jnihello_JNIUtils_callInstanceFunction(JNIEnv *env, jclass type) {
        //找到对应的类
        jclass cls = env->FindClass("com/alpaca/jnihello/JNIUtils");
        if (cls == NULL)
            return;
        //找到成员变量
        jfieldID jfieldID1 = env->GetFieldID(cls,"address","Ljava/lang/String;");
        if (jfieldID1 == NULL)
            return;
    
        //找到要调用的方法
        jmethodID jid = env->GetMethodID(cls,"function","(Ljava/lang/String;)V");
        if (jid == NULL)
            return;
        //获取类的构造方法 名称都为 <init> 返回值都为 ()V
        jmethodID m_constrcut = env->GetMethodID(cls,"<init>","()V");
        if (m_constrcut == NULL)
            return;
        //创建对象
        jobject  jniutils =env->NewObject(cls,m_constrcut,NULL);
        if (jniutils ==NULL)
            return;
        //得到jsting字符串
        jstring msg = env->NewStringUTF("call instance method");
        jstring addr = env->NewStringUTF("  shanghai  ");
    
        //修改成员变量的值
        env->SetObjectField(jniutils,jfieldID1,addr);
    
        //调用方法
        env->CallVoidMethod(jniutils,jid,msg);
        //释放资源
        env->DeleteLocalRef(msg);
        env->DeleteLocalRef(cls);
        env->DeleteLocalRef(jniutils);
        env->DeleteLocalRef(addr);
    }
    

    五、NDK中打印log

    参考NDK之打印调试信息Log

    #include <android/log.h>
    #define LOG_TAG  "C_TAG"
    #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    

    NDK开发的阅读资料
    NDK开发(一)————如何在Android Studio下进行NDK开发
    NDK开发(二)————CMake构建NDK
    NDK开发(三)——C/C++代码如何调用java层代码
    NDK(四)——JNI中数组、引用和异常的处理

    相关文章

      网友评论

          本文标题:JNI学习笔记

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