资料

正文

这个功用是点击按钮,然后在native 触发一个线程并回调界面中的方法实现UI刷新。那么涉及到的知识点就包含了:

  • 创立一个非静态的native 函数。
  • 经过jobject 反射activity中的某个方法。
  • 创立一个用完就被毁掉的native 线程。
  • 能够在正确的机遇封闭线程。

配置

cmake_minimum_required(VERSION 3.4.1)
add_library(hello-jnicallback SHARED
            hello-jnicallback.c)
target_link_libraries(hello-jnicallback
                      android
                      log
        )

能够看到,这个项目运用的是C,一起so 文件叫 hello-jnicallback。一起还是需求cmake 进行打包编译。

源码

界说了一个不可变量:

static const char *kTAG = "hello-jniCallback";
  • static 表明这个只在文件内可用
  • const 表明这个值是不可变量,声明晰就不可更改了。
  • char* 表明字符串指针,指向了一个字符数组。

宏界说,界说函数:

#define LOGI(...) 
  ((void)__android_log_print(ANDROID_LOG_INFO, kTAG, __VA_ARGS__))
#define LOGW(...) 
  ((void)__android_log_print(ANDROID_LOG_WARN, kTAG, __VA_ARGS__))
#define LOGE(...) 
  ((void)__android_log_print(ANDROID_LOG_ERROR, kTAG, __VA_ARGS__))
  • #define 界说宏
  • logi(…) 表明宏的称号,… 表明这个宏能够承受任意数量的参数。
  • (void) 这个将确保不会发生任何回来值,由于__android_log_print 实际上回来了一个整数值,运用void 能够防止发生正告或过错。

界说结构体并界说别号:

typedef struct tick_context {
  JavaVM *javaVM;
  jclass jniHelperClz;
  jobject jniHelperObj;
  jclass mainActivityClz;
  jobject mainActivityObj;
  pthread_mutex_t lock;
  int done;
} TickContext;

界说文件内变量:

TickContext g_ctx;

获取ABI类型

JNIEXPORT jstring JNICALL
Java_com_example_hellojnicallback_MainActivity_stringFromJNI(JNIEnv *env,
                                                             jobject thiz) {
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#if defined(__ARM_NEON__)
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a/NEON (hard-float)"
#else
#define ABI "armeabi-v7a/NEON"
#endif
#else
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a (hard-float)"
#else
#define ABI "armeabi-v7a"
#endif
#endif
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#define ABI "x86"
#elif defined(__x86_64__)
#define ABI "x86_64"
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#else
#define ABI "unknown"
#endif
  return (*env)->NewStringUTF(env,
                              "Hello from JNI !  Compiled with ABI " ABI ".");
}

这段代码是一个用于确认方针渠道ABI(应用程序二进制接口)的宏界说。它依据方针渠道的特性界说了不同的ABI字符串。以下是对这段代码的具体解说:

  1. #if defined(__arm__):查看是否界说了__arm__宏,这一般表明方针渠道是ARM架构
  2. #if defined(__ARM_ARCH_7A__):在ARM架构下,进一步查看是否界说了__ARM_ARCH_7A__宏,这一般表明方针渠道是ARMv7-A架构。
  3. #if defined(__ARM_NEON__):在ARMv7-A架构下,进一步查看是否界说了__ARM_NEON__宏,这表明方针渠道支撑NEON指令集。
  4. #if defined(__ARM_PCS_VFP):在ARMv7-A架构且支撑NEON指令集的情况下,进一步查看是否界说了__ARM_PCS_VFP宏,这表明方针渠道支撑浮点运算。

依据以上条件,界说了不同的ABI字符串:

  • 假如方针渠道是ARMv7-A架构,支撑NEON指令集,且支撑浮点运算,则ABI界说为armeabi-v7a/NEON (hard-float)
  • 假如方针渠道是ARMv7-A架构,支撑NEON指令集,但不支撑浮点运算,则ABI界说为armeabi-v7a/NEON
  • 假如方针渠道是ARMv7-A架构,不支撑NEON指令集,但支撑浮点运算,则ABI界说为armeabi-v7a (hard-float)
  • 假如方针渠道是ARMv7-A架构,既不支撑NEON指令集,也不支撑浮点运算,则ABI界说为armeabi-v7a
  1. 假如方针渠道不是ARMv7-A架构,则依据其他条件界说其他的ABI字符串:
    • 假如方针渠道是x86架构,则ABI界说为x86
    • 假如方针渠道是x86_64架构,则ABI界说为x86_64
    • 假如方针渠道是MIPS64架构,则ABI界说为mips64
    • 假如方针渠道是MIPS架构,则ABI界说为mips
    • 假如方针渠道是ARM64-v8a架构,则ABI界说为arm64-v8a
  2. 假如以上全部条件都不满足,则界说ABI为unknown

这段代码的首要意图是为了确认方针渠道的ABI,以便进行相应的编译和链接。

经过JNI_OnLoad进行初始化

JNI_OnLoad 的调用机遇是当Java 调用这个玩意的时分触发。所以这个触发机遇是非常早的,能够进行一些数据的初始化。

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  JNIEnv *env;
  memset(&g_ctx, 0, sizeof(g_ctx));
  g_ctx.javaVM = vm;
  if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;  // JNI version not supported.
  }
  jclass clz =
      (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
  g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);
  jmethodID jniHelperCtor =
      (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
  jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
  g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);
  queryRuntimeInfo(env, g_ctx.jniHelperObj);
  g_ctx.done = 0;
  g_ctx.mainActivityObj = NULL;
  return JNI_VERSION_1_6;
}
  • memset(&g_ctx, 0, sizeof(g_ctx)); 经过memset重置内存区域设置为特定的值,说白了便是请求内存。Java 目标经过创立目标去请求内存。
  • 然后经过 (*vm)->GetEnv(vm,(void **)&env,JNI_VERSION_1_6) 获取到env 目标。假如获取不到,就回来过错。
  • (*env)->ReleaseStringUTFChars(env, buildVersion, version); 开释本地字符串。
  • (*env)->DeleteLocalRef(env, buildVersion) 用于开释本地为 Java 目标创立的引用。

创立线程并开端使命

需求运用pthread:

#include <pthread.h>
JNIEXPORT void JNICALL
Java_com_example_hellojnicallback_MainActivity_startTicks(JNIEnv *env,
                                                          jobject instance) {
    pthread_t threadInfo_;
    pthread_attr_t threadAttr_;
    pthread_attr_init(&threadAttr_);
    pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED);
    pthread_mutex_init(&g_ctx.lock, NULL);
    jclass clz = (*env)->GetObjectClass(env, instance);
    g_ctx.mainActivityClz = (*env)->NewGlobalRef(env, clz);
    g_ctx.mainActivityObj = (*env)->NewGlobalRef(env, instance);
    int result = pthread_create(&threadInfo_, &threadAttr_, UpdateTicks, &g_ctx);
    assert(result == 0);
    pthread_attr_destroy(&threadAttr_);
    (void) result;
}
  • pthread_attr_init 初始化线程特点目标。

  • pthread_attr_setdetachstate 设置线程特点未 别离状况,当创立线程后当即别离,他不会等待其他线程完结,而是执行完结后当即停止。

  • pthread_mutex_init 设置未互斥锁,防止多个线程一起拜访和修正这些资源。

  • pthread_create 创立一个新的线程。

    • thread 一个指向线程标识符的指针,这个标识符由这个函数回来。

    • attr 线程的特点指针。

    • start_routine 一个函数指针,承受一个void* 发生,一般用作传递给线程的参数,并回来一个void. 而这儿便是 UpdateTicks。

    • arg 传递给start_routine 的参数。

  • pthread_attr_destroy 毁掉attr 目标。

  • assert 断语,当为false 的时分,就停止报错了。

线程调用的函数

void *UpdateTicks(void *context) {
    TickContext *pctx = (TickContext *) context;
    JavaVM *javaVM = pctx->javaVM;
    JNIEnv *env;
    LOGE("添加到JVM中");
    // 测验查询,
    jint res = (*javaVM)->GetEnv(javaVM, (void **) &env, JNI_VERSION_1_6);
    if (res != JNI_OK) {
        // 把当时线程附加到JVM上。
        res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
        if (JNI_OK != res) {
            LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
            return NULL;
        }
    }
    jmethodID statusId = (*env)->GetMethodID(
            env, pctx->jniHelperClz, "updateStatus", "(Ljava/lang/String;)V");
    sendJavaMsg(env, pctx->jniHelperObj, statusId,
                "TickerThread status: initializing...");
    // get mainActivity updateTimer function
    jmethodID timerId =
            (*env)->GetMethodID(env, pctx->mainActivityClz, "updateTimer", "()V");
    struct timeval beginTime, curTime, usedTime, leftTime;
    const struct timeval kOneSecond = {(__kernel_time_t) 1,
                                       (__kernel_suseconds_t) 0};
    // 发送一次音讯
    sendJavaMsg(env, pctx->jniHelperObj, statusId,
                "TickerThread status: start ticking ...");
    // 开端死循环
    while (1) {
        LOGE("---");
        gettimeofday(&beginTime, NULL);
        // 确定
        pthread_mutex_lock(&pctx->lock);
        int done = pctx->done;
        if (pctx->done) {
            pctx->done = 0;
        }
        // 解锁
        pthread_mutex_unlock(&pctx->lock);
        if (done) {
            // 跳出循环
            break;
        }
        (*env)->CallVoidMethod(env, pctx->mainActivityObj, timerId);
        gettimeofday(&curTime, NULL);
        timersub(&curTime, &beginTime, &usedTime);
        timersub(&kOneSecond, &usedTime, &leftTime);
        struct timespec sleepTime;
        sleepTime.tv_sec = leftTime.tv_sec;
        sleepTime.tv_nsec = leftTime.tv_usec * 1000;
        if (sleepTime.tv_sec <= 1) {
            nanosleep(&sleepTime, NULL);
        } else {
            sendJavaMsg(env, pctx->jniHelperObj, statusId,
                        "TickerThread error: processing too long!");
        }
    }
    sendJavaMsg(env, pctx->jniHelperObj, statusId,
                "TickerThread status: ticking stopped");
    // 将线程 从Jvm中移除
    (*javaVM)->DetachCurrentThread(javaVM);
    LOGE("将线程 从Jvm中移除 ");
    return context;
}
  • TickContext *pctx = (TickContext *) context; 结合上面的pthread_create,最后一个参数是给这个函数的入参,所以这个context 其实是这个目标。
  • (*javaVM)->AttachCurrentThread(javaVM, &env, NULL) 将当时线程附加到JVM上。
  • struct timeval beginTime, curTime, usedTime, leftTime; 声明timeval的结构体。
  • gettimeofday 获取当时时间。
  • pthread_mutex_lock 确定当时线程
  • pthread_mutex_unlock 解锁当时线程
  • timersub 计算两个值,并赋予差值,最后一个参数便是结果。
  • (*javaVM)->DetachCurrentThread(javaVM) 将数据从JVM中移除。

发送音讯

void sendJavaMsg(JNIEnv *env, jobject instance, jmethodID func,
                 const char *msg) {
    jstring javaMsg = (*env)->NewStringUTF(env, msg);
    (*env)->CallVoidMethod(env, instance, func, javaMsg);
    (*env)->DeleteLocalRef(env, javaMsg);
}
  • (*env)->NewStringUTF(env, msg) 创立一个Java 目标
  • (*env)->CallVoidMethod(env, instance, func, javaMsg) 调用Java 函数
  • (*env)->DeleteLocalRef(env, javaMsg) 删除创立的目标

发送音讯到Java 层,然后GC。

停止线程

JNIEXPORT void JNICALL Java_com_example_hellojnicallback_MainActivity_StopTicks(
        JNIEnv *env, jobject instance) {
    // 加锁
    pthread_mutex_lock(&g_ctx.lock);
    g_ctx.done = 1;
    // 解锁
    pthread_mutex_unlock(&g_ctx.lock);
    // waiting for ticking thread to flip the done flag
    struct timespec sleepTime;
    memset(&sleepTime, 0, sizeof(sleepTime));
    sleepTime.tv_nsec = 100000000;
    while (g_ctx.done) {
        nanosleep(&sleepTime, NULL);
    }
    // release object we allocated from StartTicks() function
    (*env)->DeleteGlobalRef(env, g_ctx.mainActivityClz);
    (*env)->DeleteGlobalRef(env, g_ctx.mainActivityObj);
    g_ctx.mainActivityObj = NULL;
    g_ctx.mainActivityClz = NULL;
    // 开释互斥锁
    pthread_mutex_destroy(&g_ctx.lock);
}
  • pthread_mutex_lock 加锁
  • pthread_mutex_unlock 解锁
  • memset 重置请求内存

总结

思路是创立一个线程,然后经过nanosleep 对线程进行暂停,一起创立线程的时分,在里面敞开一个死循环,当需求封闭的时分,经过不停的暂停,然后去封闭死循环。这儿就和Java 有许多不一样的了,C是直接面向内存,不存在Java直接创立的目标的说法,Java 创立目标后请求内存,并重置内存,但是C需求调用memset才行,假如不调用,就可能呈现没有被赋值的目标呈现数据紊乱的问题。感觉C才是全部皆文件的最好诠释,Java 不面向内存开发,感觉不出来这种感觉。这儿面的代码也包含了请求了的内存的手动回收,就很契合我对C的刻板印象。