Android JNI3--JNI基础

1,C预处理器

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。

所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。

#include  导入头文件
#if       if判断操作  【if的范畴 必须endif】
#elif     else if
#else     else
#endif    结束if
#define   定义一个宏
#ifdef    如果定义了这个宏 【if的范畴 必须endif】
#ifndef   如果没有定义这个宏 【if的范畴 必须endif】
#undef    取消宏定义
#pragma   设定编译器的状态

#if  举例:

#if 1 // if
    cout <<  "真" << endl;

#elif 0 // else if
    cout <<  "假" << endl;

#else
    cout << "都不满足" << endl;

#endif // 结束if
    cout << "结束if" << endl;
#ifdef举例:
#ifndef isRelease // 如果没有isRelease这个宏
#define isRelease 1 // 是否是正式环境下 【我就定义isRelease这个宏】

#if isRelease == true
#define RELEASE // 正式环境下 定义RELEASE宏

#elif isRelease == false
#define DEBUG // 测试环境下  定义DEBUG宏

#endif 
#endif 

#ifdef DEBUG // 是否定义了DEBUG这个宏
    cout << "在测试环境" << endl;
#else RELEASE
    cout << "在正式环境" << endl;
#endif // 结束IF

宏的取消#undef: 

#ifdef TEST// 是否定义了这个宏
   cout << "TEST" << endl;
#undef TEST// 取消宏的定义,下面的代码,就没法用这个宏了,相当于:没有定义过TEST宏
#endif

宏变量:

#define VALUE_I 99
#define VALUE_S "字符串"

int main() {
    int i = VALUE_I; // 预处理阶段 宏会直接完成文本替换工作,替换后的样子:int i = 99;
    string s = VALUE_S; // 预处理阶段 宏会直接完成文本替换工作,替换后的样子:string s = "字符串";

    return 0;
}

宏函数,宏函数都是大写

优点:文本替换,不会造成函数调用开销

缺点:会导致代码体积增大

#define ADD(n1, n2) n1 + n2

int main() {
    int r = ADD(1, 2);
    cout << r << endl;//返回3

    return 0;
}

2,JNI  java与native代码互调

在MainActivity中调用native方法:

public class MainActivity extends AppCompatActivity {

    public String name = "测试"; // 签名:Ljava/lang/String;

    static {
        System.loadLibrary("native-lib"); //静态代码块中加载native-lib文件
    }   
  
    public native void changeName();//调用native方法


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.txt_name);
        changeName();
        tv.setText(name);
    }

}

使用命令javah -classpath . -jni com.test.MainActivity 生成头文件com_test_MainActivity.h

#include 
#include 

// 解决循环Copy的问题 第二次就进不来了
#ifndef _Included_com_test_MainActivity // 如果没有定义这个宏
#define _Included_com_test_MainActivity // 我就定义这个宏

#ifdef __cplusplus // 如果是C++环境
extern "C" { // 全部采用C的方式 不准你函数重载,函数名一样的问题
#endif

// 函数的声明
JNIEXPORT jstring JNICALL Java_com_test_MainActivity_changeName
  (JNIEnv *, jobject);


#ifdef __cplusplus // 省略  如果是C++,啥事不干
}

#endif

在native-lib.cpp里面实现changeName()方法

#include "com_test_MainActivity.h"

// NDK工具链里面的 log 库 引入过来
#include 

#define TAG "MainAcitvity"
//自动填充
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)


// extern "C": 必须采用C的编译方式
// // 无论是C还是C++ 最终是调用到 C的JNINativeInterface,所以必须采用C的方式 extern "C"
// 函数的实现
extern "C"

JNIEXPORT  // 标记该方法可以被外部调用

jstring // Java <---> native 转换用的

JNICALL // 代表是 JNI标记

// Java_包名_类名_方法名 

// JNIEnv * env  JNI:的桥梁环境   所有的JNI操作,必须靠他

// jobject jobj  谁调用,就是谁的实例  MainActivity this
// jclass clazz 谁调用,就是谁的class MainActivity.class



extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_changeName(JNIEnv *env, jobject thiz) {
   // 获取class
   jclass j_cls = env->GetObjectClass(thiz);

   // 获取属性  L对象类型 都需要L
   // jfieldID GetFieldID(MainActivity.class, 属性名, 属性的签名)
   jfieldID j_fid = env->GetFieldID(j_cls, "name", "Ljava/lang/String;");

   // 转换工作 jstring 为JNI类型
   jstring j_str = static_cast(env->GetObjectField(thiz ,j_fid));

   // 打印字符串  目标
   char * c_str = const_cast(env->GetStringUTFChars(j_str, NULL));
   LOGD("native : %s\n", c_str);
   LOGE("native : %s\n", c_str);
   LOGI("native : %s\n", c_str);

    // 修改名字 返回给java层
    jstring jName = env->NewStringUTF("张三");
    env->SetObjectField(thiz, j_fid, jName);
}

签名规则:

    Java的boolean  --- Z 
    Java的byte  --- B
    Java的char  --- C
    Java的short  --- S
    Java的int  --- I
    Java的long  --- J   
    Java的float  --- F
    Java的double  --- D
    Java的void  --- V
    Java的引用类型  --- Lxxx/xxx/xx/类名;
    Java的String  --- Ljava/lang/String;
    Java的array  int[]  --- [I         double[][][][]  --- [[[D
    int add(char c1, char c2) ---- (CC)I
    void a()     ===  ()V

Java调用native方法,native方法调用java方法,互调

在MainActivity中:

public class MainActivity extends AppCompatActivity {

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

    public native void callSum();

    public int sum(int number1, int number2) {
        return number1 + number2;
    }

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

}

在native-lib.cpp中,实现callSum()代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_callSum(JNIEnv *env, jobject job) {
   
    jclass  mainActivityClass = env->GetObjectClass(job);

    // GetMethodID(MainActivity.class, 方法名, 方法的签名)
   jmethodID j_mid = env->GetMethodID(mainActivityClass, "sum", "(II)I");

   // 调用 Java的方法
   jint sum = env->CallIntMethod(job, j_mid, 8, 8);
   LOGE("sum result:%d", sum);

}

3,JNI 数组操作

MainActivity:

public class MainActivity extends AppCompatActivity {

    private final static String TAG = MainActivity.class.getSimpleName();

    static {
        // System.load(D:/xxx/xxxx/xxx/native-lib); 这种是可以绝对路径的加载动态链接库文件
        System.loadLibrary("native-lib"); 
        // 这种是从库目录遍历层级目录,去自动的寻找   apk里面的lib/libnative-lib.so
    }

    public native void testArrayAction(int count, String textInfo, int[] ints, String[] strs);

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

    public void arrTest() {
        int[] ints = new int[]{1,2,3,4,5,6}; // 基本类型的数组

        String[] strs = new String[]{"张三","周五","王五"}; // 对象类型的数组

        testArrayAction(99, "你好", ints, strs);

    }
}

在native-lib.cpp中:

extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_testArrayAction(JNIEnv *env, jobject thiz,
                                                             jint count,
                                                             jstring text_info,
                                                             jintArray ints,
                                                             jobjectArray strs) {
    // ① 基本数据类型  jint count, jstring text_info, 最简单的
    int countInt = count; // jint本质是int,所以可以用int接收
    LOGI("参数一 countInt:%d\n", countInt);

    // const char* GetStringUTFChars(jstring string, jboolean* isCopy)
    const char *textInfo = env->GetStringUTFChars(text_info, NULL);
    LOGI("参数二 textInfo:%s\n", textInfo);

    // ② 把int[] 转成 int*
    // jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
    int *jintArray = env->GetIntArrayElements(ints, NULL);

    // Java层数组的长度
    // jsize GetArrayLength(jarray array) // jintArray ints 可以放入到 jarray的参数中去
    jsize size = env->GetArrayLength(ints);

    for (int i = 0; i < size; ++i) {
        *(jintArray + i) += 100; // C++的修改,影响不了Java层
        LOGI("参数三 int[]:%d\n", *jintArray + i);
    }
    // 目前无法控制Java的数组 变化 +100
    // 操作杆 ----> JMV
    // env->

    /**
     * 0:           刷新Java数组,并 释放C++层数组
     * JNI_COMMIT:  只提交 只刷新Java数组,不释放C++层数组
     * JNI_ABORT:   只释放C++层数组
     */
    env->ReleaseIntArrayElements(ints, jintArray, 0);

    // ③:jobjectArray 代表是Java的引用类型数组,不一样
    jsize strssize = env->GetArrayLength(strs);
    for (int i = 0; i < strssize; ++i) {

        jstring jobj = static_cast(env->GetObjectArrayElement(strs, i));

        // 模糊:isCopy内部启动的机制
        // const char* GetStringUTFChars(jstring string, jboolean* isCopy)
        const char *jobjCharp = env->GetStringUTFChars(jobj, NULL);

        LOGI("参数四 引用类型String 具体的:%s\n", jobjCharp);

        // 释放jstring
        env->ReleaseStringUTFChars(jobj, jobjCharp);
    }
}

4,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 setAge age:" + age);
        this.age = age;
    }

    public static void showInfo(String info) {
        Log.d(TAG, "showInfo info:" + info);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

MainActivity:

public class MainActivity extends AppCompatActivity {

  

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

    public native void putObject(Student student, String str); // 传递引用类型,传递对象
    public native void insertObject(); // 凭空创建Java对象
    public native void testQuote(); // 测试引用
    public native void delQuote(); // 释放全局引用

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

    public void test02(View view) {
        Student student = new Student(); // Java new
        student.name = "史泰龙";
        student.age = 88;
        putObject(student, "九阳神功");

    }

    public void test03(View view) {
        insertObject();
    }

    public void test04(View view) {
        testQuote();
    }

    public void test05(View view) {
        delQuote(); // 必须释放全局引用
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        delQuote(); // Activity销毁时: 必须释放全局引用
    }
}

在native-lib.cpp中:

extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_putObject(JNIEnv *env,
                                                       jobject thiz,
                                                       jobject student,
                                                       jstring str) {
    const char *strChar = env->GetStringUTFChars(str, NULL);
    LOGI("strChar:%s\n", strChar);
    env->ReleaseStringUTFChars(str, strChar);

    // --------------
    // 1.寻找类 Student
    // jclass studentClass = env->FindClass("com/test/Student"); // 第一种
    jclass studentClass = env->GetObjectClass(student); // 第二种

    // 2.Student类里面的函数规则  签名
    jmethodID setName = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
    jmethodID getName = env->GetMethodID(studentClass, "getName", "()Ljava/lang/String;");
    jmethodID showInfo = env->GetStaticMethodID(studentClass, "showInfo", "(Ljava/lang/String;)V");

    // 3.调用 setName
    jstring value = env->NewStringUTF("Zhangsan");
    env->CallVoidMethod(student, setName, value);

    // 4.调用 getName
    jstring getNameResult = static_cast(env->CallObjectMethod(student, getName));
    const char *getNameValue = env->GetStringUTFChars(getNameResult, NULL);
    LOGE("调用到getName方法,值是:%s\n", getNameValue);

    // 5.调用静态showInfo
    jstring jstringValue = env->NewStringUTF("静态方法你好,我是C++");
    env->CallStaticVoidMethod(studentClass, showInfo, jstringValue);
}
// C++ 堆 栈 ...
// JNI函数  局部引用,全局引用,...
extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_insertObject(JNIEnv *env, jobject thiz) {
    /*jstring str = env->GetStringUTFChars();
    jstring str = env->GetStringUTFChars();
    jstring str = env->GetStringUTFChars();
    jstring str = env->GetStringUTFChars();
    jstring str = env->GetStringUTFChars();
    jstring str = env->GetStringUTFChars();
    jstring str = env->GetStringUTFChars();

    // 好习惯:
    // 我用完了,我记释放,在我函数执行过程中,不会导致 内存占用多
    env->ReleaseStringUTFChars()*/


    // 1.通过包名+类名的方式 拿到 Student class  凭空拿class
    const char *studentstr = "com/test/Student";
    jclass studentClass = env->FindClass(studentstr);

    // 2.通过student的class  实例化此Student对象   C++ new Student
    jobject studentObj = env->AllocObject(studentClass); // AllocObject 只实例化对象,不会调用对象的构造函数

    // 方法签名的规则
    jmethodID setName = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
    jmethodID setAge = env->GetMethodID(studentClass, "setAge", "(I)V");

    // 调用方法
    jstring strValue = env->NewStringUTF("zhansan");
    env->CallVoidMethod(studentObj, setName, strValue);
    env->CallVoidMethod(studentObj, setAge, 99);


    // env->NewObject() // NewObject 实例化对象,会调用对象的构造函数


    // ====================  下面是 Person对象  调用person对象的  setStudent 函数等

    // 4.通过包名+类名的方式 拿到 Student class  凭空拿class
    const char *personstr = "com/test/Person";
    jclass personClass = env->FindClass(personstr);

    jobject personObj = env->AllocObject(personClass); // AllocObject 只实例化对象,不会调用对象的构造函数

    // setStudent 此函数的 签名 规则
    jmethodID setStudent = env->GetMethodID(personClass, "setStudent",
            "(Lcom/test/Student;)V");

    env->CallVoidMethod(personObj, setStudent, studentObj);

    // 规范:一定记得释放【好习惯】
    // 第一类
    env->DeleteLocalRef(studentClass);
    env->DeleteLocalRef(personClass);
    env->DeleteLocalRef(studentObj);
    env->DeleteLocalRef(personObj);

    // 第二类
    // env->ReleaseStringUTFChars()

    // TODO 局部引用: jobject jclass jstring ...  【函数结束后,会自动释放】
}


jclass dogClass; // 你以为这个是全局引用,实际上他还是局部引用

extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_testQuote(JNIEnv *env, jobject thiz) {
    if (NULL == dogClass) {
        /*const char * dogStr = "com/derry/as_jni_project/Dog";
        dogClass = env->FindClass(dogStr);*/

        // 升级全局引用: JNI函数结束也不释放,反正就是不释放,必须手动释放   ----- 相当于: C++ 对象 new、手动delete
        const char * dogStr = "com/test/Dog";
        jclass temp = env->FindClass(dogStr);
        dogClass = static_cast(env->NewGlobalRef(temp)); // 提升全局引用
        // 记住:用完了,如果不用了,马上释放
        env->DeleteLocalRef(temp);
    }

    //  V  是不会变的

    // 构造函数一
    jmethodID init = env->GetMethodID(dogClass, "", "()V");
    jobject dog = env->NewObject(dogClass, init);

    // 构造函数2
    init = env->GetMethodID(dogClass, "", "(I)V");
    dog = env->NewObject(dogClass, init, 100);


    // 构造函数3
    init = env->GetMethodID(dogClass, "", "(II)V");
    dog = env->NewObject(dogClass, init, 200, 300);

    // 构造函数4
    init = env->GetMethodID(dogClass, "", "(III)V");
    dog = env->NewObject(dogClass, init, 400, 500, 600);

    env->DeleteLocalRef(dog); // 释放
    // dogClass = NULL; // 是不是问题解决了,不能这样干(JNI函数结束后,还怎么给你释放呢)

    // 这样就解决了
    /*env->DeleteGlobalRef(studentClass);
    studentClass = NULL;*/
}

// JNI函数结束,会释放局部引用   dogClass虽然被释放,但是还不等于NULL,只是一个悬空指针而已,所以第二次进不来IF,会崩溃

// 非常方便,可以使用了
extern int age; // 声明age
extern void show(); // 声明show函数  5000行代码

// 手动释放全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_delQuote(JNIEnv *env, jobject thiz) {
   if (dogClass != NULL) {
       LOGE("全局引用释放完毕,上面的按钮已经失去全局引用,再次点击会报错");
       env->DeleteGlobalRef(dogClass);
       dogClass = NULL; // 最好给一个NULL,指向NULL的地址,不要去成为悬空指针,为了好判断悬空指针的出现
   }

   // 测试下
   show();
}

你可能感兴趣的:(android,NDK开发,android,c++,开发语言)