Android Native开发入门之JNI

简介

JNI是Java Native Interface(Java本地接口)的缩写,JNI不是Android专有,而是从Java继承而来。Android作为一种嵌入式操作系统,大量个驱动、硬件相关的功能底层功能都必须在native层实现,JNI的作用和重要性大大增强。


使用场景

  1. 使用Native层API
  2. 复用已有的C/C++库
  3. 性能要求高
  4. 跨平台
  5. 安全

helloworld

定义:实现一个jni函数,输入int数组,输出逗号拼接的字符串

  1. java类中声明native方法
    package com.jack.hellojniworld;
    public class MainActivity extends AppCompatActivity {
    
    // Used to load the 'hello_jni_world-lib' library on application startup.
    static {
        System.loadLibrary("hello_jni_world");
    }
    
    public native String jniIntArrayToString(int[] intParams);
    }
    
  2. Terminal中使用命令生成头文件,并把头文件拷贝至cpp目录
    ~~~/Users/jack.zhou/soft/project/HelloJniWorld
    jackzhoudeMacBook-Pro:HelloJniWorld jack.zhou$ cd app/src/main/java/
    jackzhoudeMacBook-Pro:java jack.zhou$ javah -jni com.jack.hellojniworld.MainActivity
    jackzhoudeMacBook-Pro:java jack.zhou$ ls
    com                                     com_jack_hellojniworld_MainActivity.h
    jackzhoudeMacBook-Pro:java jack.zhou$ cat com_jack_hellojniworld_MainActivity.h 
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_jack_hellojniworld_MainActivity */
    
    #ifndef _Included_com_jack_hellojniworld_MainActivity
    #define _Included_com_jack_hellojniworld_MainActivity
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_jack_hellojniworld_MainActivity
     * Method:    jniIntArrayToString
     * Signature: ([I)Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_com_jack_hellojniworld_MainActivity_jniIntArrayToString
      (JNIEnv *, jobject, jintArray);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
  3. 在cpp下新建com_jack_hellojniworld_MainActivity.cpp,添加实现逻辑
    #include "com_jack_hellojniworld_MainActivity.h"
    #include "string"
    using namespace std;
    /**
     * [1,2,3] -> 1,2,3
     *
     * @param env JNIEnv
     * @param intArrayParams 输入的int数组
     * @return 用逗号拼接的字符串
     */
    extern "C"  jstring Java_com_jack_hellojniworld_MainActivity_jniIntArrayToString(JNIEnv *env, jobject, jintArray intArrayParams) {
        string ret;
        int paramCount = env->GetArrayLength(intArrayParams);
        jint *paramValues = env->GetIntArrayElements(intArrayParams, nullptr);
        for (int i = 0; i < paramCount; ++i) {
            jint *curParamValue = (paramValues + i);
            ret += to_string(*curParamValue);
            if (i != paramCount - 1) {
                ret += ",";
            }
        }
        env->ReleaseIntArrayElements(intArrayParams, paramValues, 0);
        return env->NewStringUTF(ret.c_str());
    }
    
  4. 总结
    • JNI定义了一套自己的数据类型,包括原始类型和引用类型,定义在jni.h。
      /* 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 */
      
      /*Reference types, in C++*/
      class _jobject {};
      class _jclass : public _jobject {};
      class _jstring : public _jobject {};
      class _jarray : public _jobject {};
      class _jobjectArray : public _jarray {};
      class _jbooleanArray : public _jarray {};
      class _jbyteArray : public _jarray {};
      class _jcharArray : public _jarray {};
      class _jshortArray : public _jarray {};
      class _jintArray : public _jarray {};
      class _jlongArray : public _jarray {};
      class _jfloatArray : public _jarray {};
      class _jdoubleArray : public _jarray {};
      class _jthrowable : public _jobject {};
      
      typedef _jobject*       jobject;
      typedef _jclass*        jclass;
      typedef _jstring*       jstring;
      typedef _jarray*        jarray;
      typedef _jobjectArray*  jobjectArray;
      typedef _jbooleanArray* jbooleanArray;
      typedef _jbyteArray*    jbyteArray;
      typedef _jcharArray*    jcharArray;
      typedef _jshortArray*   jshortArray;
      typedef _jintArray*     jintArray;
      typedef _jlongArray*    jlongArray;
      typedef _jfloatArray*   jfloatArray;
      typedef _jdoubleArray*  jdoubleArray;
      typedef _jthrowable*    jthrowable;
      typedef _jobject*       jweak;
      
    1. JNIEnv中定义了一套与native层数据交互的函数
      struct _JNIEnv {
      /* do not rename this; it does not seem to be entirely opaque */
      const struct JNINativeInterface* functions;
      jint GetVersion()
      { return functions->GetVersion(this); }
      jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
          jsize bufLen)
      { return functions->DefineClass(this, name, loader, buf, bufLen); }
      jclass FindClass(const char* name)
      { return functions->FindClass(this, name); }
      ...
      }
      

Native层操作Java对象

1.定义java类

public class JniInvokeTestClass {

    private String mStringField = "defaultStr";

    private static String sStringStaticField = "defaultStaticStr";

    public JniInvokeTestClass() {
        Log.d(MainActivity.TAG, "non param constructor invoked!");
    }

    public JniInvokeTestClass(String stringField) {
        Log.d(MainActivity.TAG, "param constructor invoked! param:" + stringField);
        this.mStringField = stringField;
    }

    public void print() {
        Log.d(MainActivity.TAG, "print invoke!mStringField:" + mStringField);
    }

    public static void staticPrint() {
        Log.d(MainActivity.TAG, "printStringStaticField invoke!sStringStaticField:" + sStringStaticField);
    }

    public void setStringField(String mStringField) {
        this.mStringField = mStringField;
    }

    public static void setStringStaticField(String sStringStaticField) {
        JniInvokeTestClass.sStringStaticField = sStringStaticField;
    }
}
  1. java层调用
public void testJniObjectToObject() {
        Log.d(TAG, "testJniObjectToObject start");
        JniInvokeTestClass jniInvokeTestClass = new JniInvokeTestClass("java common field");
        JniInvokeTestClass.setStringStaticField("java static field");
        JniInvokeTestClass ret = jniObjectToObject(jniInvokeTestClass);
        Log.d(TAG, "testJniObjectToObject ret is same object? "+(jniInvokeTestClass == ret));
    }
public native JniInvokeTestClass jniObjectToObject(JniInvokeTestClass params);
  1. native层调用对象
/**
 *
 * @param env JNIEnv
 * @param object 调用native方法的对象,实际上是MainActivity
 * @param param JniInvokeTestClass对象
 */
extern "C" jobject
Java_com_jack_hellojniworld_MainActivity_jniObjectToObject(JNIEnv *env, jobject object,
                                                           jobject param) {
    jclass jniInvokeTestClassClazz = env->GetObjectClass(param);
    //获取字段ID
    jfieldID stringFieldId = env->GetFieldID(jniInvokeTestClassClazz, "mStringField",
                                             "Ljava/lang/String;");
    jfieldID stringStaticFieldId = env->GetStaticFieldID(jniInvokeTestClassClazz,
                                                         "sStringStaticField",
                                                         "Ljava/lang/String;");
    //获取方法ID
    jmethodID printMethodId = env->GetMethodID(jniInvokeTestClassClazz, "print", "()V");
    jmethodID staticPrintMethodId = env->GetStaticMethodID(jniInvokeTestClassClazz, "staticPrint",
                                                           "()V");
    //获取字段值
    jstring stringFieldValue = static_cast<jstring>(env->GetObjectField(param, stringFieldId));
    jstring staticStringFieldValue = static_cast<jstring>(env->GetStaticObjectField(
            jniInvokeTestClassClazz, stringStaticFieldId));
    LOGD("receive param field value...");
    logd(env, "stringFieldValue:%s", stringFieldValue);
    logd(env, "staticStringFieldValue:%s", staticStringFieldValue);
    //设置字段值
    env->SetObjectField(param, stringFieldId, env->NewStringUTF("jni common field"));
    env->SetStaticObjectField(jniInvokeTestClassClazz, stringStaticFieldId,
                              env->NewStringUTF("jni static field"));
    LOGD("modify field value done!");
    //调用方法
    env->CallVoidMethod(param, printMethodId);
    env->CallStaticVoidMethod(jniInvokeTestClassClazz, staticPrintMethodId);
    //构造新实例,并打印
    LOGD("constructor new object!");
    jmethodID constructorMethodId = env->GetMethodID(jniInvokeTestClassClazz, "<init>", "()V");
    jobject nJniInvokeTestObject = env->NewObject(jniInvokeTestClassClazz, constructorMethodId);
    env->CallVoidMethod(nJniInvokeTestObject, printMethodId);
    env->CallStaticVoidMethod(jniInvokeTestClassClazz, staticPrintMethodId);
    return nJniInvokeTestObject;
}

4.运行
日志

  • 访问字段/方法,需要先通过名字和签名获取对象的FieldID和MethodID,再通过Get<field_type>Field/Set<field_type>Field/GetStatic<field_type>Field/SetStatic<field_type>Field读取和修改属性,通过Call<Return_Type>Method/CallStatic<Return_Type>Method调用方法。-访问字段/方法,需要先通过名字和签名获取对象的FieldID和MethodID,再通过Get<field_type>Field/Set<field_type>Field/GetStatic<field_type>Field/SetStatic<field_type>Field读取和修改属性,通过Call<Return_Type>Method/CallStatic<Return_Type>Method调用方法。

  • 签名
    在这里插入图片描述
    boolean字段签名:Z;
    int数组字段签名:[I;
    对象类型字段签名:Ljava/lang/String;
    (I)V: 一个整型参数,无返回值
    ([ILjava/lang/String)Z: 参数1整型数值,参数2字符类型,返回boolean

异常处理

JniEnv定义的异常处理函数:

jboolean ExceptionCheck();//检查是否发生异常
jthrowable ExceptionOccurred()//返回异常(和java层Throwable不相同,java层try-catch不能捕获)或者null
void ExceptionDescribe();//logcat日志中打印异常信息
void ExceptionClear();//清除异常,表明异常已被处理
jint Throw(jthrowable obj);
jint ThrowNew(jclass clazz, const char* message)//抛出java异常

demo

jboolean Java_com_jack_hellojniworld_MainActivity_jniHandleException(JNIEnv *env, jobject object) {
    LOGD("jniHandleException!");
    jclass clazz = env->FindClass("java/lang/XString");
    if(env->ExceptionCheck() == JNI_TRUE){
        LOGD("ExceptionOccurred!");
        env->ExceptionDescribe();
        //clear之后标志异常已被处理
        //不清除,JNI会正常返回,java层可以try-catch
        env->ExceptionClear();
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

void Java_com_jack_hellojniworld_MainActivity_jniThrowException(JNIEnv *env, jobject object) {
    LOGD("jniThrowException!");
    jclass clazz = env->FindClass("java/lang/XString");
    jthrowable throwable = env->ExceptionOccurred();
    if(throwable != nullptr){
        LOGD("ExceptionOccurred!");
        //不能直接抛出,会直接crash,外层java无法try-catch
        //需要重新创建并抛出java层异常
        //env->Throw(throwable);
        env->ExceptionClear();
        jclass throwClazz = env->FindClass("java/lang/RuntimeException");
        env->ThrowNew(throwClazz, "jni ThrowNew");
    }
}

启用C++ STL Exception特性

JNI中的引用

JNI中有三种应用:1.LocalReference(局部引用) 2.GlobalReference(全局引用)3.WeakGlobalReference弱全局引用。JNI中创建的对象同样由GC负责回收,和Java层创建的对象具有一样的生命周期。

  • 局部引用
    大部分JNI函数返回的局部引用,局部引用不能在后续的调用中被缓存和重用,一旦native函数返回,局部引用即被释放。存在局部引用表,本地函数执行期间生产的java对象会被JNIEnv隐式存放在表中,保证函数执行期间不会被回收。本地引用存在数量限制,需要注意控制对象规模,必要时提前释放局部引用

    void DeleteLocalRef(jobject object)
    jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
    
  • 全局引用
    存在全局引用表,其中的java对象不会被回收,可以手动创建全局引用实现对象复用。

    jobject NewGlobalRef(jobject obj);
    void DeleteGlobalRef(jobject object);
    
  • 弱全局应用
    如果对象只有弱全局引用,下次垃圾回收将会被回收。

    jweak NewWeakGlobalRef(jobject obj);
    void DeleteWeakGlobalRef(jweak weak);
    jboolean IsSameObject(jobject ref1, jobject ref2)	
    

其中IsSameObject只能用于判断弱全局引用是否有效;手动删除或者缓存局部引用,会出现JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x15。

测试demo

extern "C" void
Java_com_jack_hellojniworld_MainActivity_jniTestJniRef(JNIEnv *env, jobject object) {
    //测试本地引用移除
//    jstring str = env->NewStringUTF("local_ref_str");
//    env->DeleteLocalRef(str);
//    str = nullptr;
//    LOGD("jniTestJniRef sleeping...");
//    sleep(15);
//    //使用被删除的引用直接报error. 暂时未找到判断引用无效的办法,需要手动置null
//    //JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x15
//    const char * chars = env->GetStringUTFChars(str, nullptr);
//    LOGD("jniTestJniRef chars:%s", chars);

//缓存本地引用测试
//    if (cachedString == nullptr) {
//        cachedString = env->NewStringUTF("local_ref_str");
//        LOGD("jniTestJniRef set cache...");
//    } else {
//        //JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x15
//        //不支持IsSameObject测试
//        if (env->IsSameObject(cachedString, nullptr) != JNI_TRUE) {
//            const char *chars = env->GetStringUTFChars(static_cast<jstring>(cachedWeakString),
//                                                       nullptr);
//            LOGD("jniTestJniRef from cache...s%s", chars);
//        } else {
//            LOGD("jniTestJniRef from cache...not valid!");
//        }
//    };

//缓存全局引用测试
//    if (cachedString == nullptr) {
//        jstring jGlobalStr = env->NewStringUTF("local_ref_str_to_global_ref");
//        cachedString = static_cast<jstring>(env->NewGlobalRef(jGlobalStr));
//        LOGD("jniTestJniRef set cache...");
//    } else {
//        const char *chars = env->GetStringUTFChars(cachedString, nullptr);
//        LOGD("jniTestJniRef from cache...s%s", chars);
//    }

//全局弱应用测试
//profile手动gc,会被回收
//    if (cachedWeakString == nullptr) {
//        jstring jWeakGlobalStr = env->NewStringUTF("local_ref_str_to_weak_global_ref");
//        cachedWeakString = env->NewWeakGlobalRef(jWeakGlobalStr);
//        LOGD("jniTestJniRef set cache...");
//    } else {
//        if (env->IsSameObject(cachedWeakString, nullptr) != JNI_TRUE) {
//            const char *chars = env->GetStringUTFChars(static_cast<jstring>(cachedWeakString),
//                                                       nullptr);
//            LOGD("jniTestJniRef from cache...s%s", chars);
//        } else {
//            LOGD("jniTestJniRef from cache...not valid!");
//        }
//    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值