如何让JNI优雅的融入你的Android项目中来

1,712 阅读3分钟
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());
}


项目测试已经通过,欢迎留言讨论 附上源码

github.com/tank2014gz/…