Android C++ 学习笔记 (一) —— 使用JNI

澳门新浦京娱乐游戏 4

Mark:

4.4 缓存字段ID和措施ID

备注:

取得单个域值须求调用两到四个JNI函数,存在质量难题,日常景况下都以一向从Java层传输native方法调用,并非让Native方法去拜候java中的域值。

jstringMyNewString(JNIEnv*env,constchar*chars,jintlen){jclassstringClass;jmethodIDcid;jbyteArrayelemArr;jstringresult;jstringjencoding;stringClass=env-FindClass(java/lang/String);if(stringClass==NULL){returnNULL;/*exceptionthrown*/}/*GetthemethodIDfortheString(byte[]data,StringcharsetName)constructor*/cid=env-GetMethodID(stringClass,init,([BLjava/lang/String;)V);if(cid==NULL){returnNULL;/*exceptionthrown*/}jencoding=env-NewStringUTF(GBK);/*Createabyte[]thatholdsthestringcharacters*/elemArr=env-NewByteArray(len);if(elemArr==NULL){returnNULL;/*exceptionthrown*/}env-SetByteArrayRegion(elemArr,0,len,(jbyte*)chars);/*Constructajava.lang.Stringobject*/result=(jstring)(env-NewObject(stringClass,cid,elemArr,jencoding));/*Freelocalreferences*/env-DeleteLocalRef(elemArr);env-DeleteLocalRef(stringClass);returnresult;}

4.1.2 字段描述符

访问域

Java有两类域: 实例域 和
静态域。全体实例分享三个静态域,实例有投机的实例域别本。访问域的操作如下:
JniHelper中增添五个变量和三个native方法:

    // 静态域
    private static String staticField = "Static Field";
    // 实例域
    private String instanceField = "Instance Field";
    // 静态域与实例域
    public native void instanceFieldAndStaticField();

extern "C"
JNIEXPORT void JNICALL
Java_com_cgfay_ndklearningproject_JniHelper_instanceFieldAndStaticField(JNIEnv *env,
                                                                        jobject instance) {
    // 获取class对象
    jclass clazz =  env->GetObjectClass(instance);
    // 获取实例域的Id
    jfieldID instanceFieldId = env->GetFieldID(clazz, "instanceField", "Ljava/lang/String;");
    // 根据实例域Id获取实际的java 的字符串,也就是获取实例域
    jstring instanceField = (jstring) env->GetObjectField(instance, instanceFieldId);
    const char *instanceStr;
    jboolean isCopy;
    instanceStr = env->GetStringUTFChars(instanceField, &isCopy);
    ALOGI("instance field: %s", instanceStr);
    env->ReleaseStringUTFChars(instanceField, instanceStr);

    // 获取静态域的Id
    jfieldID staticFieldId = env->GetStaticFieldID(clazz, "staticField", "Ljava/lang/String;");
    // 根据静态域Id获取实际的Java的字符串,也就是获取静态域
    jstring staticField = (jstring) env->GetStaticObjectField(clazz, staticFieldId);
    const char *staticStr;
    staticStr = env->GetStringUTFChars(staticField, 0);
    ALOGI("instance field: %s", staticStr);
    env->ReleaseStringUTFChars(staticField, staticStr);

}

里头,”Ljava/lang/String;” 那个一而再会讲到。今后您只要精晓那是取得String
类型就可以。
打字与印刷输出的Log如下:

澳门新浦京娱乐游戏 1

访问域输出

以下格局 总归 仍然在运用使用java的string:java/lang/String

为了访谈一个对象的实例字段,当地点法需求做两步:
首先,通过在类援引上调用GetFieldID获取田野(fieldState of QatarID(字段ID)、字段名字和字段描述符:
Fid=(env)->GetFieldID(env,cls,”s”,”Ljava/lang/String;”);
上例中的代码通过在对象引用obj上调用GetObjectClass获取到类援引。一旦取获得字段ID,你就足以把目的和字段ID作为参数来拜候字段:
Jstr=(
env)->GetObjectField(env,obj,fid);
因为字符串和数组是超过常规规的目的,所以大家利用GetObjectField来访谈字符串类型的实例字段。除了Get/SetObjectField,JNI还援救任何如GetIntField、SetFloatField等用于访谈基本项目字段的函数。

String 类型

先放段代码作为开头,本段重要调用java中到布局函数,本段代码达成,字符串字符集转变。

/* Get the method ID for the String(char[]) constructor /
cid = (
env)->GetMethodID(env, stringClass,
“<init>”, “([C)V”);
if (cid == NULL) {
return NULL; /* exception thrown */
}

新建叁个Java String
std::string str = "hello world";
jstring javaString = env->NewStringUTF(str.c_str())
P/PPREclass=cppname=code/**youcanusethesefunctionfreely,butpleasekeepthisdeclarationwithnomodified.**createdbyjerry.lin,2011-08-04*/#includestdio.h#includejni.h#includestring.h/**convertthecharstringtoJString*parameter:*[in]pszSrc:thesourcestringtobeconverted.*[in]pszEncoding:theencodingofpszSrc,ifitisnull,meansutf8**returnvalue:*returntheconveredstringifissuccessful,otherwisereturnNULL.*/jstringCharToJString(JNIEnv*env,constchar*pszSrc,constchar*pszEncoding){jstringjstr=NULL;jclassstringClass=NULL;jbyteArraybyteElemArr=NULL;do{jmethodIDcid=NULL;jstringjstrEncoding=NULL;constchar*pszEnco=NULL;intnStrLen=0;/*checktheincomeparameters*/if(NULL==pszSrc||NULL==env){break;}/*gettheStringclassofjavaanditsconstructortocreateanewobject*//*gettheStringclassofjava*/stringClass=env-FindClass(java/lang/String);if(NULL==stringClass){break;}/*GetthemethodIDforthejava.lang.Stringconstructor*/cid=env-GetMethodID(stringClass,init,([BLjava/lang/String;)V);if(NULL==cid){break;}if(NULL==pszEncoding){pszEnco=utf-8;}else{pszEnco=pszEncoding;}jstrEncoding=env-NewStringUTF(pszEnco);if(NULL==jstrEncoding){break;}/*putcharstringintoarrayofjava*/nStrLen=(int)strlen(pszSrc);byteElemArr=env-NewByteArray(nStrLen);if(NULL==byteElemArr){break;}env-SetByteArrayRegion(byteElemArr,0,nStrLen,(jbyte*)pszSrc);/*createannew0objectofjava.lang.Stringwiththeconstructorstring(byte[],string)*/jstr=(jstring)(env-NewObject(stringClass,cid,byteElemArr,jstrEncoding));}while(0);/*Freelocalreferences*/if(NULL!=byteElemArr){env-DeleteLocalRef(byteElemArr);}if(NULL!=stringClass){env-DeleteLocalRef(stringClass);}returnjstr;}/**converttheJStringtocharstring.**parameter:*[in]jstr:thesourcestringtobeconverted.*[in]pszEncoding:theencodingtowhichtheJStringtobeconverted.*[out]pszOutBuf:thebuffertoremainthechangedstring.*[in]nBufLen:thelengthofthebuffer.**returnvalue:*thelengthofthestringinbytes,thathasbeencopiedintopszOutBuf.*/intJStringToChar(JNIEnv*env,jstringjstr,constchar*pszEncoding,char*pszOutBuf,intnBufLen){intnRet=-1;jclassstringClass=NULL;jbyteArraybyteElemArr=NULL;do{jmethodIDcid=NULL;jstringjstrEncoding=NULL;constchar*pszEnco=NULL;intnStrLen=0;/*checktheincomeparameters*/if(NULL==jstr||NULL==env){break;}/*gettheStringclassofjava*/stringClass=env-FindClass(java/lang/String);if(NULL==stringClass){break;}/*GetthemethodIDforthejava.lang.StringgetBytes(String)*/cid=env-GetMethodID(stringClass,getBytes,(java/lang/String;)[B);if(NULL==cid){break;}if(NULL==pszEncoding){pszEnco=utf-8;}else{pszEnco=pszEncoding;}jstrEncoding=env-NewStringUTF(pszEnco);if(NULL==jstrEncoding){break;}/*getcharstringintoarraywithdesignatedencode*/byteElemArr=(jbyteArray)env-CallObjectMethod(jstr,cid,jstrEncoding);if(NULL==byteElemArr){break;}nStrLen=(int)(env-GetArrayLength(byteElemArr));/*toreturnthelengthofthecharstring,ifnBufLenis0,orpszOutBufisNULL*/if(0==nBufLen||NULL==pszOutBuf){nRet=nStrLen;break;}/*ifthenBufLennStrLen,returnfailed*/if(nBufLennStrLen){nRet=-2;break;}/*getthecontentofthebyteArray,andcopyintotheoutbuffer*/jbyte*pszBytes=env-GetByteArrayElements(byteElemArr,JNI_FALSE);if(NULL!=pszBytes){memcpy(pszOutBuf,pszBytes,nStrLen);pszOutBuf[nStrLen]=0;nRet=nStrLen;}}while(0);/*Freelocalreferences*/if(NULL!=byteElemArr){env-DeleteLocalRef(byteElemArr);}if(NULL!=stringClass){env-DeleteLocalRef(stringClass);}returnnRet;}/PREBRBRP/PPBR/PPRE/PREPRE/PRE

}
当MyNewString方法第一遍被调用时,大家计算java.lang.String的布局方法的ID,并积存在静态变量cid中。
4.4.2 类的静态开头化进度中缓存字段和形式ID
我们在动用时缓存字段和形式的ID的话,每便当地方法被调用时都要检查ID是还是不是曾经被缓存。许多地方下,在字段ID和议程ID被利用前就初阶化是很方便的。VM在调用二个类的主意和字段在此之前,都会试行类的静态开首化进度,所以在静态开始化该类的历程中总结并缓存字段ID和办法ID是个不利的选用。
举个例子,为了缓存InstanceMethodCall.callback的点子ID,大家引进了叁个新的地点方法initIDs,那几个格局在InstanceMethodCall的静态初阶化进程中被调用。代码如下:
class InstanceMethodCall {
private static native void initIDs();
private native void nativeMethod();
private void callback() {
System.out.println(“In Java”);
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary(“InstanceMethodCall”);
initIDs();
}
}
与4.2节中的代码相比较,上边这段代码多了两行,initIDs方法大致鬼盖兵简政并缓存方法ID:
jmethodID MID_InstanceMethodCall_callback;

创建字节缓冲区
unsigned char *buffer  = (unsigned char *) malloc(1024);
...
jobject directBuffer = env->NewDirectByteBuffer(buffer, 1024);

这边将字符集调换的全部代码贴上,记下以备忘,其它倘使对我们有帮带,那就更加好了。

 private native void accessField();
 public static void main(String args[]) {
     InstanceFieldAccess c = new InstanceFieldAccess();
     c.s = "abc";
     c.accessField();
     System.out.println("In Java:");
     System.out.println("  c.s = "" + c.s + """);
 }
 static {
     System.loadLibrary("InstanceFieldAccess");
 }
原生线程

JNI通过JavaVM接口指针提供了AttachCurrentThread
函数便于让原生代码将原生线程附着到虚构机上。JavaVM接口指针应该及早被缓存,不然不能够被拿走
将最近线程与设想机附着和分手:

JavaVM *cachedJvm;

JNIEnv *env;

// 将当前线程附着到虚拟机
cachedJvm->AttachCurrentThread(&env, NULL);

/* 可以用JNIEnv 接口实现线程与Java应用程序的通信 */
...

// 将当前线程与虚拟机分离
cachedJvm->DetachCurrentThread();

线程使用例子如下:
在JniHelper中增多以下措施:

    /**
     * native 线程回调
     */
    private String threadCallBack() {
        return "thread call back";
    }

    // 开始线程
    public native void startThread();

native层实现如下:

/**
 * 线程操作例子
 */

JavaVM* javaVM;
jobject gInstance;

static void *thread_sample(void* argv) {
    JNIEnv *env;
    // 从全局的JavaVM中获取环境变量
    javaVM->AttachCurrentThread(&env, NULL);
    ALOGI("log in another thread!!!!");
    // 获取Java层对应的类
    jclass clazz = (jclass) env->GetObjectClass(gInstance);
    // 获取方法
    jmethodID id = env->GetMethodID(clazz, "threadCallBack", "()Ljava/lang/String;");
    jstring jStr = (jstring) env->CallObjectMethod(gInstance, id);
    const char *instanceStr;
    instanceStr = env->GetStringUTFChars(jStr, 0);
    ALOGI("instance method: %s", instanceStr);
    env->ReleaseStringUTFChars(jStr, instanceStr);
    sleep(1);
    // 删除全局变量
    env->DeleteGlobalRef((jobject)gInstance);
    javaVM->DetachCurrentThread();

    return NULL;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_cgfay_ndklearningproject_JniHelper_startThread(JNIEnv *env, jobject instance) {

    // 获取JavaVM
    env->GetJavaVM(&javaVM);
    // 创建全局对象
    gInstance = env->NewGlobalRef(instance);

    // 创建新线程
    pthread_t thread;

    pthread_create(&thread, NULL, thread_sample, NULL);
}

末尾编写翻译运维,可以见到以下的Log:

澳门新浦京娱乐游戏 2

线程使用log

工程的Github 地址如下:
NDKLearningProject

二、JStringToChar。

4.2.2 生成方法描述符

同步

JNI允许原生代码应用Java对象同步,虚构机保证仓库储存监视器的线程能够安全奉行,而别的线程等待监视器对象造成可用状态。
Java 同步代码块

synchronized(obj) {
// 同步线程安全代码块
}

等价原生代码块:

if (env->MonitorEnter(obj) == JNI_OK) {
// 错误处理
}

// 同步线程安全代码块


if (env->MonitorExit(obj) == JNI_OK) {
// 错误处理
}

java/native调用会比java/java调用慢上2~3倍,而即便native/java调用,理论上和java/native成效相像,不过,实际上,由于膝下的施用比前面五个多得多,所以java/native大概取得了十足多优化,那样,native/java调用大概比java/native调用慢上10倍之多。

}
InstanceFieldAccess这一个类定义了二个对象字段s。main方法创造了叁个目的并设置s的值,然后调用本地点法InstanceFieldAccess.accessField在本地代码中打字与印刷s的值,并把它订正为三个新值。当地方法重返后,JAVA中把这几个值再打字与印刷二回,能够看出来,字段s的值已经被转移了。上面是地点方法的兑现:
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv env, jobject obj)
{
jfieldID fid; /
store the field ID *澳门新浦京娱乐游戏 ,/
jstring jstr;
const char *str;

static {
        System.loadLibrary("native-lib");
    }

以下代码在转中匈牙利(Magyarország卡塔尔(قطر‎语混合的字符串时会出错,原因是参数中的char*。更适用的接口大概应该如下:

 stringClass = (*env)->FindClass(env, "java/lang/String");
 if (stringClass == NULL) {
     return NULL; /* exception thrown */
 }
弱全局引用

1、成立弱全局援引

jclass weakGlobalClazz = env->NewWeakGlobalRef(localClazz);

2、弱全局援用的卓有作用考验

if (env->IsSameObject(weakGlobalClazz, NULL) == JNI_FALSE) {
// 对象仍然处于活动状态且可以使用
} else {
// 对象回收,不能使用
}

3、删除弱全局援用

env->DeleteWeakGlobalRef(weakGlobalClazz);

治根的办法 照旧完整地在native code 中得以完结。推荐能够行使开源代码icu
也许iconv

收获字段ID和方法ID时,需求用字段、方法的名字和描述符进行三个追寻。检索进度绝对相比较费时,因而本节探究用缓存本领来收缩这些进程带给的损耗。缓存字段ID和形式ID的章程首要有两种。二种有别于首要在于缓存发生的随即,是在字段ID和章程ID被利用的时候,如故定义字段和措施的类静态初叶化的时候。

然后,我们看见native-lib.cpp文件中定义了扳平的法门:

jstringCharToJString(JNIEnv*env,constvoid*pszSrc,intnLen,constchar*pszEncoding);intJStringToChar(JNIEnv*env,jstringjstr,constchar*pszEncoding,void*pszOutBuf,intnBufLen);

当地点法Java_InstanceMethodCall_nativeMethod的兑现演示了在本土代码中调用JAVA方法的两步:
1、
本地点法首先调用JNI函数GetMethodID。那么些函数在内定的类中寻觅对应的措施。那一个找出进程是依照方法描述符的。假使措施不设有,GetMethodID再次回到NULL。那时,顿时从本土方法中回到,并吸引叁个NoSuchMethodError错误。
2、 本地点法通过调用CallVoidMethod来调用再次回到值为void的实例方法。
除开CallVoidMethod那个函数以外,JNI也支持对再次回到值为其余门类的法子的调用。借使您调用的办法再次来到值类型为int,你的地面方法会利用CallIntMethod。形似地,你能够调用CallObjectMethod来调用再次回到值为java.lang.String、数组等目的类型的法门。
你也能够选用Call<Type>Method体系的函数来调用接口方法。你必须从接口类型中获得格局ID,上面的代码演示了什么在java.lang.Thread实例下面调用Runnable.run方法:
jobject thd = …; /* a java.lang.Thread instance /
jmethodID mid;
jclass runnableIntf =
(
env)->FindClass(env, “java/lang/Runnable”);
if (runnableIntf == NULL) {
… /* error handling /
}
mid = (
env)->GetMethodID(env, runnableIntf, “run”, “()V”);
if (mid == NULL) {
… /* error handling /
}
(
env)->CallVoidMethod(env, thd, mid);
… /* check for possible exceptions */
在3.3.5中,大家采取FindClass来获得二个类的援用,在这里间,大家得以学到如何收获一个接口的引用。

破获与抛出十分

在JniHelper中定义多少个形式:

    /**
     * native层捕获异常与抛出异常
     * @throws NullPointerException
     */
    private void throwingMethod() throws NullPointerException {
        throw new NullPointerException("Null Pointer!");
    }
    // 捕获异常
    public native void acessMethods();

native层的贯彻如下:

/**
 * 捕获异常与抛出异常
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_cgfay_ndklearningproject_JniHelper_acessMethods(JNIEnv *env, jobject instance) {

    jthrowable ex;
    jclass clazz = env->GetObjectClass(instance);
    // 获取跑出异常的方法
    jmethodID throwingMethodId = env->GetMethodID(clazz, "throwingMethod", "()V");
    // 调用方法
    env->CallVoidMethod(instance, throwingMethodId);
    // 查询虚拟机中是否有挂起的异常(显式做异常处理)
    ex = env->ExceptionOccurred();
    if (ex != 0) {
        // 描述
        env->ExceptionDescribe();
        // 使用完成需要清除异常
        env->ExceptionClear();

        // 抛出异常
        jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
        if (newExcCls) {
            env->ThrowNew(clazz, "Exception message");
        }
    }

}

调用acessMethods 方法后,你就拜望到如下所示的极其:

澳门新浦京娱乐游戏 3

特别处理

一、CharToJString。

4.2.1 调用实例方法

接下来大家回去Java层的MainActivity.java文件,可以看来诸如以下的代码:

揭发了,其实便是在调用java.lang.String的点子。

4.1.1 访谈二个对象字段的流水生产线

调用方法

java有两类格局 ——
实例方法和静态方法。上边介绍怎样使用Native方法反过来调用java层的点子。
在JniHelper中增多多少个措施:

    /**
     * native调用的静态方法
     * @return
     */
    private static String staticMethod() {
        return "Static Method";
    }

    /**
     * native调用的实例方法
     * @return
     */
    private String instanceMethod() {
        return "Instance Method";
    }

    // 静态方法与实例方法
    public native void instanceMethodAndStaticMethod();

做客方法的不二等秘书技如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_cgfay_ndklearningproject_JniHelper_instanceMethodAndStaticMethod(JNIEnv *env,
                                                                          jobject instance) {

    // 获取class对象
    jclass clazz = env->GetObjectClass(instance);
    // 获取实例方法Id
    jmethodID instanceMethodId = env->GetMethodID(clazz, "instanceMethod", "()Ljava/lang/String;");
    jstring instanceMethodResult = (jstring) env->CallObjectMethod(instance, instanceMethodId);
    const char *instanceStr;
    instanceStr = env->GetStringUTFChars(instanceMethodResult, 0);
    ALOGI("instance method: %s", instanceStr);
    env->ReleaseStringUTFChars(instanceMethodResult, instanceStr);

    // 获取静态方法Id
    jmethodID staticMethodId = env->GetStaticMethodID(clazz, "staticMethod", "()Ljava/lang/String;");
    jstring staticMethodResult = (jstring) env->CallStaticObjectMethod(clazz, staticMethodId);
    const char *staticStr;
    staticStr = env->GetStringUTFChars(staticMethodResult, 0);
    ALOGI("static method call: %s", staticStr);
    env->ReleaseStringUTFChars(staticMethodResult, staticStr);
}

个中,”(卡塔尔国Ljava/lang/String;”
表示的是办法的具名,富含参数和重回值。后续将会介绍到。编写翻译运转,则足以看来打字与印刷输出一下Log:

澳门新浦京娱乐游戏 4

拜会方法输出

如上代码时是参谋《The JavaTM NativeInterfaceProgrammer’s Guide and
Specification》改的。

字段ID和方法ID能够在字段的值被访谈照旧措施被回调的时候缓存起来。上边包车型大巴代码中把字段ID存款和储蓄在静态变量在那之中,那样当本地点法被另行调用时,不必又一次找寻字段ID:
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv env, jobject obj)
{
static jfieldID fid_s = NULL; /
cached field ID for s */

可怜管理

native中的非凡行为与Java中的有所分化。大家能够在native层发生极其后回传给java层做拍卖。

android本身也是用了开源的库:iconv,只私下认可是未对外导出。

}
StaticFieldAccess那个类包涵二个静态字段si,main方法成立了叁个目的,早先化静态字段,然后调用本地点法StaticFieldAccess.accessField在地头代码中打字与印刷静态字段中的值,然后设置新的值,为了演示那几个值确实被改成了,在本土方法重返后,JAVA中另行那个静态字段的值。
下边是地面方法StaticFieldAccess.accessField的实现:
JNIEXPORT void JNICALL
Java_StaticFieldAccess_accessField(JNIEnv env, jobject obj)
{
jfieldID fid; /
store the field ID */
jint si;

在Android Studio 新建多少个满含C++的工程,新建完毕后,能够在app
module目录下看看多个称呼CMakeLists.txt的公文,大家开垦该文件,能够见到以下的剧情,作者对中间的cmake代码做了讲授:

4.1.3 访谈静态字段

public native String stringFromJNI();

现行反革命,你明白了哪些通过JNI来访谈JVM中的基本类型数据和字符串、数组那样的引用类型数据,下一步就是上学怎样和JVM中任意对象的字段和措施开展互相。比方从地点代码中调用JAVA中的方法,约等于平常说的来源于地方方法中的callbacks(回调)。
我们从进行字段访谈和办法回调时索要的JNI函数起初上课。本章的稍后有的我们会探讨哪些通过一些cache(缓存)技巧来优化这个操作。在结尾,大家还商谈谈从本土代码中访问字段和回调方法时的频率难题。

一贯缓冲区获取

一贯得到原生字节数组的内部存储器地址:

unsigned char* buffer = (unsigned char *) env->GetDirectBufferAddress(directBuffer);

4.1 访谈字段

NIO操作

NIO(Native
I/OState of Qatar在缓冲处理区、大面积互联网和文件I/O以至字符集协助地方的脾性有所校勘。NIO缓冲区的多寡传送质量较好,更切合在原生代码和Java应用程序之间传递大批量数量。

 private native void accessField();
 public static void main(String args[]) {
     StaticFieldAccess c = new StaticFieldAccess();
     StaticFieldAccess.si = 100;
     c.accessField();
     System.out.println("In Java:");
     System.out.println("  StaticFieldAccess.si = " + si);
 }
 static {
     System.loadLibrary("StaticFieldAccess");
 }
线程

虚构机扶助运维时原生代码,但有节制:
只在原生方法推行时期以致正在执行原生方法的线程意况下有个别援引是卓有功能的,局地援用无法在八线程间分享
被传送给每种原生方法的JNIEnv接口指针在于方法调用相关的线程中也是可行的,它不可能被其它线程缓存或使用

4.2 调用方法

有的和大局援引

在上一节大家接收过八个奇特的C字符串“Ljava/lang/String”来表示一个JVM中的字段类型。那一个字符串被称为JNI
田野同志 descriptor(字段描述符)。
字符串的从头到尾的经过由字段被声称的档案的次序决定。举例,使用“I”来表示四个int类型的字段,“F”来代表三个float类型的字段,“D”来表示二个double类型的字段,“Z”来代表一个boolean类型的字段等等。
像java.lang.String那样的引用类型的叙说符都以以L开始,前边跟着多个JNI类描述符,以事务部结尾。一个JAVA类的人名中的包名分隔符“.”被转形成“/”。因而,对于二个字段类型的字段来讲,它的陈说符是“Ljava/lang/String”。
数组的叙述符中富含“]”字符,前边会跟着数组类型的描述符,如“[I”是int[]种类的字段的叙说符。12.3.3详尽介绍了各体系型的字段描述以致他们表示的JAVA类型。
您可以动用javap工具来生成字段描述符。

备注:

为了品质思索,日常会缓存常用的法子Id。举例native层实行操作完成未来,通过这样的办法回调输出。

拜谒静态字段和拜会实例字段相同,看下边这一个InstanceFieldAccess例子的变形:
class StaticFielcdAccess {
private static int si;

JNIEXPORT jstring

JNICALL
Java_com_cgfay_ndklearningproject_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */)

}
运作程序可得到输出结果:
In C:
StaticFieldAccess.si = 100
In Java:
StaticFieldAccess.si = 200
访谈静态字段和对象实例字段的不一样点:
1、
访问静态字段使用GetStaticFieldID,而访谈对象的实例字段使用Get菲尔德ID,但是,那三个方法都有平等的回来值类型:j田野先生ID。

以此库正是CMakeLists.txt中的add_library中定义的名目,接着定义了三个native方法:

4.4.1 使用时缓存

本条主意包括了包名,Java层的包名中的 “.” 了
“_”。至此,大家询问到了JNI调用的最主旨格局。就是经过定义一串长长的包名以至相应措施,通过如此的形式绑定native层。好了,废话十分少说,直接步向正题。首先,为了方便使用log输出,大家定义如下方法支持大家打字与印刷输出,用来代替printf:

 /* Get a reference to obj's class */
 jclass cls = (*env)->GetObjectClass(env, obj);

 printf("In C:n");

 /* Look for the static field si in cls */
 fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
 if (fid == NULL) {
     return; /* field not found */
 }
 /* Access the static field si */
 si = (*env)->GetStaticIntField(env, cls, fid);
 printf("  StaticFieldAccess.si = %dn", si);
 (*env)->SetStaticIntField(env, cls, fid, 200);
有的引用

好多JNI函数重临局地援用。能够透过FindClass函数再次来到一个有个别引用。局地援用不能够再持续的调用中被缓存以至重用。当贰个原生方法重返时,它被电动释放,也能够调用DeleteLocalRef(clazz卡塔尔(قطر‎;方法释放原生代码

JAVA扶持二种田野(字段),每三个指标的实例都有一个对象字段的复制;全数的指标分享叁个类的静态字段。当地方法应用JNI提供的函数能够得到和改造这二种字段。先看贰个从本地代码中拜见对象字段的例证:
class InstanceFieldAccess {
private String s;

域和章程描述符

前方提到的得到于域ID 和 方法Id
均各自需求域描述符和办法描述符。描述符能够通过上边包车型大巴报表对应的签定映射获得:

Java 类型 签名
Boolean Z
Byte B
Char C
Short S
Int I
Long J
Float F
Double D
类全称 L类全称
type[] [type
void V
方法类型 (参数类型)返回值类型

比方后边的 “(卡塔尔国Ljava/lang/String;”
表示的是参数为void,重回值为String的法子。java/lang/String;
表示的是类的康健,也正是java.lang.String。
大家能够通过应用javap 来反汇编制程序序,取得每一种目里面包车型地铁具名

 /* Get a reference to obj's class */
 jclass cls = (*env)->GetObjectClass(env, obj);

 printf("In C:n");

 /* Look for the instance field s in cls */
 fid = (*env)->GetFieldID(env, cls, "s",
                          "Ljava/lang/String;");
 if (fid == NULL) {
     return; /* failed to find the field */
 }




 /* Read the instance field s */
 jstr = (*env)->GetObjectField(env, obj, fid);
 str = (*env)->GetStringUTFChars(env, jstr, NULL);
 if (str == NULL) {
     return; /* out of memory */
 }
 printf("  c.s = "%s"n", str);
 (*env)->ReleaseStringUTFChars(env, jstr, str);

 /* Create a new string and overwrite the instance field */
 jstr = (*env)->NewStringUTF(env, "123");
 if (jstr == NULL) {
     return; /* out of memory */
 }
 (*env)->SetObjectField(env, obj, fid, jstr);
数据类型介绍

JNI的原来数据类型 —— Primitive Data Types

Java Type JNI Type C/C++ Type Size
Boolean Jboolean unsigned char unsigned 8 bits
Byte jbyte char signed 8 bits
Char jchar unsigned short unsgined 16 bits
Short jshort short signed 16 bits
Int jint int signed 32 bits
Long jlong long long signed 64 bits
Float jfloat float 32 bits
Double jdouble double 64 bits

JNI 引用类型:

Java Type Native Type
java.lang.Class jclass
java.lang.Throwable jthrowable
java.lang.String jstring
Other objects jobject
java.lang.Object[] jobjectArray
boolean[] jbooleanArray
byte[] jbyteArray
char[] jcharArray
short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray
Other Arrays jArray

}
由于多少个线程大概还要做客那几个当地方法,上面方法中的代码十分的大概会变成混乱,其实没事,四个线程总括的ID其实是形似的。
一律的讨论,大家也得以缓存java.lang.String的布局方法的ID:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jcharArray elemArr;
static jmethodID cid = NULL;
jstring result;

那么,依据那个模板,大家来深入分析一下,Android JNI的利用办法呢。
率先,Java层中动用了下边包车型客车代码加载了叁个库,库的称号是native-lib:

JAVA中有二种差异门类的不二诀要,实例方法必需在四个类的某部对象实例上面调用。而静态方法能够在其余三个目的实例上调用。对于营造方式的调用大家延缓到下一节。
JNI扶助一多元完整的函数令你能够在本土代码中回调JAVA方法,上边例子演示了何等从本地代码中调用二个JAVA中的实例方法:
class InstanceMethodCall {
private native void nativeMethod();
private void callback() {
System.out.println(“In Java”);
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary(“InstanceMethodCall”);
}
}
上面的是本地点法的兑现:
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv env, jobject obj)
{
jclass cls = (
env)->GetObjectClass(env, obj);
jmethodID mid =
(env)->GetMethodID(env, cls, “callback”, “()V”);
if (mid == NULL) {
return; /
method not found /
}
printf(“In Cn”);
(
env)->CallVoidMethod(env, obj, mid);
}
运路程序,获得如下输出:
In C
In Java

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_cgfay_ndklearningproject_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {

    std::string hello = "Hello from C++";

    return env->NewStringUTF(hello.c_str());
}

JNI中呈报字段使用字段描述符,描述方法一致有主意描述符。一个方法描述符富含参数类型和重回值类型。参数类型出早先边前,并由一对圆括号将它们括起来,参数类型按它们在措施表明中现身的逐条被列出来,何况两个参数类型之间一向不分隔符。如若二个方法未有参数,被代表为一对空圆括号。方法的回到值类型紧跟参数类型的右括号后边。
诸如,“(I)V”表示这么些方法的四个参数类型为int,何况有叁个void类回值。“()D”表示那个办法未有参数,再次来到值类型为double。
格局描述符中或者会蕴藏类描述符(12.3.2),如方法native private String
getLine(String卡塔尔(قطر‎;的叙说符为:“(Ljava/lang/String;卡塔尔国Ljava/lang/String;”
数组类型的叙说符以“[”开头,前边跟着数组成分类型的陈述符。如,public
static void main(String[]
argsState of Qatar;的叙述符是:”([Ljava/lang/String;)V”
12.3.4详细描述了怎么生成一个JNI方法描述符。雷同,你能够应用javap工具来打字与印刷出JNI方法描述符。
4.2.3 调用静态方法
前二个例证演示了多少个本土方法如何调用实例方法。雷同地,当地点法中平等可以调用静态方法:
1、
通过GetStaticMethodID获取情势ID。对应于调用实例方法时的GetMethodID。
2、
传入类、方法ID、参数,并调用提供静态方法调用功效的JNI类别函数中的多少个,如:CallStaticVoidMethod,CallStaticBooleanMethod等。
调用静态方法和调用实例方法的JNI函数有叁个比十分的大的例外,后者第三个参数是类援引,后面一个是指标实例的援引。
在JAVA访谈叁个静态方法能够经过类,也得以经过对象实例。而JNI的规定是,在本地代码中回调JAVA中的静态方法时,必得钦命二个类引用才行。上边包车型客车例证演示了那么些用法:
class StaticMethodCall {
private native void nativeMethod();
private static void callback() {
System.out.println(“In Java”);
}
public static void main(String args[]) {
StaticMethodCall c = new StaticMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary(“StaticMethodCall”);
}
}
上边是地面方法的兑现:
JNIEXPORT void JNICALL
Java_StaticMethodCall_nativeMethod(JNIEnv env, jobject obj)
{
jclass cls = (
env)->GetObjectClass(env, obj);
jmethodID mid =
(env)->GetStaticMethodID(env, cls, “callback”, “()V”);
if (mid == NULL) {
return; /
method not found /
}
printf(“In Cn”);
(
env)->CallStaticVoidMethod(env, cls, mid);
}
当调用CallStaticVoidMethod时,确认保障您传入的是类援用cls实际不是目的援引obj。运路程序,输出为:
In C
In Java
4.2.4 调用父类的实例方法
若果一个艺术被定义在父类中,在子类中被遮住,你也能够调用那个实例方法。JNI提供了一多重成就那些职能的函数:CallNonvirtual<Type>Method。为了调用三个概念在父类中的实例方法,你必需听从下边包车型大巴步调:
1、 使用GetMethodID从三个针对父类的援引个中得到格局ID。
2、
传入对象、父类、方法ID和参数,并调用CallNonvirtualVoidMethod、CallNonvirtualBooleanMethod等一多元函数中的多少个。
这种调用父类实例方法的情景其实比相当少境遇,平时在JAVA中得以很简单地变成:super.f(卡塔尔;
CallNonvirtualVoidMethod也足以被用来调用父类的结构函数。这么些在下节就能够讲到。
4.3 调用布局函数
JNI中,结构函数能够和实例方法一致被调用,调用格局也相同。传入“<init>”作为艺术名,“V”作为重返类型。你能够透过向JNI函数NewObject传入方法来调用构造函数。上面包车型地铁代码达成了与JNI函数NewString相符的效力:把仓库储存在C缓冲区内的Unicode编码的字符种类,创立成三个java.lang.String对象:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jmethodID cid;
jcharArray elemArr;
jstring result;

# cmake的最低版本
cmake_minimum_required(VERSION 3.4.1)

# 添加库
add_library( #  库名称
             native-lib

             # 库的类型 静态(STATIC)/动态(SHARED)
             SHARED

             # 包含的源文件
             src/main/cpp/native-lib.cpp )

# 查找库
find_library( # 重新命名
              log-lib

              # 查找NDK中的库
              log )

# 链接库
target_link_libraries( # 库名称,跟前面add_library中的名称保持一致
                       native-lib

                       # 需要链接的库名称, 可以链接多个库,用空格或换行符隔开
                       ${log-lib} )
 /* Create a char[] that holds the string characters */
 elemArr = (*env)->NewCharArray(env, len);
 if (elemArr == NULL) {
     return NULL; /* exception thrown */
 }
 (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);

 /* Construct a java.lang.String object */
 result = (*env)->NewObject(env, stringClass, cid, elemArr);

 /* Free local references */
 (*env)->DeleteLocalRef(env, elemArr);
 (*env)->DeleteLocalRef(env, stringClass);
 return result;

在app/src/main/cpp/目录下看看叁个native-lib.cpp的公文。张开文件,我们能够见到诸如以下的代码:

 stringClass = (*env)->FindClass(env, "java/lang/String");
 if (stringClass == NULL) {
     return NULL; /* exception thrown */
 }

 /* Note that cid is a static variable */
 if (cid == NULL) {
     /* Get the method ID for the String constructor */
     cid = (*env)->GetMethodID(env, stringClass,
                               "<init>", "([C)V");
     if (cid == NULL) {
         return NULL; /* exception thrown */
     }
 }

 /* Create a char[] that holds the string characters */
 elemArr = (*env)->NewCharArray(env, len);
 if (elemArr == NULL) {
     return NULL; /* exception thrown */
 }
 (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);

 /* Construct a java.lang.String object */
 result = (*env)->NewObject(env, stringClass, cid, elemArr);

 /* Free local references */
 (*env)->DeleteLocalRef(env, elemArr);
 (*env)->DeleteLocalRef(env, stringClass);
 return result;
全局引用

大局援引在原生方法的三番五次调用进度中仍然有效,除非被显式地释放掉。全局方法的利用如下:
1、创立全局引用

jclass localClazz;
jclass globalClazz;
...
localClazz = env->FindClass("java/lang/String");
globalClazz = env->NewGlobalRef(localClazz);
...
env->DeleteLocalRef(localClazz);

2、删除全局引用

env->DeleteGlobalRef(globalClazz);

JNIEXPORT void JNICALL
Java_InstanceMethodCall_initIDs(JNIEnv env, jclass cls)
{
MID_InstanceMethodCall_callback =
(
env)->GetMethodID(env, cls, “callback”, “()V”);
}
VM进行静态早先化时在调用任何措施前调用initIDs,这样方法ID就被缓存了全局变量中,本地点法的兑现就不要再张开ID总结:
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv env, jobject obj)
{
printf(“In Cn”);
(
env)->CallVoidMethod(env, obj,
MID_InstanceMethodCall_callback);
}
4.4.3 二种缓存ID的不二等秘书技之间的对待
假如JNI工程师无法说了算措施和字段所在的类的源码的话,在使用时缓存是个创制的方案。举例在MyNewString此中,我们不可能在String类中插入叁个initIDs方法。
比起静态起头时缓存来讲,使用时缓存有局地短处:
1、 使用时缓存的话,每趟使用时都要检查一下。
2、
方法ID和字段ID在类被unload时就能失灵,假如你在动用时缓存ID,你不得不确定保障一旦本地代码信赖于那么些ID的值,那么那几个类不被会unload(下一章演示了什么通过利用JNI函数成立二个类援引来堤防类被unload)。另一方面,假如缓存爆发在静态初叶化时,当类被unload和reload时,ID会被重复计算。
据此,尽恐怕在静态伊始化时缓存字段ID和章程ID。
4.5 JNI操作JAVA中的字段和情势时的效能
学完了什么样缓存ID来升高功效后,你只怕会对采取JNI访谈java字段和形式的作用不太明了,native/java比起java/native和java/java来的话,成效怎样呢?
当然,那取决VM的实现。大家不可能交到在大规模的VM上通用的数额,但大家得以由此分析本地点法回调java方法和JNI操作字段以致艺术的进度来交给二个大致的概念。
作者们从相比java/native和java/java的频率开首。java/native调用比java/java要慢,首要有以下多少个原因:
1、
java/native比起JVM内部的java/java来讲有三个调用转变进程,在把调整权和输入切换给本地点法此前,VM必得做一些优秀的操作来创建参数和栈帧。
2、
对VM来讲,对艺术调用进行内联比较便于,而内联java/native方法要难得多。
据大家的评估价值,VM进行java/native调用时的消耗是java/java的2~3倍。当然VM能够拓宽一些调治,使用java/native的开支临近或然等于java/java的费用。
本领上来讲,native/java调用和java/native是平日的。但骨子里native/java调用少之甚少见,VM平日不会优化native/java这种回调方式。许多VM中,native/java调用的损耗能够到达java/java调用的10倍。
动用JNI访谈字段的费用决议于通过JNIEnv进行调用的消耗。以屏弃一个目的引用来讲,本地代码必得依附于特定的JNI函数技巧做到,而以此依附是必须的,它把本地代码和VM中指标的中间格局很好地隔断开。

Array 类型

Array类型操作如下:

extern "C"
JNIEXPORT jintArray JNICALL
Java_com_cgfay_ndklearningproject_JniHelper_arrayFromJNI(JNIEnv *env, jobject instance) {

    // 新建一个 int 数组
    jintArray javaArray = env->NewIntArray(10);
    if (javaArray != 0) {
        // 赋值操作
        jint *h = new jint[10];
        for (int i = 0; i < 10; ++i) {
            h[i] = i;
        }
        env->SetIntArrayRegion(javaArray, 0, 10, h);

        // 从jArray中取得数据
        jint nativeArray[10];
        env->GetIntArrayRegion(javaArray, 0, 10, nativeArray);
        for (int i = 0; i < sizeof(nativeArray) / sizeof(jint); ++i) {
            ALOGI("nativeArray: %d", nativeArray[i]);
        }

        // 使用GetIntArrayElements方法获取的是一个指针,是需要释放的
        jint *nativeDirectArray;
        jboolean isCopy;
        nativeDirectArray = env->GetIntArrayElements(javaArray, &isCopy);
        for (int i = 0; i < 10; ++i) {
            ALOGI("nativeDirectArray: %d", nativeDirectArray[i]);
        }
        if (isCopy == JNI_TRUE) {
            ALOGI("native direct array is a copy of the java array");
        } else {
            ALOGI("native direct array points to actual array");
        }
        // 释放类型
        // 0: 复制内容并释放本地数组
        // JNI_CIMMIT: 复制内容但不释放本地数据,用于定期更新Java数据
        // JNI_ABORT: 释放本地数组,但不复制内容
        env->ReleaseIntArrayElements(javaArray, nativeDirectArray, 0);
    }

    return javaArray;
}
 jclass cls = (*env)->GetObjectClass(env, obj);
 jstring jstr;
 const char *str;

 if (fid_s == NULL) {
     fid_s = (*env)->GetFieldID(env, cls, "s", 
                                "Ljava/lang/String;");
     if (fid_s == NULL) {
         return; /* exception already thrown */
     }
 }

 printf("In C:n");

 jstr = (*env)->GetObjectField(env, obj, fid_s);
 str = (*env)->GetStringUTFChars(env, jstr, NULL);
 if (str == NULL) {
     return; /* out of memory */
 }
 printf("  c.s = "%s"n", str);
 (*env)->ReleaseStringUTFChars(env, jstr, str);

 jstr = (*env)->NewStringUTF(env, "123");
 if (jstr == NULL) {
     return; /* out of memory */
 }
 (*env)->SetObjectField(env, obj, fid_s, jstr);
将Java String转成 C String
    const char *str;
    jboolean isCopy;
    str = env->GetStringUTFChars(javaString, &isCopy);
    if (!str) {
        printf("java string: %s", str);
        if (isCopy == JNI_TRUE) {
            ALOGI("C string is a copy of the Java string");
        } else {
            ALOGI("C string points to actual string");
        }
    }
    env->ReleaseStringUTFChars(javaString, str);

编写翻译运营大家能够打印一下Log:

澳门新浦京娱乐游戏 5

打印的log

}
运路程序,取得输出为:
In C:
c.s = “abc”
In Java:
c.s = “123”

#include "android/log.h"
#define JNI_TAG "JNI_LEARN"
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, JNI_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, JNI_TAG, __VA_ARGS__)

}
上面这么些地面方法有些复杂,要求详解一下。首先,FindClass重临二个java.lang.String类的引用,接着,GetMethodID再次回到构造函数String(char[]
chars卡塔尔国的点子ID。大家调用NewCharArray分配贰个字符数组来保存字符串成分。JNI函数NewObject调用方法ID所标记的布局函数。NewObject函数须求的参数有:类的援用、布局方法的ID、构造方法供给的参数。
DeleteLocalRef允许VM释放被部分援引elemArr和stringClass引用的财富。5.2.1中详细描述了调用DeleteLocalRef的机遇和原因。
以那件事例引出了三个难题,既然大家能够运用JNI函数来得以达成平等的成效,为啥JNI还索要NewString那样的停放函数?原因是,内置函数的功效远高于在本地代码里面调用布局函数的API。而字符串又是最常用到的靶子类型,因而供给在JNI中赋予极其的支撑。
您也能够达成通过CallNonvirtualVoidMethod函数来调用布局函数。这种情状下,本地代码必需首先通过调用AllocObject函数创制两个未起头化的对象。上边例子中的result
= (env卡塔尔(قطر‎->NewObject(env, stringClass, cid,
elemArrState of Qatar;可以被如下代码替换:
result = (
env)->AllocObject(env, stringClass);
if (result) {
(env)->CallNonvirtualVoidMethod(env, result, stringClass,
cid, elemArr);
/
we need to check for possible exceptions /
if ((
env)->ExceptionCheck(env)) {
(*env)->DeleteLocalRef(env, result);
result = NULL;
}
}
AllocObject成立了一个未初阶化的对象,使用时必然要丰裕当心,确认保障七个目的方面,构造函数最多被调用三次。当地代码不该在多个指标方面调用数次布局函数。一时,你可能会意识创造二个未初步化的对象然后一段时间未来再调用布局函数的格局是很有用的。即便如此,抢先百分之三十八境况下,你应有接收NewObject,尽量防止使用轻便失误的AllocObject/CallNonvirtualVoidMethod方法。

备注:

原生方法中的内部存款和储蓄器分配超过设想机管理范围,且不可能用设想机的杂质回笼器回收原生方法中的内部存款和储蓄器。原生函数应该手动释放未利用的内部存款和储蓄器避防止内部存款和储蓄器泄漏。

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图