Android ndk-jni语法—— 3

249 阅读5分钟

今天我们继续学习jni相关知识点————调用java构造方法。

一. jni调用java类的构造方法

首先我们创建一个java类,并声明其无参构造和有参构造,并添加日志打印:

package com.carey.myndk;

import android.util.Log;

public class Company {
    private String name;

    public Company() {
        Log.i("carey", "==== 调用了Company构造.");
    }

    public Company(String name) {
        this.name = name;
        Log.i("carey", "==== 调用了Company有参构造,name:" + name);
    }
    
    public void salary() {
        Log.i("carey", "==== 公司发工资了,很开心。");
    }

    public String getName() {
        return name;
    }

}

同时我们新增native方法:

public native Company callConstructor();

并在cpp文件中进行方法的实现:

extern "C"
JNIEXPORT jobject JNICALL
Java_com_carey_myndk_MainActivity_callConstructor(JNIEnv *env, jobject thiz) {
    // 通过类的全类名获取类对象
    jclass cls = env->FindClass("com/carey/myndk/Company");
    // 获取构造方法 参数1类对象 参数2 <init>代表调用构造方法 参数3 ()V 代表无参数构造方法签名
    jmethodID initMid = env->GetMethodID(cls, "<init>", "()V"); // 无参构造
    // 根据构造方法创建对象
    jobject companyObj = env->NewObject(cls, initMid);
    // 获取salary方法
    jmethodID salaryMid = env->GetMethodID(cls, "salary", "()V");
    // 调用company的salary方法
    env->CallVoidMethod(companyObj, salaryMid);
    return companyObj;
}

通过上面的方法我们能创建Company对象并返回,我们在java中调用callConstructor并增加打印:

Company company = callConstructor();
Log.i("carey", "==== callConstructor, 创建对象 " + company);

查看控制台日志打印:

image.png 我们可以看到,调用了Company的无参构造创建了对象,同时调用了对象的方法。

我们在app下build.gradle文件中配置了支持的CPU架构:

ndk {
    abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}

项目编译成功后会在 debug环境下:app/build/intermediates/cmake/debug/obj release环境下:app/build/intermediates/cmake/release/obj 下看到生成的so文件:

image.png

二. jni调用java类的有参构造方法

定义native方法:

public native Company callParamsConstructor();

在cpp文件中实现,调用company的有参构造函数,并传入名称:

extern "C"
JNIEXPORT jobject JNICALL
Java_com_carey_myndk_MainActivity_callParamsConstructor(JNIEnv *env, jobject thiz) {
    // 通过类的全类名获取类对象
    jclass cls = env->FindClass("com/carey/myndk/Company");
    // 获取构造方法 
    // 参数1类对象 
    // 参数2 <init>代表调用构造方法 
    // 参数3 (Ljava/lang/String;)V 代表有参数构造方法签名
    jmethodID initMid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V"); // 有参构造
    // 要传入的参数,公司名称
    jstring name = env->NewStringUTF("MyCompany");
    // 根据有参构造方法创建对象
    jobject companyObj = env->NewObject(cls, initMid, name);
    return companyObj;
}

在java中调用,并将公司名称显示在页面上:

Company company = callParamsConstructor();
tv.setText("公司名称:" + company.getName());

查看控制台日志输出:

image.png

页面结果显示:

image.png

三. jni调用java类成员方法,并将结果写入文件

首先在Company类中新增获取时间戳的方法:

public String getTime() {
    return String.valueOf(System.currentTimeMillis());
}

创建native方法把内容写入文件中:

public native String writeContentToFile(String path);

同时在cpp文件中实现该方法:

extern "C"
JNIEXPORT jstring JNICALL
Java_com_carey_myndk_MainActivity_writeContentToFile(JNIEnv *env, jobject thiz, jstring path) {
    // 通过类的全类名获取类对象
    jclass cls = env->FindClass("com/carey/myndk/Company");
    if (cls == NULL) {
        return NULL; // 处理类查找失败
    }
    // 获取构造方法 参数1类对象 参数2 <init>代表调用构造方法 参数3 ()V 代表无参数构造方法签名
    jmethodID initMid = env->GetMethodID(cls, "<init>", "()V"); // 无参构造
    if (initMid == NULL) {
        return NULL; // 处理方法查找失败
    }
    // 根据构造方法创建对象
    jobject companyObj = env->NewObject(cls, initMid);
    if (companyObj == NULL) {
        return NULL; // 处理对象创建失败
    }
    jmethodID getTimeMid = env->GetMethodID(cls, "getTime", "()Ljava/lang/String;");
    if (getTimeMid == NULL) {
        return env->NewStringUTF("getTimeMid == NULL"); // 处理方法查找失败
    }
    jstring jTimeStr = (jstring)env->CallObjectMethod(companyObj, getTimeMid);
    if (jTimeStr == NULL) {
        return env->NewStringUTF("jTimeStr == NULL"); // 处理方法调用失败
    }
    // 将 String 转成 C 中字符数组
    const char *time_str = env->GetStringUTFChars(jTimeStr, NULL);
    if (time_str == NULL) {
        return NULL; // 处理字符串转换失败
    }
    // 获取 SD 卡路径
    const char *path_str = env->GetStringUTFChars(path, NULL);
    if (path_str == NULL) {
        env->ReleaseStringUTFChars(jTimeStr, time_str);
        return NULL; // 处理字符串转换失败
    }

    char file_path[100] = {0};
    // 路径上拼接文件名称
    strncpy(file_path, path_str, sizeof(file_path) - 1);
    strncat(file_path, "/time_log.txt", sizeof(file_path) - strlen(file_path) - 1);
    // 存储拼接后的字符串
    char content[200] = {0};
    // 先将 time_str 内容复制到 content 中
    strncpy(content, time_str, sizeof(content) - 1);
    // 再将 "今天我很开心," 追加到 content 后面
    strncat(content, "今天我很开心,", sizeof(content) - strlen(content) - 1);

    // 将 time 时间戳内容写入到文件中
    FILE *fp = fopen(file_path, "w");
    if (fp == NULL) {
        env->ReleaseStringUTFChars(jTimeStr, time_str);
        env->ReleaseStringUTFChars(path, path_str);
        return env->NewStringUTF(file_path); // 处理文件打开失败
    }
    fputs(content, fp);
    fclose(fp);

    // 释放资源
    env->ReleaseStringUTFChars(jTimeStr, time_str);
    env->ReleaseStringUTFChars(path, path_str);
    return env->NewStringUTF("success");
}

代码中我们调用Company对象中的getTime方法获取时间戳,完了追加内容后写入到指定path路径下。我们测试下,写入到手机download目录下:

// 执行 SD 卡读写操作
private void performSDCardOperation() {
    String result = writeContentToFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath());
    Log.e("carey" , "结果 " + result);
}

这里需要我们添加读写权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
};

if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
    performSDCardOperation();
} else {
    // 请求权限
    requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_EXTERNAL_STORAGE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限被授予,执行读写操作
            performSDCardOperation();
        } else {
            // 请授予权限
        }
    }
}

调用方法写入后,可以查看到后台日志:

image.png

同时我们在手机download目录下可以看到写入的文件内容:

image.png

四. 总结

今天学习了jni调用java类的无参和有参构造函数和成员方法,并将指定内容写入到指定目录下的文件中。喜欢的点赞收藏和订阅,感谢!