Android开发中遇到c++与java方法相互调用,通过中间jni处理转换,jni注册有两种模式:
- 静态注册 Android开发通过ide生成的demo基本都是静态注册,形如:
package com.aonions.opengl.jni
object MainApp {
init {
System.loadLibrary("aonions")
}
external fun test(): String
}
对应生成jni代码
extern "C"
JNIEXPORT jstring JNICALL
Java_com_aonions_opengl_jni_MainApp_test(JNIEnv *env, jobject thiz) {
return env->NewStringUTF("test-jni");
}
如上所示:Java_com_aonions_opengl_jni_MainApp_test 中包含了java中对该方法的包名,类名,方法名,需要一一对应,缺陷很明亮,java中修改、增加、修改包名等,都需要在对应jni中一一处理,在接口过多情况下,明显不适用
- 动态注册
参考Android原生代码,其实可以发现使用Jni_onload动态注册,对其做基本封装使用
新建onLoad.cpp类
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "jni.h"
#include "LogUtils.h"
#ifdef LOG_UTILS_TAG
#undef LOG_UTILS_TAG
#endif //LOG_UTILS_TAG
#define LOG_UTILS_TAG "JNIOnload"
namespace android {
int register_MainApp(JNIEnv* env);
}
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = nullptr;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return result;
}
register_MainApp(env);
return JNI_VERSION_1_4;
}
jni动态注册提供一个JNIhelp类 头文件
/*
* JNI helper functions.
*
* This file may be included by C or C++ code, which is trouble because jni.h
* uses different typedefs for JNIEnv in each language.
*
* TODO: remove C support.
*/
#ifndef NATIVEHELPER_JNIHELP_H_
#define NATIVEHELPER_JNIHELP_H_
#include "jni.h"
#include <errno.h>
#include <unistd.h>
#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
#ifdef __cplusplus
extern "C" {
#endif
/*
* Register one or more native methods with a particular class.
* "className" looks like "java/lang/String". Aborts on failure.
* TODO: fix all callers and change the return type to void.
*/
int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);
#ifdef __cplusplus
}
#endif
#endif
方法实现 help类
#if defined(__ANDROID__)
/* libnativehelper is built by NDK 19 in one variant, which doesn't yet have the GNU strerror_r. */
#undef _GNU_SOURCE
#endif
#include "jni.h"
#include "JNIHelp.h"
#include "LogUtils.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <string>
#ifdef LOG_UTILS_TAG
#undef LOG_UTILS_TAG
#endif //LOG_UTILS_TAG
#define LOG_UTILS_TAG "JNIOnload"
extern "C" int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
if (clazz == nullptr) {
LOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
LOGD("%s, success\n", __func__);
return JNI_TRUE;
}
构建main.cpp类,集成onLoad中 注册的方法
#include "jni.h"
#include "LogUtils.h"
#include "JNIHelp.h"
#ifdef CAR_LOG_TAG
#undef CAR_LOG_TAG
#endif //CAR_LOG_TAG
#define CAR_LOG_TAG "MainApp"
#define APP_PACKAGE_CLASS_NAME "com/aonions/opengl/jni/MainApp"
namespace android {
jstring test(JNIEnv *env, jclass type){
return env->NewStringUTF("test-jni");
}
//------------------------------------jni loaded----------------------------------------------------------
static const JNINativeMethod methodsRx[] = {
{"test", "()Ljava/lang/String;", (void*)test },
};
int register_MainApp(JNIEnv *env){
return jniRegisterNativeMethods(env, APP_PACKAGE_CLASS_NAME , methodsRx, NELEM(methodsRx) );
}
};
对比静态注册,在修改包名,添加修改方法下能够快速修改完善
在onLoad类中还可以通过添加新的java实现类处理注册
namespace android {
int register_MainApp(JNIEnv* env);
//注册新类
int register_MainSecondApp(JNIEnv* env);
}
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = nullptr;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return result;
}
register_MainApp(env);
//注册新类
register_MainSecondApp(env);
return JNI_VERSION_1_4;
}
拆分类对比静态多出,但在后续迭代优化是方便
提供一个额外的jni log输出类LogUtils.h
#include <android/log.h>
#ifndef _LOG_UTILS_H_
#define _LOG_UTILS_H_ 1
#ifndef LOG_UTILS_TAG
#define LOG_UTILS_TAG "default"
#endif //LOG_UTILS_TAG
#define CarLogE(...) \
__android_log_print(ANDROID_LOG_ERROR, LOG_UTILS_TAG, __VA_ARGS__)
#define CarLogD(...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_UTILS_TAG, __VA_ARGS__)
#define CarLogI(...) \
__android_log_print(ANDROID_LOG_INFO, LOG_UTILS_TAG, __VA_ARGS__)
#define CarLogW(...) \
__android_log_print(ANDROID_LOG_WARN, LOG_UTILS_TAG, __VA_ARGS__)
#define LOGE CarLogE
#define LOGD CarLogD
#define LOGI CarLogI
#define LOGW CarLogW
#endif //_LOG_UTILS_H_
配置CMakeList.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)
# now build app's shared lib
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
# 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.
file(GLOB SRC_LIST "main/*.cpp")
file(GLOB SRC_MAIN_LIST "main/src/*.cpp")
add_library( # Sets the name of the library.
aonions
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${SRC_LIST}
${SRC_MAIN_LIST})
# 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.
# 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.
aonions
# Links the target library to the log library
# included in the NDK.
android
log)
上述导入ide中即可运行编译,但在工程量大下,项目中导入c++编译实际是很费时间,并且涉及环境问题,在不同配置下可能出现无法编译成功;
解决方案可把c++代码在linux服务器配置编译 通过cmake编译动态so库,在导入项目, 提供一个编译脚本configure.sh
rm -rf build
mkdir -p build
# shellcheck disable=SC2164
cd build
cmake \
-D CMAKE_SYSTEM_NAME=Android \
-D CMAKE_SYSTEM_VERSION=21 \
-D ANDROID_PLATFORM=android-21 \
-D CMAKE_BUILD_TYPE=Release \
-D CMAKE_TOOLCHAIN_FILE=/usr/local/ndk/android-ndk-r21b/build/cmake/android.toolchain.cmake \
-D CMAKE_ANDROID_NDK=/usr/local/ndk/android-ndk-r21b \
-D CMAKE_ANDROID_ARCH_ABI=armeabi-v7a \
..
make -j4
前提服务器配置好ndk交叉编译环境