NDK与JNI基础-AndriodStudio混合编译使用记录


首先说明下相应的概念:

NDK是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

简言之:NDK是一种工具。借助他可以实现一些更适合Android平台版本的C/C++开发

起始:NDK是Google公司推出的帮助Android开发者通过C/C++本地语言编写应用的开发包

JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。

简言之:JNI是一些头文件,里面定义了一些方法以及使用标准

起始:JNI是Java调用Native机制,是Java语言自己的特性全称为Java Native Interface

总之:NDK是一种工具需要借助JNI的标准。所以他们扯在了一起,这里有篇不错的文章:NDK与JNI的关系

JNI的一些数据类型:

在Android最新的NDK与JNI开发中,以前的.mk文件被替换为了CMakeList.txt

创建一个NDK工程并实现属性引用

首先你得去下一堆东西,补充相应的环境支持 也就是Android Studio 中要安装 Android SDK->SDK Tools中安装 CMake, LLDB, NDK. 很大,时间炒鸡久,特别是还被墙(2-3M的速度大概用了30分钟?不太记得清)

另外检查一个插件是否勾上

安装完新建工程时勾选Include C++ support,然后在工程的Project 工具栏选择,Project选项 ,你会发现多了几个文件,在main中和java对齐的cpp目录里包括了一个.cpp的实现,src目录同级多了一个CMakeList.txt

实际上AS已经生成了一些示例,有没有似曾相识?

毋庸置疑,运行后肯定就是显示Hello from C++了,写这里的代码你不喜欢用->的话可以用.(点)然后按Table键IDE很舒服的帮你转换方法调用,仿写一个方法

使用IDE的提示去自动在CPP里创建方法,然后会发现,这个套路原来是一样的

这样我们就知道自己怎么去创建了,那个extern “C”是代表支持混合编程的意思,下面我们改下

extern "C"
JNIEXPORT jstring JNICALL
Java_com_lckiss_ndkgradle_MainActivity_updateNameFromC(JNIEnv *env, jobject instance) {

    // Jobject代表的是谁调用的这个方法,比如这里就是MainActivity的类对象 首先混去jclass
    //C++的命名习惯性加_
    jclass _jclass=env->GetObjectClass(instance);

    /**
     * 获取属性ID jfieldID GetFieldID(jclass clazz, const char* name, con st char* sig)
     * 第二个参数表示我们想要获取的属性名 比如我们新加的name,
     * 第三个参数看下面的图,其代表的是JNI想要访问Java中的属性,变量等需要的一个签名
     * String不属于基本数据类型 所以需要用简写加上类的完整路径就是L java/lang/String ;
     */    jfieldID  _jfieldID = env->GetFieldID(_jclass,"name","Ljava/lang/String;");

    /**
     * jobject GetObjectField(jobject obj, jfieldID fieldID)
     * object向下强转为jstring 和java一样
     */    jstring res= (jstring) env->GetObjectField(instance, _jfieldID);
    //输出下
    printf("%#x\n",res);

    //转换成Java的string去调用
    char * str= (char *) env->GetStringUTFChars(res, NULL);
    //新生成一个char数组去拼接,方便演示
    char text[20]="success";
    char* finalRes= strcat(str,text);
    //最后将结果返回
    return env->NewStringUTF(finalRes);
}

然后MainActivity中:

public class MainActivity extends AppCompatActivity {

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

    //新定义一个属性
    public String name="Test ";

    @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(updateNameFromC());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */    public native String stringFromJNI();
    public native String updateNameFromC();
}

运行下就会显示test success。

test success

其实简单的用法就是这样,并没什么难度,NDK已经简化了很多很多东西,附上签名描述图

JNI常用方法函数

JIN方法数组的引用(静态方法等等诸如此类)

继续在Activity中添加方法

public String getName(){
    return "this is name form act";
}
//我们来通过这个方法来返回上面的 "this is name form act"
public native String getMethod();

然后按老套路继续写Cpp,顺便改Activity中的那个textview.setText的来源

extern "C"
JNIEXPORT jstring JNICALL
Java_com_lckiss_ndkgradle_MainActivity_getMethod(JNIEnv *env, jobject instance) {
    //先拿到class
    jclass _jclass = env->GetObjectClass(instance);
    //拿方法 jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    jmethodID _jmethodID = env->GetMethodID(_jclass, "getName", "()Ljava/lang/String;");
    //jni调用java的方法 静态方法等诸如此类 env->CallStaticObjectMethod()
    jstring _jstring = (jstring) env->CallObjectMethod(instance, _jmethodID);

    //转换成Java的string去调用 和上面一样
    char *str = (char *) env->GetStringUTFChars(_jstring, NULL);
    //新生成一个char数组去拼接,方便演示
    char text[20] = "success";
    char *finalRes = strcat(str, text);
    //最后将结果返回
    return env->NewStringUTF(finalRes);
}

最后肯定是返回:

this is name form act success

附上方法的签名图

小彩蛋:

so库文件哪里找?

还有SDK目录下NDK的一些东西

接下来是数组的引用

Activity中加入以下内容

private int[] source={1,4,0,7,33,11};

@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(getMethod());
    //利用JNI来进行数组排序
    getArray(source);
    for (int i = 0; i <source.length ; i++) {
        Log.d("source", "onCreate: "+source[i]);
    }
}
public native void getArray(int[] arrays);

Cpp中追加头文件以及内容

#include <stdlib.h>
/**
 * 首先将__lhs强制声明为指向整数的指针(int*),再读取指针对应的整数(最外面的*)
 * @param __lhs
 * @param __rhs
 * @return
 */int compare(const void* __lhs, const void* __rhs){
    return (*(int*)__lhs-*(int*)__rhs);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_lckiss_ndkgradle_MainActivity_getArray(JNIEnv *env, jobject instance, jintArray arrays_) {
    //已经自动拿到了数组
    jint *arrays = env->GetIntArrayElements(arrays_, NULL);

    //获取数组长度
    int _len=  env->GetArrayLength(arrays_);
    //void qsort(void* __base, size_t __nmemb, size_t __size, int (*__comparator)(const void* __lhs, const void* __rhs));
    //第三个参数是需要实现的一个方法
    qsort(arrays,_len, sizeof(int),compare);

    env->ReleaseIntArrayElements(arrays_, arrays, 0);
}

然后运行,你会发现排序成功,

基本上就是这样子玩了,套路都是一样一样的。秒秒钟就成了老司机

第一部分完结,请看第二部分:NDK与JNI基础-JIN的引用、缓存策略、异常

 

声明:TIL|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA[ZH]协议进行授权

转载:转载请注明原文链接 - NDK与JNI基础-AndriodStudio混合编译使用记录


Life is very interesting. In the end, some of your greatest pains become your greatest strengths.