1、JNI注册
(1)静态注册
public native String stringFromJNI(String msg);
extern "C"
JNIEXPORT void JNICALL
Java_com_jni_study_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz,jstring msg) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
(2)动态注册
jstring stringFromJNI(JNIEnv *env, jobject thiz){
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
static const JNINativeMethod gMethods[] = {
{"stringFromJNI", "()Ljava/lang/String;", (jstring*)stringFromJNI}
};
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
__android_log_print(ANDROID_LOG_INFO, "native", "Jni_OnLoad");
JNIEnv* env = NULL;
if(vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
return -1;
jclass clazz = env->FindClass("com/example/efan/jni_learn2/MainActivity");
if (!clazz){
__android_log_print(ANDROID_LOG_INFO, "native", "cannot get class: com/example/efan/jni_learn2/MainActivity");
return -1;
}
if(env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])))
{
__android_log_print(ANDROID_LOG_INFO, "native", "register native method failed!\n");
return -1;
}
return JNI_VERSION_1_4;
}
2、Java和C互调?
(1)Java调用C/C++
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
}
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_test_MainActivity_stringFromJNI(JNIEnv* env,jobject ) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
(2)C/C++调用Java
public class PrintUtils{
private void printMessage(String message) {
System.out.print("....");
}
}
JNIEXPORT void JNICALL Java_com_jni_study_MainActivity_printMessage(JNIEnv *env, jclass cls){
jclass clazz = (*env)->FindClass(env, "com/jni/study/PrintUtils");
jmethodID mid_construct = (*env)->GetMethodID(env,clazz, "<init>","()V");
jmethodID mid_instance = (*env)->GetMethodID(env, clazz, "printMessage", "(Ljava/lang/String;I)V");
jobject jobj = (*env)->NewObject(env,clazz,mid_construct);
jstring str_arg = (*env)->NewStringUTF(env,"我是实例方法");
(*env)->CallVoidMethod(env,jobj,mid_instance,str_arg);
(*env)->DeleteLocalRef(env,clazz);
(*env)->DeleteLocalRef(env,jobj);
(*env)->DeleteLocalRef(env,str_arg);
}
- jclass FindClass(const char* name) 根据类名来查找一个类,完整类名。
- jclass GetObjectClass(jobject obj) 根据一个对象,获取该对象的类。
- jclass GetSuperClass(jclass obj) 获取一个传入的对象获取他的父类的jclass。
- jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) 获取非静态方法的ID。
- jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig) 获取静态方法的ID。
3、JNI在开发中会被用到的场景
(1)加密算法
(2)图片压缩
- 使用libjpeg,在ubuntu系统下生成Android平台不同cpu架构使用的头文件和.a静态库并导入Android项目中。
- 然后通过JNI是调用头文件和静态库中的方法去进行图片压缩,比如哈夫曼算法压缩、去掉透明度等一系列方式在保证图片清晰度在Android上相差不大的情况下降低图片文件大小。
- 最后在编程成apk的时候会生成对应的.so库。
(3)音频压缩
- 使用FFAC,在ubuntu系统下生成Android平台不同cpu架构使用的头文件和.a静态库并导入Android项目中。
- 首先通过Android系统提供的AudioRecord进行音频采集。
- 然后通过FAAC将采集好PCM音频数据编码成AAC。
- 最终将AAC音频数据通过流媒体协议(rtmp或webrtc)将其发送到流媒体服务。
(4)视频压缩
- 使用x264,在ubuntu系统下生成Android平台不同cpu架构使用的头文件和.a静态库并导入Android项目中。
- 首先通过Android系统提供的Camera2进行视频数据采集。
- 然后通过x264将采集好的YUV/RGB原始视频数据编程h264。
- 最终h264视频数据通过流媒体协议(rtmp或webrtc)将其发送到流媒体服务。
(5)推流
- 使用RTMPDump,在ubuntu系统下生成Android平台不同cpu架构使用的头文件和.a静态库并导入Android项目中。
- 然后使用RTMPDump将压缩后的音频和视频上传大流媒体服务器。
4、JNI Crash定位步骤
(1)找到未strip且符号表完整的so库文件
# 这几行代码表示debug版本的so文件保留so保留符号库,这样会导致so文件很大,
# 如果要让release版本保留符号库文件,就替换成CMAKE_C_FLAGS_RELEASE和CMAKE_CXX_FLAGS_RELEASE
# 但务必在正式对外发布的时候去掉release 配置的-g选项,以免增加文件size
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
# R16之前版本的NDK默认是编译时加-g的,新版本不确定,所以需要不strip的 so文件,最好在CMake里配置一下-g
app/build/intermediaters/transforms/stripDebugSymbol/debug
(2)确定发生Crash的设备对应的CPU架构
- 在JNI Crash的日志里,如果有lib/arm, 则是armeabi-v7a架构; 如果有lib/arm64, 则是arm64-v8a架构;然后根据CPU架构找相应的toolchain:
- arm64-v8a对应的是aarch64-linux-android-4.9
- armeabi-v7a对应的是arm-linux-androideabi-4.9
(3)使用add2line和ndk-stack等工具分析JNI Crash的log
- addr2line:作用是根据内存地址找到对应的报错代码的文件名和行号。
/Android/Sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin
/media/kyle/a393d005-ebe5-42a0-8c6a-c86fdfb185c1/Android/Sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin
# -f表示显示函数名, -e表示execution,后面是包含符号库的文件 以及报错的内存地址(即Crash log里pc后的字段)
arm-linux-androideabi-addr2line -f -e xxx.so 0x8eb09258
- ndk-stack:作用是一键生成更可读的Crash日志。
/media/kyle/a393d005-ebe5-42a0-8c6a-c86fdfb185c1/Android/Sdk/ndk-bundle/ndk-stack
# -sym表示symbols
ndk-stack -sym App/build/intermediates/transforms/mergeJniLibs/release/0/lib/对应的abi目录 -dump jniCrash.log
adb logcat | ndk-stack -sym App/build/intermediates/transforms/mergeJniLibs/release/0/lib/对应的abi目录