JNI代码如何和原生代码优雅的结合这个问题你有没有考虑过,我今天认真思考了一下,希望能与君共勉,思考一下我如何在自己的代码中串烧一下NDK代码,希望能大家一块思考一下,是否有更好的方式来实现;
首先第一步,我们Android Studio建立一个C++项目 为了更小的NDK so动态库的轻便运行,
gradle文件要做一步小小的配置
android {
compileSdkVersion 29
defaultConfig {
applicationId "com.dr.demo.componentapp"
minSdkVersion 14
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
ndk{
abiFilters "armeabi-v7a"
}
}
}
}第二步:在动手书写项目的是我要思考一下如何设计一下自己的Base类,BaseActivity中首先需要抽象业务层,
public abstract class BaseActivity extends AppCompatActivity implements JNILogMessageCall {
//JNI 业务结构抽象出来的接口
protected JNIManager jniManager;
//如果需要打印日志我们可以在自己的业务逻辑中增加一个日志打印
private TextView logTextView;
protected abstract JNIManager getJNIManager();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
jniManager = getJNIManager();
if (jniManager != null) {
jniManager.setJNILogMessageCall(this);
}
logTextView = findViewById(R.id.logView);
initView();
}
public abstract void initView();
public abstract int getLayoutId();
@Override
public void logMessage(String message) {
Log.i("ydr", message);
if (logTextView != null) {
logTextView.append(message);
logTextView.append("\n");
}
}
} 为了更加优雅的将JNI业务层附加进来我们稍微的设计下接口JNILogMessageCall(JNI层Log日志打印),JNIManager(JNI抽象接口)
public interface JNILogMessageCall {
void logMessage(String message);
}
public interface JNIManager {
void setJNILogMessageCall(JNILogMessageCall messageCall);
}
真正的JNI业务类可以这么来设计
public class TestJNIManager implements JNIManager {
JNILogMessageCall messageCall;
static {
System.loadLibrary("demo01-lib");
}
@Override
public void setJNILogMessageCall(JNILogMessageCall messageCall) {
this.messageCall = messageCall;
}
public native String stringFromJNI();
//我们在JNI层通JNI调用到Java的这个方法来实现C调用Java层的方法
private void setLogMessage(String message) {
if (messageCall != null) {
messageCall.logMessage(message);
}
}
}cpp下面的编辑器自动为我们构建的native.cpp我们可以稍微设计一下在cpp创建一个文件夹demo01,自己创建一个CMakeList.txt组织一下目前的项目编译的动态库
aux_source_directory(. demo01)
//指定头文件目录
include_directories(../include)
add_library(
demo01-lib
SHARED
${demo01})
//链接的动态库
target_link_libraries(demo01-lib
log)
然后cpp目录下的CMakeList.txt指定demo目录,
cmake_minimum_required(VERSION 3.4.1)
add_subdirectory(demo01)这样我们就可以在cpp项目结构下面构架多个文件目录demo01 demo02....等等
我们的C++业务代码如何组织呢,我们可以有两种方式一种是编译器起初定位Java全类名+方法名来定位到我们的JNI函数,另外我们可以通过动态的方式来实现
首先我们要引入<jni.h>头文件
定义两个成员:
const char *className = "com/dr/demo/componentapp/jni/TestJNIManager";
static JNINativeMethod methods[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
};
//这个成员是动态链接JNI中C++ 和 Javanative方法然后实现JNI中两个方法:
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) 注册和反注册我们java中定义的native方法
jint registerMethod(JNIEnv *env, jint version) {
jclass clazz = env->FindClass(className);
if (clazz == nullptr) {
return JNI_ERR;
}
jint size = sizeof(methods) / sizeof(methods[0]);
jint res = env->RegisterNatives(clazz, methods, size);
if (res == 0) {
return version;
}
return JNI_ERR;
}
void unRegisterMethod(JNIEnv *env, jint version) {
jclass clazz = env->FindClass(className);
if (className != nullptr) {
env->UnregisterNatives(clazz);
}
}
//注册
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
jint res = vm->GetEnv((void **) &env, JNI_VERSION_1_4);
if (res == 0) {
return registerMethod(env, JNI_VERSION_1_4);
} else if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == 0) {
return registerMethod(env, JNI_VERSION_1_6);
}
return JNI_ERR;
}
//反注册
JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
if ((vm->GetEnv((void **) &env, JNI_VERSION_1_4))) {
unRegisterMethod(env, JNI_VERSION_1_4);
} else if ((vm->GetEnv((void **) env, JNI_VERSION_1_6))) {
unRegisterMethod(env, JNI_VERSION_1_6);
}
}具体的JNI业务实现这样来实现
//缓存日志的方法
static jmethodID methodID = NULL;
static void logInfo(JNIEnv *env, jobject _clazz, const char *format, ...) {
if (NULL == methodID) {
jclass clazz = env->GetObjectClass(_clazz);
methodID = env->GetMethodID(clazz, "setLogMessage", "(Ljava/lang/String;)V");
env->DeleteLocalRef(clazz);
}
if (NULL != methodID) {
//格式化日志消息
char buffer[MAX_LOG_MESSAGE_LENGTH];
va_list ap;
va_start(ap, format);
vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, format, ap);
va_end(ap);
//将缓冲区转换为Java字符窜
jstring message = env->NewStringUTF(buffer);
if (NULL != message) {
//记录消息
env->CallVoidMethod(_clazz, methodID, message);
//释放消息引用
env->DeleteLocalRef(message);
}
}
}
//真正要实现的代码
extern "C" JNIEXPORT jstring JNICALL stringFromJNI(
JNIEnv *env,
jobject thiz) {
std::string hello = "Hello from C++";
logInfo(env, thiz, "log info");
return env->NewStringUTF(hello.c_str());
}项目测试已经通过,欢迎留言讨论 附上源码