3-ndk学习之jni基础篇(1)

首先需要搞清楚几个概念,是先有jni,还是ndk?
很多人都会觉得先有ndk,然后才有的jni,其实不是的。JNI是JAVA提供的,即Java Native Interface,它是为了实现java和底层代码(例如c,c++)之间的交互而生的一套api。NDK是安卓的一套工具集(JNI,gcc,g++ ..... ),为了方便编译而集成的。
因此可以联系android系统架构的概念,


u=1052085805,427367910&fm=26&gp=0.jpg

从上到下:上层应用层,如联系人,通话,短信,我们开发的apk应用等;
系统应用层,如四大组件,系统通知,manager等,这些都是java写的;
类库层, 大部分是c,c++写的,主要是jvm和一些三方库比如sqlite等;
linux内核层,主要是一些蓝牙,wifi,camara驱动等;
所以网上会有很多人都觉得,JNI是很伟大的,如果没有JNI,这个系统架构不复存在。

jni项目介绍

首先在as中新建一个c++项目,会自动生成CMakeList.txt native-lib.cpp ,代码如下,默认生成的那些注释我都删掉了,下面注释是我自己理解的:

CMakeList.txt

cmake_minimum_required(VERSION 3.4.1)
add_library(
        native-lib
        SHARED    # 动态库  Linux .so   Windows .dll
        native-lib.cpp)
find_library(
        log-lib
        log)
target_link_libraries(
        native-lib
        ${log-lib})

native-lib.cpp

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

extern "C"  // 支持C语言的代码
JNIEXPORT   // Linux 和 Windows jni.h 内部定义全部都不一样,此宏代表我要暴露出去的标准形式定义
            // 例如:在Windows中,对外暴露的标准就规定了,所以函数必须是Windows系统规则定义的
jstring
JNICALL     // Linux 和 Windows jni.h 内部定义全部都不一样,此宏代表 当前函数 压栈规则(行参规则)
            // 例如:Windows中:代表函数压栈 从右 到 左边
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

我们自己写一个jni方法

在MainActivity中,新建一个最简单的native方法,alt+enter自动生成还是很简单的,

public native void test01();

其中在native-lib.cpp中生成如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test01(JNIEnv *env, jobject instance) {
      //任何jni方法都会有这两个参数
      //其中第一个参数 env,Java虚拟机自动携带过来的,就是为了让我们可以使用JNI的API
      //其中第二个参数instance,谁调用就是指的谁,这里是java中的 MainActivity 这个实例
}

c,jni,java3个领域中数据类型的转换关系

    // C领域中              JNI领域中          Java领域中
    // int                         jint                        int
    // const char *          jstring                  String
    //                              jclass                   class
    //                              jmethodID            Method

接下来就开始进行jni的基础学习了,在MainActivity中新建几个test方法,大家可以直接撸代码,注释步骤写的非常清楚:

jni基本方法调用

package com.example.myapplication;

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

public class MainActivity extends AppCompatActivity {

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

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

        int[] ints = {1,2,3,4,5,6};
        String[] strings = {"斯塔克", "史蒂夫"};
        //第一个test1方法
        test1(42,"a warm heart",ints,strings);
    }

    //第一个test方法
    public native void test1(int number, String text, int[] intArray, String[] array);
}

#include <jni.h>
#include <string>
#include <android/log.h>

// 日志打印
#define TAG "ftd"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)


extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test1(JNIEnv *env, jobject instance, jint number,
                                                  jstring text_, jintArray intArray_,
                                                  jobjectArray array) {
    //1,打印第一个参数,int和jint可以随意切换,所以可以这样直接赋值
    int my_number = number;
    LOGD("my_number: %d\n", my_number);



    //2,打印第二个参数
    const char * my_text = env->GetStringUTFChars(text_,NULL);
    //GetStringUTFChars方法中的第二个参数意思是,第一重意思:是否在内部完成Copy操作,NULL==0 false,  第二重意思:要给他一个值,让内部可以转起来,这个值,随意
    LOGD("my_text: %s\n", my_text);
    //这里一定要记得回收GetStringUTFChars
    env->ReleaseStringUTFChars(text_,my_text);



    //3,打印第三个参数,即int数组
    jint * my_int_array = env->GetIntArrayElements(intArray_,NULL);
    //由于这里遍历的时候不知道数组的大小,所有还需要获取一下数组的大小,同样也是jni的api
    jsize  my_int_array_length = env->GetArrayLength(intArray_);
    for (int i = 0; i < my_int_array_length; i++) {
        int result = *(my_int_array+i);//这里使用了指针遍历
        LOGD("遍历IntArray里面的值:%d\n",result);
    }
    //还是要记着回收
    env->ReleaseIntArrayElements(intArray_,my_int_array,0);//0代表刷新



    //4,打印第四个参数,即String数组,引用类数据类型比较复杂
    jsize my_string_array_length = env->GetArrayLength(array);
    for (int i = 0; i < my_string_array_length; i++) {
        //引用类数据类型都先用jobject接收
        jobject jobject1 = env->GetObjectArrayElement(array,i);
        jstring jstring1 = static_cast<jstring>(jobject1);
        const char * itemStr = env->GetStringUTFChars(jstring1,NULL);
        LOGD("遍历String Array 里面的值:%s\n", itemStr);
        // 回收
        env->ReleaseStringUTFChars(jstring1, itemStr);
    }
}

运行结果如下


image.png

jni调用对象,是反射机制

新建Student实体类

public class Student {
    private final static String TAG = Student.class.getSimpleName();

    public String name;
    public int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        Log.d(TAG, "Java setName: name:" + name);
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        Log.d(TAG, "Java setName: age:" + age);
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
    public static void myStaticMethod() {
        Log.d(TAG, "myStaticMethod: ");
    }
}

MainActivity中调用

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

        int[] ints = {1,2,3,4,5,6};
        String[] strings = {"斯塔克", "史蒂夫"};
        //第一个test1方法
        test1(42,"a warm heart",ints,strings);


        Student student = new Student();
        student.age = 27;
        student.name = "托尼";
        //第二个test2方法
        test2(student);
    }

    //第一个test方法
    public native void test1(int number, String text, int[] intArray, String[] array);

    //第二个test方法
    public native void test2(Student student);

native-lib中:

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

    // 1.获取字节码(这里要注意是全包名,但是c++不认识.   所有要把.改成/)
    const char * student_clss_str = "com/example/myapplication/Student";
    jclass student_class = env->FindClass(student_clss_str);

    // 2.拿到方法对象
    const char * sig = "(Ljava/lang/String;)V";
    jmethodID  setName = env->GetMethodID(student_class, "setName", sig);

    //签名原理:
    //(1).首先先来一个()
    //(2).然后括号后面跟方法的返回值  void--V
    //(3).然后括号里面是方法的参数,String -- java/lang/String;
    //(4).然后在括号后的第一位加L


    sig = "(I)V";//基本数据类型简单,直接一个I就可以
    jmethodID setAge = env->GetMethodID(student_class, "setAge", sig);

    sig = "()V";//没有参数,直接就不写
    jmethodID myStaticMethod = env->GetStaticMethodID(student_class, "myStaticMethod", sig);

    // 3.调用对象
    const char * str = "09";
    jstring str2 = env->NewStringUTF(str);
    env->CallVoidMethod(student, setName, str2);
    env->CallVoidMethod(student, setAge, 888);
    env->CallStaticVoidMethod(student_class, myStaticMethod);

    env->DeleteLocalRef(student_class); // 回收
    env->DeleteLocalRef(student); // 回收
}

运行结果为


image.png

这里要说些签名,按照原理写可以,也可以直接在命令行里面敲,首先需要把Student编译成class文件(也就是运行一下),在app-build-intermediates-javac-debug-compileDebugJavaWithJavac-classes里,打开命令行窗口,输入javap -s com.example.myapplication.Student,就可以获得Student下所有方法的签名了。


image.png

谢谢观看,有不对或者不详细的地方希望大家在评论区提出宝贵的意见。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容