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; }