Android Studio1.4.x JNI开发基础 - 简单实例

  接上一篇,搭建好基于Android Studio的环境以后,编写native代码相对来讲也比较简单了。在Android上编写Native代码和在Linux编写C/C++代码仍是有区别,Native代码通常须要与JVM交互数据,须要遵循必定的规范,本文来介绍一下基本的JNI代码写法。java

  咱们仍是从实例出发,配置好Android Studio工程以后,咱们须要建立jni目录和在jni目下建立c/c++文件和相应的头文件,建立方式见下图。android

  在实例工程中咱们建立了NdkSample.cpp 和 NdkSample.h,源码见下面:c++

#include "NdkSample.h"

JNIEXPORT jstring JNICALL Java_com_zyp_ndktest_MainActivity_sayHello
        (JNIEnv *env, jclass cls, jstring j_str)
{
    const char *c_str = nullptr;
    char buff[128] = {0};
    jboolean isCopy;    
    c_str = env->GetStringUTFChars(j_str, &isCopy);
    printf("isCopy:%d\n",isCopy);
    if(c_str == NULL)
    {
        return NULL;
    }
    printf("C_str: %s \n", c_str);
    sprintf(buff, "hello %s", c_str);
    env->ReleaseStringUTFChars(j_str, c_str);
    return env->NewStringUTF(buff);
}  
#ifndef NDKTEST_NDKSAMPLE_H
#define NDKTEST_NDKSAMPLE_H

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

extern "C" {
JNIEXPORT jstring JNICALL
        Java_com_zyp_ndktest_MainActivity_sayHello(JNIEnv *env, jclass type,
                                                       jstring filename);
}

#endif //NDKTEST_NDKSAMPLE_H

  如今来简单介绍一下,首先是NdkSample.h文件,刚刚建立的时候只有相应的预处理命令,咱们在头文件预处理命令之间加上 jni.h ,stdio.h ,string.h 后两个非必要。将咱们要在java层调用的接口声明出来,放在extern "c"{} 中(告诉编译器按照C标准进行编译)。第一次接触jni的同窗看到那么复杂的函数命名和奇怪的JNIEXPORT ,JNICALL,JNIEnv之类的估计有点不习惯,本文就不详细介绍它们的意思,其实你跟踪源码它们就是几个宏(JNIEnv是一个结构体保存当前环境的上下文),其它的jstring,jclass之类的很好理解就是在Native环境中对JVM中java对应结构的一种表示方式。app

  函数命令方式是包名加activity名加函数名,表如咱们在java层中的包名是java.com.zyp.ndktest,在MainActivity中调用sayHello函数,则jni层函数命名就要写成上面的方式。ide

  接下来看NdkSample.cpp文件中函数的定义。ni层的函数还须要多两个参数,一个是JNIEnv * ,一个是jclass。咱们在java层调用的时候就只用传递前两个参数以外的参数。例子中咱们想从java层传递一个String类型的参数到jni层,jni层从JVM中取数据的时候取到的倒是jstring类型,在jni层咱们不能直接使用须要转换。这里咱们经过env->GetStringUTFChars(j_str, &isCopy)函数来完成,将j_str所在的地址转换并赋值给const char *类型的指针,以后咱们就能够经过该指针访问那块内存了。注意这里是const char * 表示该指针指向的内存区域的内容是不能够改变的,java中的String 也是自带final属性的。GetStringUTFChars()其实是将JVM内部的Unicode转化成为了C/C++认识的UTF-8的格式的字符串,注意这个函数内部发生了内存分配,至关因而拷贝了一份Unicode而后进行转化,因此后面须要ReleaseStringUTFChars()来释放内存。函数

  最后该函数返回一个新的构建好的jstring类型给java层,为了将C/C++层的UTF-8字符串转换为JVM中的Unicode字符串,须要调用另一个函数NewStringUTF()来完成转换。gradle

  咱们注意到JVM中的内容jni中不能直接操做须要进行转换,jni中的内同也要进行转换,所以也有大量的相关jni接口存在,后面文章中会挑选一些来说解。spa

  此外,函数中JNIEnv *env这个参数须要说一下,在C和C++中使用方式是不同的,不要搞混了。在C中,看到JNIEnv 咱们实质是取得了JNINativeInterface* (JNIEnv指针的指针),咱们得使用**env获取结构体,从而才能使用结构体里面的方法。在C++中,看到JNIEnv咱们实质是取得了JNIEnv*(JNIEnv结构体的指针),咱们能够直接使用env->使用结构体里面的方法。注意咱们调用GetStringUTFChars()的方式,可是注意和Java_com_zyp_ndktest_MainActivity_sayHello()进行区别。指针

  如今来看看咱们在java层中如何调用jni层的接口,看下面代码:code

package com.zyp.ndktest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String ret = sayHello("zhuzhu");
        Log.i("JNI_INFO", ret);
    }

    static {
        System.loadLibrary("NdkSample");
    }

    public native static String sayHello(String str);
}

  咱们首先要经过System.loadLibrary()加载jni代码编译后生成的.so,可是这个库的名字怎么来的呢,注意回过头去看上一篇中gradle ndk{}中的内容,咱们是在那里进行的命名的;而后还要声明native static 类型的该函数。而后直接调用就行了。运行结果见下图。

  但愿经过这篇文章可以让你们入门JNI开发^_^。

相关文章
相关标签/搜索