NDK開發(九) :Hello jniCallback

轉載請以連接形式標明出處: 本文出自:103style的博客java

本文操做以 Android Studio 3.5 版本爲例android

本文爲參考官方示例 hello-jniCallback 動手寫的 Demo.git


NDK開發文章彙總github


功能介紹

  • 經過 JNI 獲取並調用靜態內部類 JniHandler 的 靜態方法getBuildVersion() 和 公開方法getRuntimeMemorySize()獲取 當前的系統版本 以及 當前可用內存
  • 經過 JNI 線程 實現 開始和中止每秒打印距離開始計時的秒數

編寫測試代碼

  • 編寫測試代碼 JniCallbackDemo.bash

    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.app

  • 添加如下代碼到CMakeLists.txt.函數

    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$JniHandler");.
    • 這裏構建了一個結構體jniCallback保存獲取的 實例
    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;
    }
    複製代碼
  • 建立線程執實現開始計時的邏輯:ui

    • 經過pthread_create建立線程的時候,第一個參數:線程id的指針;第二個參數:線程屬性的指針;第一個參數:在線程中運行的函數;第四個參數:運行函數的參數
    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);
    }
    複製代碼
  • 線程的執行函數:this

    /**
     * 調用 類 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;
    }
    複製代碼
  • 中止計時的邏輯:

    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);
    }
    複製代碼

執行測試代碼

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,在控制檯即會輸出如下信息。

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
...
複製代碼

相關文檔


源碼

若是以爲不錯的話,請幫忙點個讚唄。

以上


掃描下面的二維碼,關注個人公衆號 Android1024, 點關注,不迷路。

Android1024
相關文章
相關標籤/搜索