资料
正文
这个功用是点击按钮,然后在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字符串。以下是对这段代码的具体解说:
-
#if defined(__arm__)
:查看是否界说了__arm__
宏,这一般表明方针渠道是ARM架构。 -
#if defined(__ARM_ARCH_7A__)
:在ARM架构下,进一步查看是否界说了__ARM_ARCH_7A__
宏,这一般表明方针渠道是ARMv7-A架构。 -
#if defined(__ARM_NEON__)
:在ARMv7-A架构下,进一步查看是否界说了__ARM_NEON__
宏,这表明方针渠道支撑NEON指令集。 -
#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
。
- 假如方针渠道不是ARMv7-A架构,则依据其他条件界说其他的ABI字符串:
- 假如方针渠道是x86架构,则ABI界说为
x86
。 - 假如方针渠道是x86_64架构,则ABI界说为
x86_64
。 - 假如方针渠道是MIPS64架构,则ABI界说为
mips64
。 - 假如方针渠道是MIPS架构,则ABI界说为
mips
。 - 假如方针渠道是ARM64-v8a架构,则ABI界说为
arm64-v8a
。
- 假如方针渠道是x86架构,则ABI界说为
- 假如以上全部条件都不满足,则界说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的刻板印象。