ndk实例总结系列
ndk实例总结:jni实例
ndk实例总结:opencv图像处理
ndk实例总结:安卓Camera与usbCamera原始图像处理
ndk实例总结补充:使用V4L2采集usb图像分析
ndk实例总结:使用fmpeg播放rtsp流
前言
本篇博客总结下个人进行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方法的流程