android jni编程笔记
获得方法签名
Java生成类方法签名,JNI回调应用层时特别有用
- 首先Java类需编译生成class字节码
- cd到字节码所在目录
- 执行cmd: javap -s 类名(不需要加上.class)
- 控制台即会打印签名信息,如下
Compiled from "JniParser.java"
public class com.gosuncn.libparser.jni.JniParser {
public static com.gosuncn.libparser.jni.JniParser getInstance();
descriptor: ()Lcom/gosuncn/libparser/jni/JniParser;
public void responseDataCallBack(long, int, java.lang.String);
descriptor: (JILjava/lang/String;)V
public native long createParser();
descriptor: ()J
...
}
jni-回调到应用层
在jni中经常需要将数据回调给应用层,通常的做法是将要回调给应用层的对象序列化为json字符串(cJSON此库非常好用,下载后也有例子说明),传递到应用层再反序列化为对象使用。
下面举例说明
- Step 1.声明回调
public class JniInstant {
private static final String TAG = "JniInstant";
static {
System.loadLibrary("instant");
}
private static JniInstant instance;
private JniInstant() {
}
public static JniInstant getInstance() {
if (instance == null) {
instance = new JniInstant();
}
return instance;
}
/**
* 初始化
* 此接口必须首先调用,且成功后才可使用其他接口
*
* @return 0表示成功
*/
public native int init();
//////////////////////////////////////////////////////回调///////////////////////////////////////
/**
* GxxApp异常回调
* descriptor: (I)V
* @param status 0--下线 1--上线
*/
public void gxxAppExceptionCallback(int status){
Log.e(TAG, "gxxAppExceptionCallback:status(0--下线 1--上线)= "+status);
}
- Step 2.初始化回调
// c或cpp文件中
#include <jni.h>
JavaVM *jvmInstant= NULL;
jobject objInstant = NULL;
jmethodID gxxAppExceptionCallback=NULL;
extern "C"
JNIEXPORT jint JNICALL
Java_com_gosuncn_instant_jni_JniInstant_init(JNIEnv *env, jobject instance) {
env->GetJavaVM(&jvmInstant);
//函数参数中 jobject 或者它的子类,其参数都是 local reference。Local reference 只在这个 JNI函数中有效,JNI函数返回后,引用的对象就被释放,它的生命周期就结束了。
// 若要留着日后使用,则需根据这个 local reference 创建 global reference。Global reference 不会被系统自动释放,它仅当被程序明确调用 DeleteGlobalReference 时才被回收。(JNI多线程机制)
objInstant = env->NewGlobalRef(instance);
//在子线程中不能这样用
// jclass objclass = env->FindClass( "com/gosuncn/video/JniRGBPlayer");
//这种写法可以用在子线程中,但限制了回调方法的位置必须在当前类中,如本例必须在JniInstant类里
jclass objclass = env->GetObjectClass(instance);
//此处的gxxAppExceptionCallback为应用层的回调方法名,回调方法必须在应用层的java类里,如本例是在JniInstant类里,关于方法对应的方法签名(如"(I)V")可使用javap获得,具体在文章中有说明
gxxAppExceptionCallback = env->GetMethodID(objclass, "gxxAppExceptionCallback", "(I)V");
return 0;
}
...
- Step 3.回调应用层
/**
* 判断是否已经绑定线程
* @param env 注意这里用取地址符
* @return true--已经绑定成功,false--已经绑定,无需再绑定
*/
bool attachThread(JavaVM *mJavaVM,JNIEnv* &env) {
bool attached = false;
if (mJavaVM == NULL) {
LOGE("JavaVM == NULL!!!,please check");
return attached;
}
switch (mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6)) {
case JNI_OK://已绑定
LOGI("JNI_OK");
break;
case JNI_EDETACHED://已解绑
LOGI("JNI_EDETACHED");
if (mJavaVM->AttachCurrentThread(&env, NULL) != JNI_OK) {
LOGE("Could not attach current thread. ");
} else {
attached = true;
}
break;
case JNI_EVERSION:
LOGE("Invalid java version. ");
}
return attached;
}
/**
* GxxApp异常消息回调
* @param lHandle 登陆句柄
* @param eEvent 异常事件
* @param pUserData 用户数据
*/
void funPtrExceptionCallback(long lHandle,
EnumExceptionEvent eEvent,
void *pUserData) {
JNIEnv *env;
bool attached = attachThread(jvmInstant, env);
//在此处将数据回调到应用层,,如本例中JniInstant类中的gxxAppExceptionCallback即可收到此回调
if (env != NULL && objInstant != NULL && gxxAppExceptionCallback != NULL) {
//CallVoidMethod后面为方法的参数,方法签名时有多少个,就得写多少个,本例中只有一个整型参数
env->CallVoidMethod(objInstant, gxxAppExceptionCallback, (jint) eEvent);
}
if (attached) {
jvmInstant->DetachCurrentThread();
}
}
字符处理
/**
* 将jstring转化为GBK编码char数组
* @param [in]env
* @param [in]jstr
* @param [out]returnChar 用户自己管理内存,自己开辟自己销毁
* @param [in]returnCharLen 用户开辟的returnChar的长度,如果长度不够会导致失败
* @return 字符串长度,为-1则表示失败
*/
int jstringToPCharGBK(JNIEnv *env, jstring jstr, char *returnChar, int returnCharLen) {
if (returnChar == NULL) {
return 0;
}
jclass tmpClass = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("gb2312");
jmethodID mid = env->GetMethodID(tmpClass, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
if(alen<=0){
return 0;
}
if (returnCharLen <= alen) {
return -1;
}
jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
strcpy(returnChar, reinterpret_cast<const char *>(ba));
returnChar[alen]=0;
env->ReleaseByteArrayElements(barr, ba, 0);
return alen;
}
/**
* 将GBK编码的字符串char*转化为jstring
* @param env
* @param pchar
* @return
*/
jstring charToJstringGBK(JNIEnv *env, const char *pchar ) {
// 定义java String类 strClass
jclass strClass = env->FindClass("java/lang/String");
// 获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
// 建立byte数组
jbyteArray bytes = env->NewByteArray(strlen(pat));
// 将char* 转换为byte数组
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte *) pat);
//设置String, 保存语言类型,用于byte数组转换至String时的参数
jstring encoding = env->NewStringUTF(
"gb-2312");
//将byte数组转换为java String,并输出
jstring result = (jstring) env->NewObject(strClass, ctorID, bytes, encoding);
env->DeleteLocalRef(bytes);
env->DeleteLocalRef(encoding);
return result;
}
CMakeLists.txt例子
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
set(JNILIBS_SO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs)
# 定义源文件目录
get_filename_component(CPP_SRC_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp ABSOLUTE)
# 定义源文件目录下的源文件
file(GLOB_RECURSE cpp_sources ${CPP_SRC_DIR}/*.*)
if (ANDROID_ABI MATCHES "^armeabi-v7a$")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=softfp -mfpu=neon")
elseif(ANDROID_ABI MATCHES "^arm64-v8a")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -ftree-vectorize")
endif()
# Specifies a path to native header files.
include_directories(src/main/cpp/encoder/)
include_directories(src/main/cpp/camera/)
include_directories(src/main/cpp/common/)
include_directories(src/main/cpp/audio/)
include_directories(src/main/cpp/media/)
include_directories(src/main/cpp/libyuv/include/libyuv/)
include_directories(src/main/cpp/libyuv/include/)
//添加动态库或静态库,注意动静态库都必须放置在${ANDROID_ABI}文件夹下
add_library(libcamera SHARED IMPORTED )
set_target_properties(libcamera PROPERTIES IMPORTED_LOCATION
${JNILIBS_SO_PATH}/${ANDROID_ABI}/libcamera.so )
add_library(libyuv SHARED IMPORTED )
set_target_properties(libyuv PROPERTIES IMPORTED_LOCATION
${JNILIBS_SO_PATH}/${ANDROID_ABI}/libyuv.so )
add_library(uv STATIC IMPORTED)
set_target_properties(uv
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libuv.a)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
instant
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${cpp_sources}
)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
# 如果有依赖关系,被依赖的要放置在后面,假如instant依赖libyuv,则libyuv要放置在后面
instant
libyuv
# Links the target library to the log library
# included in the NDK.
${log-lib} )
#升级到gradle4之后会报错误:More than one file was found with OS independent path 'lib/armeabi/libstreamhandler.so'
#解决办法有两个:一是删除jniLibs/armeabi/libstreamhandler.so,同时注释掉下面生成so输出路径的语句即可
#二是在当前build.gradle中添加 android{ packagingOptions { pickFirst 'lib/armeabi/libstreamhandler.so' }}
#在指定目录生成so文件,注意目录区分大小写,如jniLibs_DIR的“jniLibs”必须和后面build.gradle指定的sourceSet目录中指定的“jniLibs”完全一致
set(jniLibs_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs)
set_target_properties( instant
PROPERTIES
LIBRARY_OUTPUT_DIRECTORY
"${jniLibs_DIR}/${ANDROID_ABI}")
build.gradle例子
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions -D_LINUX -Wno-error=format-security"
}
}
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters 'armeabi-v7a'// ,'arm64-v8a','x86', 'x86_64', 'armeabi'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
packagingOptions {
pickFirst 'lib/armeabi-v7a/libinstant.so'
//假如so冲突,可在此进行排除
exclude 'lib/armeabi-v7a/libavcodec-57.so'
exclude 'lib/armeabi-v7a/libAvDecodePlugin.so'
exclude 'lib/armeabi-v7a/libavdevice-57.so'
exclude 'lib/armeabi-v7a/libavfilter-6.so'
exclude 'lib/armeabi-v7a/libavformat-57.so'
exclude 'lib/armeabi-v7a/libavutil-55.so'
exclude 'lib/armeabi-v7a/libGMFLib.so'
exclude 'lib/armeabi-v7a/libGSFoundation.so'
exclude 'lib/armeabi-v7a/libGSLog.so'
exclude 'lib/armeabi-v7a/libGSUtil.so'
exclude 'lib/armeabi-v7a/libpicture.so'
exclude 'lib/armeabi-v7a/libpostproc-54.so'
exclude 'lib/armeabi-v7a/libswresample-2.so'
exclude 'lib/armeabi-v7a/libswscale-4.so'
}
}
...