ndk实例总结:jni实例

ndk实例总结系列

ndk实例总结:jni实例
ndk实例总结:opencv图像处理
ndk实例总结:安卓Camera与usbCamera原始图像处理
ndk实例总结补充:使用V4L2采集usb图像分析
ndk实例总结:使用fmpeg播放rtsp流
ndk实例总结:基于libuvc的双usbCamera处理
ndk实例总结补充:使用libuvc采集usb图像分析
ndk实例总结:jni日志存储

前言

本篇博客总结下个人进行ndk开发时的使用实例

log打印

使用Android Log

新建一个android_log.h文件

#include <android/log.h>

#define LOG_TAG "JNIDemo"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

在需要打印日志的源码文件中加入头文件即可

#include "android_log.h"

void test() {
    LOGI("Hello World!");
}

结果如下:

I/JNIDemo: Hello World!

注意点:Android LOG打印变量是类似c语言中printf的形式,比如输出int变量需要这样写LOGI("int:%d ",i);

使用cout

在ndk中无法直接使用cout打印日志,因此需要将cout输出流重定向给Android Log

新建一个AndroidBuf.hpp文件

#include <iostream>
#include <streambuf>
#include <android/log.h>

class AndroidBuf : public std::streambuf {
    enum {
        BUFFER_SIZE = 255,
    };

public:
    AndroidBuf() {
        buffer_[BUFFER_SIZE] = '\0';
        setp(buffer_, buffer_ + BUFFER_SIZE - 1);
    }

    ~AndroidBuf() {
        sync();
    }

protected:
    virtual int_type overflow(int_type c) {
        if (c != EOF) {
            *pptr() = c;
            pbump(1);
        }
        flush_buffer();
        return c;
    }

    virtual int sync() {
        flush_buffer();
        return 0;
    }

private:
    int flush_buffer() {
        int len = int(pptr() - pbase());
        if (len <= 0)
            return 0;

        if (len <= BUFFER_SIZE)
            buffer_[len] = '\0';

#ifdef ANDROID
        android_LogPriority t = ANDROID_LOG_INFO;
        __android_log_write(t, "Native", buffer_);
#else
        LOGI("%s", buffer_);
#endif

        pbump(-len);
        return len;
    }

private:
    char buffer_[BUFFER_SIZE + 1];
};

其实还是使用Android Log来打印日志的

使用方法:

#include "android_buf.hpp"

void test() {
    LOGI("Hello World!");
    //将cout重定向给AndroidBuf
    std::cout.rdbuf(new AndroidBuf);
    //打印日志
    std::cout << "Hello World!" << std::endl;
    //删除重定向
    delete std::cout.rdbuf(0);
}

注意:重定向和删除重定向代码分别放在app启动和app结束时就可以

string与jstring转换

两个互转的函数:

#include <jni.h>
#include <iostream>

jstring string_to_jstring(JNIEnv *env, std::string str) {
    return env->NewStringUTF(str.c_str());
}

std::string jstring_to_string(JNIEnv *env, jstring j_str) {
    const char *c = env->GetStringUTFChars(j_str, 0);
    std::string c_str = std::string(c);
    env->ReleaseStringUTFChars(j_str, c);
    return c_str;
}

jni中特别需要注意的点就是使用env指针创建的对象在使用完毕时需要销毁,如env->GetStringUTFChars创建则使用env->ReleaseStringUTFChars来销毁,普通的jclass与jobject则使用env->DeleteLocalRef,如果是全局引用env->NewGlobalRef则需要使用env->DeleteGlobalRef来销毁

但也有特例,比如要传到java层的对象就不需要销毁了,如return env->NewStringUTF(str.c_str());

使用:

jstring stringFromJNI(JNIEnv *env) {
    std::string hello = "Hello from C++";
    return string_to_jstring(env, hello);
}

void stringToJNI(JNIEnv *env, jstring str) {
    std::string s = jstring_to_string(env, str);
    std::cout << s << std::endl;
    LOGI("%s", s.c_str());
}

arrayList与vector类型转换

#include <jni.h>
#include <iostream>
#include <vector>

template<class _T>
std::vector<_T> array_list_to_vector(JNIEnv *env, jobject &arrayList) {
    //先找到要调用的类,ArrayList,这里使用全局引用,也可以使用局部引用
    jclass java_util_ArrayList = reinterpret_cast<jclass> (env->NewGlobalRef(
            env->FindClass("java/util/ArrayList")));
    //获取java方法id
    //参数2是调用的方法名,<init>表示构造函数
    //参数3表示方法签名,(I)V表示参数为int型,返回值void型
    jmethodID java_util_ArrayList_size = env->GetMethodID(java_util_ArrayList, "size", "()I");
    //同上,参数2表示add方法,参数3表示参数为Object对象,返回值为boolean型
    jmethodID java_util_ArrayList_get = env->GetMethodID(java_util_ArrayList, "get",
                                                         "(I)Ljava/lang/Object;");
    //调用size方法,获取arrayList的大小
    jint len = env->CallIntMethod(arrayList, java_util_ArrayList_size);
    //创建vector对象
    std::vector<_T> result;
    result.reserve(len);
    for (jint i = 0; i < len; i++) {
        //调用get方法,获取jobject
        jobject element = env->CallObjectMethod(arrayList, java_util_ArrayList_get, i);
        //将jobject转换成c/c++类型
        auto f = java_to_native<_T>(env, element);
        //放入victor中
        result.emplace_back(f);
        //删除局部引用
        env->DeleteLocalRef(element);
    }
    //删除全局引用
    env->DeleteGlobalRef(java_util_ArrayList);
    return result;
}

template<class _T>
jobject vector_to_array_list(JNIEnv *env, std::vector<_T> &vector) {
    //先找到要调用的类,ArrayList,这里使用全局引用,也可以使用局部引用
    jclass java_util_ArrayList = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("java/util/ArrayList")));
    //获取java方法id
    //参数2是调用的方法名,<init>表示构造函数
    //参数3表示方法签名,(I)V表示参数为int型,返回值void型
    jmethodID java_util_ArrayList_ = env->GetMethodID(java_util_ArrayList, "<init>", "(I)V");
    //同上,参数2表示add方法,参数3表示参数为Object对象,返回值为boolean型
    jmethodID java_util_ArrayList_add = env->GetMethodID(java_util_ArrayList, "add", "(Ljava/lang/Object;)Z");
    //调用构造函数创建ArrayList对象
    jobject result = env->NewObject(java_util_ArrayList, java_util_ArrayList_, vector.size());
    //遍历vector
    for (auto &v: vector) {
        //c/c++类型转jobject
        auto element = native_to_java<_T>(env, v);
        //调用add方法,将jobject添加到arrayList中
        env->CallBooleanMethod(result, java_util_ArrayList_add, element);
        //删除局部引用
        env->DeleteLocalRef(element);
    }
    //删除全局引用
    env->DeleteGlobalRef(java_util_ArrayList);
    return result;
}

这里使用了c++的模板template<class _T>,是类似java中泛型的特性,在调用模板方法时传入指定类型来调用相应的重载函数,如调用array_list_to_vector<int>后就会调用java_to_native<int>函数

还有需要注意的是,在创建arrayList的jclass时使用的是全局引用,这里只是演示一下全局引用的用法,可以使用局部引用来代替

// 全局引用
jclass java_util_ArrayList = static_cast<jclass>(env->NewGlobalRef(env->FindClass("java/util/ArrayList")));
env->DeleteGlobalRef(java_util_ArrayList);
// 局部引用
jclass java_util_ArrayList = env->FindClass("java/util/ArrayList");
env->DeleteLocalRef(java_util_ArrayList);

java类型与c/c++类型的转换:

/**
 * 声明java类型转c/c++类型的模板函数
 */
template<class _T>
_T java_to_native(JNIEnv *env, jobject &element);

/**
 * 重载int型的转换函数
 */
template<>
int java_to_native(JNIEnv *env, jobject &element) {
    //查找要调用的类
    jclass cls = env->FindClass("java/lang/Integer");
    //获取java方法id
    jmethodID getVal = env->GetMethodID(cls, "intValue", "()I");
    //删除局部引用
    env->DeleteLocalRef(cls);
    //调用intValue方法
    return env->CallIntMethod(element, getVal);
}

/**
 * 重载complex_bean类型的转换函数
 */
template<>
complex_bean java_to_native(JNIEnv *env, jobject &element) {
    //查找要调用的类
    jclass cls = env->FindClass("com/gavinandre/jnidemo/bean/ComplexBean");
    //获取java方法id
    jmethodID getInteger = env->GetMethodID(cls, "getInteger", "()I");
    jmethodID getList = env->GetMethodID(cls, "getList", "()Ljava/util/ArrayList;");
    //删除ComplexBean jclass局部引用
    env->DeleteLocalRef(cls);
    //创建c++对象
    complex_bean _complex_bean;
    //调用getInteger方法后赋值
    _complex_bean.integer = env->CallIntMethod(element, getInteger);
    //调用getList方法获取arrayList
    jobject list = env->CallObjectMethod(element, getList);
    //将arrayList转换成vector后赋值
    _complex_bean.list = array_list_to_vector<float>(env, list);
    return _complex_bean;
}

/**
 * 声明c/c++类型转java类型的模板函数
 */
template<class _T>
jobject native_to_java(JNIEnv *env, _T &element);

/**
 * 重载int型的转换函数
 */
template<>
jobject native_to_java(JNIEnv *env, int &element) {
    //查找要调用的类
    jclass cls = env->FindClass("java/lang/Integer");
    //获取java方法id
    jmethodID init = env->GetMethodID(cls, "<init>", "(I)V");
    //调用构造函数创建对象
    jobject result = env->NewObject(cls, init, element);
    //删除局部引用
    env->DeleteLocalRef(cls);
    return result;
}

/**
 * 重载complex_bean类型的转换函数
 */
template<>
jobject native_to_java(JNIEnv *env, complex_bean &element) {
    //查找要调用的类
    jclass cls = env->FindClass("com/gavinandre/jnidemo/bean/ComplexBean");
    //获取java方法id
    jmethodID init = env->GetMethodID(cls, "<init>", "()V");
    jmethodID setInteger = env->GetMethodID(cls, "setInteger", "(I)V");
    jmethodID setList = env->GetMethodID(cls, "setList", "(Ljava/util/ArrayList;)V");
    //调用构造函数创建ComplexBean对象
    jobject result = env->NewObject(cls, init);
    //调用setInteger方法
    env->CallVoidMethod(result, setInteger, element.integer);
    //将vector转换成arrayList
    jobject list = vector_to_array_list<float>(env, element.list);
    //调用setList方法
    env->CallVoidMethod(result, setList, list);
    //删除setList方法局部引用
    env->DeleteLocalRef(list);
    //删除ComplexBean jclass局部引用
    env->DeleteLocalRef(cls);
    return result;
}

使用模板来让程序自动调用相应类型的重载函数

int型与其他原始类型比较简单,原始类型例子这里只给出int型,除布尔类型的其他原始类型可以去看demo,布尔类型在vector中比较特殊,因此不建议在vector中使用布尔类型

复杂对象的转换需要多做几步,比如java对象中包含一个arrayList则需要先将对象中的arrayList转换成vector后放入native的对象中

使用:

void intListToJNI(JNIEnv *env, jobject &array_list) {
    auto result = array_list_to_vector<int>(env, array_list);
    std::cout << "intListToJNI: [";
    for (const auto &s: result) {
        std::cout << s << " ";
    }
    std::cout << "]" << std::endl;
}

void complexObjectListToJNI(JNIEnv *env, jobject &array_list) {
    auto result = array_list_to_vector<complex_bean>(env, array_list);
    std::cout << "complexObjectListToJNI: [";
    for (const auto &s: result) {
        std::cout << s.integer << " ";
        for (const auto &ss : s.list) {
            std::cout << ss << " ";
        }
    }
    std::cout << "]" << std::endl;
}

jobject intListFromJNI(JNIEnv *env) {
    std::vector<int> v_s;
    v_s.reserve(3);
    v_s.emplace_back(3);
    v_s.emplace_back(2);
    v_s.emplace_back(1);
    return vector_to_array_list<int>(env, v_s);
}

jobject complexObjectListFromJNI(JNIEnv *env) {
    std::vector<complex_bean> v_s;
    v_s.reserve(3);
    v_s.emplace_back(complex_bean(3, {3.3, 3.2, 3.1}));
    v_s.emplace_back(complex_bean(2, {2.3, 2.2, 2.1}));
    v_s.emplace_back(complex_bean(1, {1.3, 1.2, 1.1}));
    return vector_to_array_list<complex_bean>(env, v_s);
}

生成uuid

新建一个uuid_lib.hpp文件

#include <vector>
#include <iostream>
#include <sstream>
#include <random>
#include <climits>
#include <algorithm>
#include <functional>
#include <string>

unsigned char random_char() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 255);
    return static_cast<unsigned char>(dis(gen));
}

std::string generate_hex(const unsigned int len) {
    std::stringstream ss;
    for (auto i = 0; i < len; i++) {
        auto rc = random_char();
        std::stringstream hexstream;
        hexstream << std::hex << int(rc);
        auto hex = hexstream.str();
        ss << (hex.length() < 2 ? '0' + hex : hex);
    }
    return ss.str();
}

使用:

jstring uuidFromJNI(JNIEnv *env) {
    std::string uuid = generate_hex(16);
    return string_to_jstring(env, uuid);
}

调用java静态方法

#include <jni.h>
#include <iostream>
#include "android_log.h"

std::string callJavaStaticMethod(JNIEnv *env, std::string class_name,
                                 std::string method_name, std::string method_sign) {
    // 先找到要调用的类
    jclass clazz = env->FindClass(class_name.c_str());
    if (clazz == nullptr) {
        LOGE("find class %s error !", class_name.c_str());
        return nullptr;
    }
    // 获取java方法id
    // 参数二是调用的方法名,参数三是方法的签名
    jmethodID id = env->GetStaticMethodID(clazz, method_name.c_str(), method_sign.c_str());
    if (id == nullptr) {
        LOGE("find method %s error !", method_name.c_str());
        return nullptr;
    }
    // 开始调用java中的静态方法
    jstring result = static_cast<jstring>(env->CallStaticObjectMethod(clazz, id));
    // 释放资源
    env->DeleteLocalRef(clazz);
    // 将jstring转换为string
    return jstring_to_string(env, result);
}
package com.gavinandre.jnidemo.utils;

import android.os.Environment;
import android.support.annotation.Keep;

import java.io.File;

public class FileUtil {

    @Keep
    public static String getSDPath() {
        File sdDir = null;
        //判断sd卡是否存在
        boolean sdCardExist = Environment.getExternalStorageState()
                .equals(Environment.MEDIA_MOUNTED);
        //获取根目录
        if (sdCardExist) {
            sdDir = Environment.getExternalStorageDirectory();
        }
        return sdDir != null ? sdDir.toString() : null;
    }

}

注意点:需要在jni调用的函数如果没有被其他java函数调用,则会在打包编译时被优化掉,因此需要使用@Keep注解来防止被优化

使用:

jstring getSDPath(JNIEnv *env) {
    std::string sd_path = callJavaStaticMethod(
            env, "com/gavinandre/jnidemo/utils/FileUtil", "getSDPath", "()Ljava/lang/String;");
    return string_to_jstring(env, sd_path);
}

base64 编解码

新建一个base64.hpp文件

#include <iostream>
#include <string>

static const std::string base64_chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "0123456789+/";


static inline bool is_base64(unsigned char c) {
    return (isalnum(c) || (c == '+') || (c == '/'));
}

std::string base64_encode(unsigned char const *bytes_to_encode, unsigned int in_len) {
    std::string ret;
    int i = 0;
    int j = 0;
    unsigned char char_array_3[3];
    unsigned char char_array_4[4];

    while (in_len--) {
        char_array_3[i++] = *(bytes_to_encode++);
        if (i == 3) {
            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
            char_array_4[3] = char_array_3[2] & 0x3f;

            for (i = 0; (i < 4); i++)
                ret += base64_chars[char_array_4[i]];
            i = 0;
        }
    }

    if (i) {
        for (j = i; j < 3; j++)
            char_array_3[j] = '\0';

        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
        char_array_4[3] = char_array_3[2] & 0x3f;

        for (j = 0; (j < i + 1); j++)
            ret += base64_chars[char_array_4[j]];

        while ((i++ < 3))
            ret += '=';

    }

    return ret;

}

std::string base64_decode(std::string const &encoded_string) {
    int in_len = encoded_string.size();
    int i = 0;
    int j = 0;
    int in_ = 0;
    unsigned char char_array_4[4], char_array_3[3];
    std::string ret;

    while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
        char_array_4[i++] = encoded_string[in_];
        in_++;
        if (i == 4) {
            for (i = 0; i < 4; i++)
                char_array_4[i] = base64_chars.find(char_array_4[i]);

            char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

            for (i = 0; (i < 3); i++)
                ret += char_array_3[i];
            i = 0;
        }
    }

    if (i) {
        for (j = i; j < 4; j++)
            char_array_4[j] = 0;

        for (j = 0; j < 4; j++)
            char_array_4[j] = base64_chars.find(char_array_4[j]);

        char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

        for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
    }

    return ret;
}

使用:

#include "utils/base64.hpp"

jstring base64Encode(JNIEnv *env) {
    std::string str = "Hello World!";
    std::string encoded = base64_encode(reinterpret_cast<const unsigned char *>(str.c_str()), str.length());
    return string_to_jstring(env, encoded);
}

void base64decode(JNIEnv *env, jstring encode_data) {
    std::string decoded_data = base64_decode(jstring_to_string(env, encode_data));
    LOGI("base64decode %s", decoded_data.c_str());
}

完整代码见demo

ndk开发基础学习系列:

JNI和NDK编程(一)JNI的开发流程
JNI和NDK编程(二)NDK的开发流程
JNI和NDK编程(三)JNI的数据类型和类型签名
JNI和NDK编程(四)JNI调用Java方法的流程

完整demo:

https://github.com/GavinAndre/JNIDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值