Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Qt on Android Episode 5(翻譯)

Qt on Android Episode 5(翻譯)

編輯:關於Android編程

 /

我們已經知道了如何搭建 Qt on Android 開發環境,怎樣使用 Qt on Android ,有哪些可用的部署策略以及如何為應用簽名,是時候繼續前進了。這篇文章,我們來講 JNI 。(BogDan 啊,我等你等了好久,當時我寫《Qt on Android核心編程》時沒等到……)

為什麼需要 JNI

因為 Qt 要實現 Android 的所有功能是不現實的。要想使用 Android 系統已經具備的功能,就需要通過 JNI 來訪問它們。 JNI 是在 Java 和 C++ 之間相互調用的唯一途徑。

JNI 簡介

這篇文章我們來學習 JNI 的基本知識,下一篇文章呢,我們將研究如何使用本文介紹的 JNI 知識來擴展我們的 Qt 應用。

網上有太多太多關於 JNI 的討論了,本文將聚焦於如何在 Android 系統上通過 Qt 來使用 JNI 。從 5.2 開始, Qt 攜帶了 Qt Android Extras 這個模塊。當我們不得不使用 JNI 時,它提供給我們更舒適的體驗。

我們會討論兩件事:

 

從 C++ 中調用一個 Java 函數從 Java 中回調一個 C++ 函數

 

從 C++ 中調用一個 Java 函數

使用 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);
    }
}

如你所見,我們在 com.kdab.training 包內的 MyJavaClass 類內定義了 fibonacci 這個靜態方法,這個方法會計算 fibonacci 數並返回結果。

 

現在我們來看看怎麼從 Qt 中調用 fibonacci 這個方法。

第一步,因為我們要用到 Qt Android Extras ,所以要修改一下 .pro 文件,如下:

 

# Changes to your .pro file
# ....
QT += androidextras
# ....

第二步,執行調用:

 

 

// C++ code
#include 
int fibonacci(int n)
{
    return QAndroidJniObject::callStaticMethod
                        (com/kdab/training/MyJavaClass // class name
                        , fibonacci // method name
                        , (I)I // signature
                        , n);
}

Yes ! 這就是所有的事兒喽。

 

讓我們仔細地來看看這段代碼,看看裡面都有什麼:

 

我們使用 QAndroidJniObject::callStaticMethod 來調用一個 Java 方法第一個參數是全路徑類名,包名後跟一個類名,包名中的 . 被替換為 /第二個參數是方法名第三個參數是方法簽名最後一個參數是我們要傳遞給 Java 方法的參數

 

請閱讀 QAndroidJniObject 的文檔來了解方法簽名和參數類型的細節。

從 Java 中回調一個 C++ 函數

為了從 Java 回調 C++ 方法,你可以按下面的步驟來做:

 

在 Java 中使用 native 關鍵字來聲明 native 方法在 C++ 中注冊 native 方法調用 Java 裡的 native 方法

 

使用 native 關鍵字聲明 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 根本不知道它是神馬玩意兒。

使用 Java_Fully_Qualified_ClassName_MethodName 注冊函數

代碼:

 

#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::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 系列文章:

 

  • Qt on Android Episode 1(翻譯)Qt on Android Episode 2(翻譯)Qt on Android Episode 3(翻譯)Qt on Android Episode 4(翻譯)

     

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved