Android JNI 调用

1.Android Studio创建native项目

 

 

 对项目进行解释

cmake_minimum_required(VERSION 3.10.2)

# Declares and names the project.
project("jnitest")

# 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.
        # 设置生成.so 的文件名,也是你在 java 代码里调用的名字,填一个就好了,记住!!
        jnitest


        # Sets the library as a shared library.
        # 设置库的类型 一种静态文件 STATIC .a 一种动态文件 SHARED .so
        SHARED

        # Provides a relative path to your source file(s).
        # 需要编译的c/c++ 文件,这里填相对路径
        native-lib.cpp
        test.c)

# 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.
        # 指定链接的目标库
        jnitest

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

 

2.创建完成后,就是标准的Native 项目,MainActivity里有 java 调 c 的例子

/**
     * A native method that is implemented by the 'jni' native library, which is packaged with this
     * application.
     */
    public native String stringFromJNI();
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jni_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

3.创建自定义log

#ifndef JNITEST_LOG_H
#define JNITEST_LOG_H
#define LOG_TAG "jniTest"
//#endif //JNITEST_LOG_H
//#ifndef LOGGING_H
//#define LOGGING_H
#include <android/log.h>

//定义TAG之后,我们可以在LogCat通过TAG过滤出NDK打印的日志
// 定义debug信息
#define LOGD(TAG, ...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
// 定义info信息
#define LOGI(TAG, ...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
// 定义warn信息
#define LOGW(TAG, ...) __android_log_print(ANDROID_LOG_WARN,TAG,__VA_ARGS__)
// 定义error信息
#define LOGE(TAG, ...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)

#endif //LOGGING_H

使用   LOGI(LOG_TAG,"CallIntMethod %d",ret);

4.c 调java 

总体流程

1.获取jclass

 

 

2.获取jmethodid

3.获取jclass对象

4.对象调用方法

 

详细步骤

获取你需要访问的Java对象的类

如果被Native调用的Java类是静态类:FindClass通过传java中完整的类名来查找java的class

jclass jclazz = (*env).FindClass("com/example/jnitest/Test");

如果是非静态类:GetObjectClass通过传入jni中的一个java的引用来获取该引用的类型

env->GetObjectClass(thiz)

他们之间的区别是,前者要求你必须知道完整的类名,后者要求在Jni有一个类的引用

获取调用方法的MethodID

GetMethodID 得到一个实例的方法的ID

GetStaticMethodID 得到一个静态方法的ID

获取对象的属性

GetFieldID 得到一个实例的域的ID

GetStaticFieldID 得到一个静态的域的ID

JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数

环境
jni里面调用java方法的环境分为2种:

第一种:在env所在线程调用java方法,这种情况不需要做特殊处理,直接按照步骤执行即可

第二种:在pthread子线程调用java方法,这种情况下就需要做处理了

在jni中,子线程中是不能直接调用JNIEnv对象的,也不能直接调用env线程中的jobject对象。因为jni中,JNIEnv是和线程相关的,每一个native方法所在线程就有一个当前线程相关的JNIEnv对象,而pthread线程中是不能调用native方法所在线程的JENnv对象的。

解决办法是:利用JavaVM虚拟机

JavaVM是和进程相关的,一个进程里面的JavaVM都是同一个,所以在pthread线程中就可以通过JavaVM来获取(AttachCurrentThread)当前线程的JNIEnv指针,然后就可以使用JNIEnv指针操作数据了。

还有在pthread线程中调用jobject对象时,首先需要把native线程里面的jobject创建全局引用(env->NewGlobalRef(jobj)),其返还的jobject对象就可以在程序中使用了,具体的调用函数的代码和函数的返回值相关,对应规则如下:


static JavaVM *mJavaVm = NULL;
static jclass mClass = NULL;
static bool isInitialed = false;

static
inline JNIEnv *JNI_OnLoad(bool *needsDetach) { *needsDetach = false; JNIEnv *env = NULL; int status = mJavaVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4); if (status < 0) { JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL}; int result = mJavaVm->AttachCurrentThread(&env, (void *) &args); if (result != JNI_OK) { TX_DBG("thread attach failed: %#x", result); return NULL; } *needsDetach = true; } return env; } static inline void detachJNI() { int result = mJavaVm->DetachCurrentThread(); if (result != JNI_OK) { TX_DBG("thread detach failed: %#x", result); } }

static void skTr069JniConfig(JNIEnv *env) {
jclass clazz;
env->GetJavaVM(&mJavaVm);

if (NULL == (clazz = env->FindClass(JNI_REG_CLASS))) {
TX_ERR("call FindClass(%s) failed", JNI_REG_CLASS);
return;
}
mClass = reinterpret_cast<jclass> (env->NewGlobalRef(clazz));

for (int ii = 0; ii < ARRAY_SIZE(sFuncScript); ii++) {
if (NULL == (sJavaFunction[ii] = env->GetStaticMethodID(mClass, sFuncScript[ii].name, sFuncScript[ii].type))) {
TX_ERR("call GetStaticMethodID %s(%s) failed", sFuncScript[ii].name, sFuncScript[ii].type);
return;
}
}
//env->DeleteGlobalRef(mClass);
}
 
总结:
1.在JNI_OnLoad中,保存JavaVM*,这是跨线程的,持久有效的,而JNIEnv*则是当前线程有效的。一旦启动线程,用AttachCurrentThread方法获得env。
2.通过JavaVM*和JNIEnv可以查找到jclass。
3.把jclass转成全局引用,使其跨线程。
4.然后就可以正常地调用你想调用的方法了。
5.用完后,别忘了delete掉创建的全局引用和调用DetachCurrentThread方法。
 

在java中创建要被调用的java方法和native方法

public native int callAge();

    public int getAge(){
        Log.e(TAG, "getAge: 我被C语言调用了");
        return 20;
    }
public class Test {
    public native String callName();

    public String getName(){
        Log.e("TAG", "getAge: 我被C语言调用了");
        return "wang";
    }
}

 

在native里通过 反射的方式调用java方法

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_jnitest_MainActivity_callAge(JNIEnv *env, jobject thiz) {
    // TODO: implement callAge()
    //1、获取字节码
    jclass jclazz = (*env).FindClass("com/example/jnitest/MainActivity");
    //2、获取方法
    jmethodID jmethodId = (*env).GetMethodID(jclazz,"getAge","()I");
    //3、实例化对象
    jobject jobject1 = (*env).AllocObject(jclazz);
    //4、调用方法
    //jstring jstr = (*env).NewStringUTF("这句话来自C");
    int ret = (*env).CallIntMethod(jobject1,jmethodId);
    LOGI(LOG_TAG,"CallIntMethod %d",ret);
    return ret;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jnitest_Test_callName(JNIEnv *env, jobject thiz) {
    // TODO: implement callName()
    const char *pStr = NULL;
    //1、获取字节码
    jclass jclazz = (*env).FindClass("com/example/jnitest/Test");
    //2、获取方法
    jmethodID jmethodId = (*env).GetMethodID(jclazz,"getName","()Ljava/lang/String;");
    //3、实例化对象
    jobject jobject1 = (*env).AllocObject(jclazz);
    //4、调用方法
    //jstring jstr = (*env).NewStringUTF("这句话来自C");
    auto ret = (jstring)(*env).CallObjectMethod(jobject1,jmethodId);
    if (ret) pStr = env->GetStringUTFChars(ret, NULL);
    LOGI(LOG_TAG,"CallIntMethod %s",pStr);
    env->ReleaseStringUTFChars(ret, pStr);
    //env->GetObjectClass(thiz)
    return ret;
}

JNI开发-线程操作 - 简书 (jianshu.com)

posted @ 2023-03-04 14:53  xiaowang_lj  阅读(115)  评论(0编辑  收藏  举报