編輯:關於Android編程
/
我們已經知道了如何搭建 Qt on Android 開發環境,怎樣使用 Qt on Android ,有哪些可用的部署策略以及如何為應用簽名,是時候繼續前進了。這篇文章,我們來講 JNI 。(BogDan 啊,我等你等了好久,當時我寫《Qt on Android核心編程》時沒等到……)
因為 Qt 要實現 Android 的所有功能是不現實的。要想使用 Android 系統已經具備的功能,就需要通過 JNI 來訪問它們。 JNI 是在 Java 和 C++ 之間相互調用的唯一途徑。
這篇文章我們來學習 JNI 的基本知識,下一篇文章呢,我們將研究如何使用本文介紹的 JNI 知識來擴展我們的 Qt 應用。
網上有太多太多關於 JNI 的討論了,本文將聚焦於如何在 Android 系統上通過 Qt 來使用 JNI 。從 5.2 開始, Qt 攜帶了 Qt Android Extras 這個模塊。當我們不得不使用 JNI 時,它提供給我們更舒適的體驗。
我們會討論兩件事:
從 C++ 中調用一個 Java 函數從 Java 中回調一個 C++ 函數
使用 Qt Android Extras 來調用一個 Java 函數是相當簡單的。
首先,我們來創建一個 Java 的靜態方法:
// java file android/src/com/kdab/training/MyJavaClass.java package com.kdab.training; public class MyJavaClass { // this method will be called from C/C++ public static int fibonacci(int n) { if (n < 2) return n; return fibonacci(n-1) + fibonacci(n-2); } }
現在我們來看看怎麼從 Qt 中調用 fibonacci 這個方法。
第一步,因為我們要用到 Qt Android Extras ,所以要修改一下 .pro 文件,如下:
# Changes to your .pro file # .... QT += androidextras # ....
// C++ code #includeint fibonacci(int n) { return QAndroidJniObject::callStaticMethod (com/kdab/training/MyJavaClass // class name , fibonacci // method name , (I)I // signature , n); }
讓我們仔細地來看看這段代碼,看看裡面都有什麼:
我們使用 QAndroidJniObject::callStaticMethod 來調用一個 Java 方法第一個參數是全路徑類名,包名後跟一個類名,包名中的 . 被替換為 /第二個參數是方法名第三個參數是方法簽名最後一個參數是我們要傳遞給 Java 方法的參數
請閱讀 QAndroidJniObject 的文檔來了解方法簽名和參數類型的細節。
為了從 Java 回調 C++ 方法,你可以按下面的步驟來做:
在 Java 中使用 native 關鍵字來聲明 native 方法在 C++ 中注冊 native 方法調用 Java 裡的 native 方法
讓我們來稍稍改動一下之前的 Java 代碼:
// java file android/src/com/kdab/training/MyJavaClass.java package com.kdab.training; class MyJavaNatives { // declare the native method public static native void sendFibonaciResult(int n); } public class MyJavaClass { // this method will be called from C/C++ public static int fibonacci(int n) { if (n < 2) return n; return fibonacci(n-1) + fibonacci(n-2); } // the second method that will be called from C/C++ public static void compute_fibonacci(int n) { // callback the native method with the computed result. MyJavaNatives.sendFibonaciResult(fibonacci(n)); } }
我個人比較傾向把所有的 native 方法都隔離到一個獨立的類裡,所以我在 com.kdab.training 包內定義了 MyJavaNatives 類,聲明了 sendFibonaciResult 這個 native 方法。靜態的 compute_fibonacci 方法會調用 sendFibonaciResult 來回調 C++ ,發送計算結果,而不再通過 fibonacci 的返回值來做這件事。sendFibonaciResult ,這個方法會被 C++ 代碼調用,但它不像 fibonacci 那樣直接返回計算結果,它使用 sendFibonaciResult 這個 native 方法來回調 C++ 世界來傳遞計算結果。
如果你嘗試運行現在的代碼,會失敗。因為 sendFibonaciResult 還沒注冊, JVM 根本不知道它是神馬玩意兒。
代碼:
#include#include #ifdef __cplusplus extern C { #endif JNIEXPORT void JNICALL Java_com_kdab_training_MyJavaNatives_sendFibonaciResult(JNIEnv *env, jobject obj, jint n) { qDebug() << Computed fibonacci is: << n; } #ifdef __cplusplus } #endif
我們看到的第一件事,就是,所有的函數都必須導出為 C 函數,而不是 C++ 函數函數名字必須遵循下面的模板:Java 關鍵,包名,類名,方法名,之間用短下劃線分割當 JVM 加載 so 文件時,會掃描這個模板,自動為你注冊你所有符合這個模板的函數第一個參數 env 是 JNIEnv 類型的指針,指向一個 JNIEnv 對象第二個參數 obj ,代表你聲明這個 native 方法的那個 Java 對象對於你要注冊的每一個函數,第一和第二個參數是強制的,必須的從第三個參數開始,對應 Java 類裡聲明的 native 方法的參數,順次哦,一一對應。所以呢,我們的 C++ 代碼裡,第三個參數就對應 Java 代碼裡 sendFibonaciResult 的第一個參數。
使用這種方式來注冊和聲明 native 方法是很簡單的,但是它有幾個缺點:
函數名巨長,比如 Java_com_kdab_training_MyJavaNatives_sendFibonaciResult ,鬼才記得住,記得住敲得也煩不是庫必須導出所有的函數不安全, JVM 沒辦法檢查函數的簽名,因為函數是以 C 的方式導出的,而不是 C++ 的方式
為了使用 JNIEnv::RegisterNatives 來注冊 native 函數,我們需要做下面四步:
第一步,我們需要訪問 JNIEnv 指針。最簡單的方法是定義和導出 JNI_OnLoad 方法,每個 so 文件一次,可以定義在任何的 cpp 文件裡。第二步,為我們想導出的 C++ 方法創建一個數組第三步,使用 JniEnv::FindClass 找到聲明這些 native 方法的 Java 類的 ID 第四步,調用 JNIEnv::RegisterNatives(java_class_ID, methods_vector, n_methods)
代碼如下:
// C++ code #include#include // define our native method static void fibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n) { qDebug() << Computed fibonacci is: << n; } // step 2 // create a vector with all our JNINativeMethod(s) static JNINativeMethod methods[] = { { sendFibonaciResult, // const char* function name; (I)V, // const char* function signature (void *)fibonaciResult // function pointer } }; // step 1 // this method is called automatically by Java VM // after the .so file is loaded JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { JNIEnv* env; // get the JNIEnv pointer. if (vm->GetEnv(reinterpret_cast (&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // step 3 // search for Java class which declares the native methods jclass javaClass = env->FindClass(com/kdab/training/MyJavaNatives); if (!javaClass) return JNI_ERR; // step 4 // register our native methods if (env->RegisterNatives(javaClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) { return JNI_ERR; } return JNI_VERSION_1_6; }
static void fibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n),這是我們注冊的方法, JVM 將會調用它。第一個參數 env 指向 JNIEnv 對象第二個參數 obj 是聲明 fibonaciResult 這個本地方法的 Java 對象的引用 第一、第二個參數對於要導出的函數是強制的、必須的從第三個參數開始,對應 Java 類裡聲明的 native 方法的參數,順次哦,一一對應。所以呢,我們的 C++ 代碼裡,第三個參數就對應 Java 代碼裡 sendFibonaciResult 的第一個參數。我們把這個方法加到了 JNINativeMethod 類型的方法數組裡JNINativeMethod 結構體有下列成員:const char* name - 函數名字,必須和 Java 裡聲明的 native 方法名字一致const char* signature - 函數簽名,必須和 Java 裡聲明的 native 方法的參數一致void* fnPtr - C++函數指針,我們的代碼裡呢,就是 fibonaciResult 。如你所見, C++ 函數名字是無所謂的,因為 JVM 只需要一個指針。剩下的代碼簡單、清晰,沒必要再解釋了吧親,還有注釋呢哈。
這種方式用起來看著有那麼一點點復雜,但是它有下列好處:
C++ 方法的名字可以隨你的便親庫只需要導出一個函數安全, JVM 會校驗函數簽名
用哪種方式來注冊 native 函數是個人偏好問題,但我還是推薦使用 JNIEnv::RegisterNatives 這種方式,因為它提供了額外的保護:當 JVM 檢測到函數簽名不匹配時會拋出一個異常。
這篇文章我們學習了 JNI 的基本知識,下一篇文章呢,我們將研究如何使用本文介紹的 JNI 知識來擴展我們的 Qt 應用。我們會更多的討論 Qt on Android 應用的架構、如何擴展你應用的 Java 部分,我們還會提供一個實際的例子來說明如何在 Qt 的線程和 Java 的 UI 線程之間相互調用。
回顧一下我翻譯的 Qt on Android Episode 系列文章:
在軟件開發的過程中,為了讓軟件在不同的場景下都可以使用,所以機型適配是不可或缺並且非常重要耗時的一個環節。一:機型適配需要考慮的幾個方面:1,Android的版本2.手機
原文來自官方文檔:https://developer.android.com/guide/components/tasks-and-back-stack.html 應用
前言:為了使ListView性能更優,最普遍的方法就是添加一個ViewHolder靜態類。雖然性能有很大的提高,但是同樣也伴隨著Item控件內容顯示重復或錯亂的情況。&n
Device Administration對於這個應用,市場上很多,但是看一下評論就知道效果有多差了,因為99%一鍵鎖屏應用沒辦法卸載。今天就開發一個小應用,實現輕松點擊