前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NDK开发(九) :Hello jniCallback

NDK开发(九) :Hello jniCallback

作者头像
103style
发布2022-12-19 13:38:45
3360
发布2022-12-19 13:38:45
举报
文章被收录于专栏:Android开发经验分享

转载请以链接形式标明出处: 本文出自:103style的博客

本文操作以 Android Studio 3.5 版本为例

本文为参考官方示例 hello-jniCallback 动手写的 Demo.



功能介绍

  • 通过 JNI 获取并调用静态内部类 JniHandler 的 静态方法getBuildVersion() 和 公开方法getRuntimeMemorySize()获取 当前的系统版本 以及 当前可用内存
  • 通过 JNI 线程 实现 开始和停止每秒打印距离开始计时的秒数

编写测试代码

编写测试代码 JniCallbackDemo.

代码语言:javascript
复制
public class JniCallbackDemo {
    private static final String TAG = "JniCallbackDemo";
    static {
        System.loadLibrary("jni_callback");
    }
    private int timeCount;
    public native void startTiming();
    public native void stopTiming();

    private void printTime() {
        Log.e(TAG, "timeCount = " + timeCount);
        timeCount++;
    }
    public static class JniHandler {
        public static String getBuildVersion() {
            return Build.VERSION.RELEASE;
        }
        public long getRuntimeMemorySize() {
            return Runtime.getRuntime().freeMemory();
        }
        private void updateStatus(String msg) {
            if (msg.toLowerCase().contains("error")) {
                Log.e("JniHandler", "Native Err: " + msg);
            } else {
                Log.i("JniHandler", "Native Msg: " + msg);
            }
        }
    }
}

创建 jni_callback.cpp.

添加以下代码到CMakeLists.txt.

代码语言:javascript
复制
add_library(
        jni_callback
        SHARED
        jni_callback.cpp)
target_link_libraries(
        jni_callback
        ${log-lib})

通过JNI实现功能逻辑

获取当前的系统版本和可用内存

  • 因为这个只需要执行一次就好了,我们可以放到JNI_OnLoad方法中去实现(在应用层调用.so库首先会执行 JNI_OnLoad 方法)。
  • 获取内部类用 而不是 .,例如: env->FindClass("com/lxk/ndkdemo/JniCallbackDemo
  • 这里构建了一个结构体jniCallback保存获取的 实例
代码语言:javascript
复制
void queryRuntimeInfo(JNIEnv *env) {
    //获取getBuildVersion的方法id
    jmethodID staticMethodId = env->GetStaticMethodID(jniCallback.jniHandlerClz, "getBuildVersion",
                                                      "()Ljava/lang/String;");
    if (!staticMethodId) {
        LOGE("Failed to retrieve getBuildVersion() methodID");
        return;
    }
    //执行静态方法获取getBuildVersion的方法id
    jstring releaseVersion = static_cast<jstring>(env->CallStaticObjectMethod(
            jniCallback.jniHandlerClz, staticMethodId));
    //获取字符串的地址
    const char *version = env->GetStringUTFChars(releaseVersion, nullptr);
    if (!version) {
        LOGE("Unable to get version string");
        return;
    }
    LOGD("releaseVersion = %s", version);
    //释放字符串的内存
    env->ReleaseStringUTFChars(releaseVersion, version);
    //删除引用
    env->DeleteLocalRef(releaseVersion);

    //获取非静态方法 getRuntimeMemorySize 的id
    jmethodID methodId = env->GetMethodID(jniCallback.jniHandlerClz, "getRuntimeMemorySize", "()J");
    if (!methodId) {
        LOGE("Failed to retrieve getRuntimeMemorySize() methodID");
        return;
    }
    //调用 getRuntimeMemorySize
    jlong freeMemorySize = env->CallLongMethod(jniCallback.jniHandlerObj, methodId);

    //打印可用内存
    LOGD("Runtime free memory size: %"
                 PRId64, freeMemorySize);
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGD("JNI_OnLoad");
    JNIEnv *env;
    //给jniCallback初始化地址
    memset(&jniCallback, 0, sizeof(jniCallback));

    jniCallback.javaVM = vm;

    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    //获取JniCallbackDemo.JniHandler 类
    jclass clz = env->FindClass("com/lxk/ndkdemo/JniCallbackDemo$JniHandler");
    //赋值到结构体
    jniCallback.jniHandlerClz = static_cast<jclass>(env->NewGlobalRef(clz));
    if (!clz) {
        LOGE("FindClass JniCallbackDemo$JniHandler error");
        return JNI_ERR;
    }
    //获取构造函数
    jmethodID initMethodId = env->GetMethodID(jniCallback.jniHandlerClz, "<init>", "()V");
    //构建类
    jobject instance = env->NewObject(jniCallback.jniHandlerClz, initMethodId);
    if (!instance) {
        LOGE("NewObject jniHandler error")
        return JNI_ERR;
    }
    //赋值到结构体
    jniCallback.jniHandlerObj = env->NewGlobalRef(instance);

    //调用 JniHandler 的相关方法
    queryRuntimeInfo(env);

    jniCallback.done = 0;
    jniCallback.jniCallbackDemoObj = nullptr;
    return JNI_VERSION_1_6;
}

创建线程执实现开始计时的逻辑:

  • 通过pthread_create创建线程的时候,第一个参数:线程id的指针;第二个参数:线程属性的指针;第一个参数:在线程中运行的函数;第四个参数:运行函数的参数
代码语言:javascript
复制
extern "C"
JNIEXPORT void JNICALL
Java_com_lxk_ndkdemo_JniCallbackDemo_startTiming(JNIEnv *env, jobject instance) {
    LOGD("jni startTiming");

    //线程ID
    pthread_t threadInfo;
    //线程属性
    pthread_attr_t threadAttr;
    //初始化线程属性
    pthread_attr_init(&threadAttr);
    //设置脱离状态的属性
    pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);

    //互斥锁
    pthread_mutex_t lock;
    //初始化互斥锁
    pthread_mutex_init(&lock, nullptr);

    //获取当前类
    jclass clz = env->GetObjectClass(instance);
    //保存类和 实例 到 结构体中
    jniCallback.jniCallbackDemoClz = static_cast<jclass>(env->NewGlobalRef(clz));
    jniCallback.jniCallbackDemoObj = env->NewGlobalRef(instance);

    // StartTiming :在线程中运行的函数  jniCallback 运行函数的参数
    int result = pthread_create(&threadInfo, &threadAttr, StartTiming, &jniCallback);
    assert(result == 0);
    //删除线程属性
    pthread_attr_destroy(&threadAttr);
}

线程的执行函数:

代码语言:javascript
复制
/**
 * 调用 类 instance 的 void方法 methodId
 */
void sendJavaMsg(JNIEnv *env, jobject instance, jmethodID methodId, const char *msg) {
    LOGD("jni sendJavaMsg");
    //获取字符串
    jstring javaMsg = env->NewStringUTF(msg);
    //调用对应方法
    env->CallVoidMethod(instance, methodId, javaMsg);
    //删除本地引用
    env->DeleteLocalRef(javaMsg);
}

void *StartTiming(void *context) {
    //获取参数
    JniCallback *JniCallback = static_cast<jni_callback *>(context);
    JavaVM *javaVm = JniCallback->javaVM;
    JNIEnv *env;

    jint res = javaVm->GetEnv((void **) (&env), JNI_VERSION_1_6);
    LOGD("javaVm->GetEnv() res = %d", res);
    if (res != JNI_OK) {
        //链接到虚拟机
        res = javaVm->AttachCurrentThread(&env, nullptr);
        if (JNI_OK != res) {
            LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
            return nullptr;
        }
    } else {
        LOGE("javaVm GetEnv JNI_OK");
    }

    //获取 JniHandler 的 updateStatus 函数
    jmethodID statusId = env->GetMethodID(JniCallback->jniHandlerClz, "updateStatus",
                                          "(Ljava/lang/String;)V");

    sendJavaMsg(env, JniCallback->jniHandlerObj, statusId, "TimeThread status: initializing...");
    //获取 JniCallbackDemo 的 printTime 函数
    jmethodID timerId = env->GetMethodID(JniCallback->jniCallbackDemoClz, "printTime", "()V");
    //声明时间变量
    struct timeval beginTime, curTime, usedTime, leftTime;
    const struct timeval kOneSecond = {
            (__kernel_time_t) 1,
            (__kernel_suseconds_t) 0
    };

    sendJavaMsg(env, JniCallback->jniHandlerObj, statusId,
                "TimeThread status: prepare startTiming ...");

    while (true) {
        //获取当前的时间 赋值给 beginTime
        gettimeofday(&beginTime, nullptr);
        //占有互斥锁
        pthread_mutex_lock(&JniCallback->lock);
        //获取当前的状态
        int done = JniCallback->done;
        if (JniCallback->done) {
            JniCallback->done = 0;
        }
        //释放互斥锁
        pthread_mutex_unlock(&JniCallback->lock);

        if (done) {
            LOGD("JniCallback done");
            break;
        }

        //调用 printTime 函数
        env->CallVoidMethod(JniCallback->jniCallbackDemoObj, timerId);

        //获取当前的时间 赋值给 curTime
        gettimeofday(&curTime, nullptr);

        //计算函数运行消耗的时间
        //usedTime = curTime - beginTime
        timersub(&curTime, &beginTime, &usedTime);

        //计算需要等待的时间
        //leftTime = kOneSecond - 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, nullptr);
        } else {
            sendJavaMsg(env, JniCallback->jniHandlerObj, statusId,
                        "TimeThread error: processing too long!");
        }
    }
    sendJavaMsg(env, JniCallback->jniHandlerObj, statusId,
                "TimeThread status: ticking stopped");
    //释放线程
    javaVm->DetachCurrentThread();
    return context;
}

停止计时的逻辑:

代码语言:javascript
复制
extern "C"
JNIEXPORT void JNICALL
Java_com_lxk_ndkdemo_JniCallbackDemo_stopTiming(JNIEnv *env, jobject instance) {
    LOGD("jni stopTiming");

    //占用互斥锁
    pthread_mutex_lock(&jniCallback.lock);
    //修改线程运行的条件
    jniCallback.done = 1;
    //释放互斥锁
    pthread_mutex_unlock(&jniCallback.lock);

    //初始化等待时间
    struct timespec sleepTime;
    memset(&sleepTime, 0, sizeof(sleepTime));
    sleepTime.tv_nsec = 100000000;

    while (jniCallback.done) {
        nanosleep(&sleepTime, nullptr);
    }

    //删除引用
    env->DeleteGlobalRef(jniCallback.jniCallbackDemoClz);
    env->DeleteGlobalRef(jniCallback.jniCallbackDemoObj);
    jniCallback.jniCallbackDemoObj = nullptr;
    jniCallback.jniCallbackDemoClz = nullptr;

    //删除互斥锁
    pthread_mutex_destroy(&jniCallback.lock);
}

执行测试代码

代码语言:javascript
复制
private void testJniCallback() {
    jniCallbackRun(true);
}

private void jniCallbackRun(boolean run) {
    if (jniCallbackDemo == null) {
        jniCallbackDemo = new JniCallbackDemo();
    }
    if (run) {
        Toast.makeText(this, "开始计时,请查看控制台日志输出", Toast.LENGTH_SHORT).show();
        jniCallbackDemo.startTiming();
    } else {
        jniCallbackDemo.stopTiming();
    }
}

点击按钮jni callback test,在控制台即会输出以下信息。

代码语言:javascript
复制
08-22 17:00:34.726 18711-18711/com.lxk.ndkdemo D/JNI: [jni_callback.cpp][63]: JNI_OnLoad
08-22 17:00:34.726 18711-18711/com.lxk.ndkdemo D/JNI: [jni_callback.cpp][42]: releaseVersion = 6.0.1
08-22 17:00:34.726 18711-18711/com.lxk.ndkdemo D/JNI: [jni_callback.cpp][59]: Runtime free memory size: 2439224
08-22 17:00:34.726 18711-18711/com.lxk.ndkdemo D/JNI: [jni_callback.cpp][207]: jni startTiming
08-22 17:00:34.726 18711-18758/com.lxk.ndkdemo D/JNI: [jni_callback.cpp][120]: javaVm->GetEnv() res = -2
08-22 17:00:34.726 18711-18758/com.lxk.ndkdemo D/JNI: [jni_callback.cpp][104]: jni sendJavaMsg
08-22 17:00:34.736 18711-18758/com.lxk.ndkdemo I/JniHandler: Native Msg: TimeThread status: initializing...
08-22 17:00:34.736 18711-18758/com.lxk.ndkdemo D/JNI: [jni_callback.cpp][104]: jni sendJavaMsg
08-22 17:00:34.736 18711-18758/com.lxk.ndkdemo I/JniHandler: Native Msg: TimeThread status: prepare startTiming ...
08-22 17:00:34.736 18711-18758/com.lxk.ndkdemo E/JniCallbackDemo: timeCount = 0
08-22 17:00:35.736 18711-18758/com.lxk.ndkdemo E/JniCallbackDemo: timeCount = 1
08-22 17:00:36.736 18711-18758/com.lxk.ndkdemo E/JniCallbackDemo: timeCount = 2
08-22 17:00:37.736 18711-18758/com.lxk.ndkdemo E/JniCallbackDemo: timeCount = 3
08-22 17:00:38.736 18711-18758/com.lxk.ndkdemo E/JniCallbackDemo: timeCount = 4
08-22 17:00:39.736 18711-18758/com.lxk.ndkdemo E/JniCallbackDemo: timeCount = 5
08-22 17:00:40.736 18711-18758/com.lxk.ndkdemo E/JniCallbackDemo: timeCount = 6
08-22 17:00:41.736 18711-18758/com.lxk.ndkdemo E/JniCallbackDemo: timeCount = 7
...

相关文档


源码

以上

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 功能介绍
  • 编写测试代码
  • 通过JNI实现功能逻辑
  • 执行测试代码
  • 相关文档
  • 源码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档