1. 程式人生 > >Android studio 下 JNI 開發實例

Android studio 下 JNI 開發實例

ndk create void 上層 執行效率 約束 添加 src 傳輸協議

在AS中進行 NDK 開發之前,我們先來簡單的介紹幾個大家都容易搞懵的概念:

  1. 到底什麽是JNI,什麽是NDK?

  2. 何為“交叉編譯”?

先看什麽是 JNI?JNI 的全稱就是 Java Native Interface,即java本地開發接口。可能大家和我一樣,一聽到接口什麽的就犯懵:“我也知道這是java本地開發接口的意思,但它具體是個什麽意思我還是搞不明白。”其實JNI它就是一種協議,一說協議,那它就是對某種東西的一個規範和約束,說的好聽一點就是標準化。如果你想用我這個東西,那你必須要遵守我這邊的規範。像http協議一樣,http作為超文本傳輸協議,它規範了我們上網時從客戶端到服務器端等一系列的運作流程。正因為如此,我們才能暢通無阻的上網。那麽換做JNI也一樣,只不過JNI這個協議是用來溝通java代碼和外部的本地代碼(c/c++)。也就是說有了JNI這個協議,我們才能夠隨意的讓java代碼調用C/C++的代碼,同樣C/C++的代碼也可以調用java的代碼。如果沒有這個協議作為支撐,那麽java和C/C++代碼想要相互調用是不可能的。下面通過兩個圖簡單看一下JNI協議在系統架構中處於什麽位置:

技術分享圖片

在上圖中,上層綠色的部分一般都是用 Java 代碼寫的,下層橘黃色的部分一般都是用 C/C++ 代碼寫的。可以看出,正式由於有了中間 JNI 的存在我們才可以在 Application 層通過 JNI 調用下層中的一些東西。了解了JNI 的概念後,我們再看看 NDK,NDK(Native Development Kit)就比較好理解了,它就是一個本地開發的“工具包”。Java 開發要用到 JDK,Android 開發要用到 SDK,那我們在 Android 中要進行 native 開發,也要用到它對應的工具包,即 NDK。通俗的來講,NDK 就是幫助我們可以在Android應用中使用 C/C++ 來完成特定功能的一套工具。 NDK的作用有很多,我們簡單的列舉兩個,比如:

1. 首先 NDK 可以幫助開發者“快速”開發 C(或C++) 的動態庫。

2. 其次,NDK 集成了“交叉編譯器”。使用 NDK,我們可以將要求高性能的應用邏輯使用 C 開發,從而提高應用程序的執行效率。

上面提到了“交叉編譯”,我們最後再解釋一下什麽是交叉編譯。大家都知道編譯器在將中間代碼連接成當前計算機可執行的二進制程序時,連接程序會根據當前計算機的 CPU、操作系統的類型來轉換。而根據運行的設備的不同,CPU 的架構也是不同,大體有如下三種常見的 CUP 架構:

  • arm 結構 :主要在移動手持、嵌入式設備上。我們的手機幾乎都是使用的這種 CUP 架構。

  • x86 結構 : 主要在臺式機、筆記本上使用。如 Intel 和 AMD 的 CPU 。

  • MIPS 架構:多用在網關、貓、機頂盒等設備。

若想在使用了基於 x86 架構 CPU 的操作系統上編譯出可以在基於 arm 結構 CPU 的操作系統上運行的代碼,就必須使用交叉編譯。所以綜上所述:交叉編譯就是在一個平臺下(比如:CPU 架構為 X86,操作系統為 Windows)編譯出在另一個平臺上(比如:CPU 架構為 arm, 操作系統為 Linux)可以執行的二進制代碼。Google 提供的 NDK 就可以完成交叉編譯的工作。 好了,上面的基本概念介紹完以後,我們正式進入 AS 下 NDK 開發的講解。

準備工作

首先,你需要把 NDK 相關的下載下來。如下圖所示,紅色框選中的都是開發中需要用到的。

  • NDK:通過 NDK-build 方法來使用本地庫

  • CMake:通過 CMake 方法來使用本地庫

  • LLDB:用來調試 C/C++ 的工具

技術分享圖片

編寫代碼

配置好 NDK 開發環境之後,在項目的布局文件添加一個 TextView,通過調用底層自己寫好的 C/C++ 代碼來返回一個字符串,最後呈現在 TextView上。

具體代碼內容如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.text);
        textView.setText(JNIUtils.getString());
    }
}

其中的 JNIUtils 下面馬上就會提到。接下去,在 MainActivity 同級,新建一個類,包含一個 native 方法。

public class JNIUtils {
   public static native String getString(); }

但是,會發現,方法名是飄紅的,說明還沒有被識別:

技術分享圖片

把鼠標放到上面,它會提示我們對應的JNI頭文件沒有查找到。那麽接下來我們要做的就是去生成與這個 getString() 方法所對應的頭文件。

技術分享圖片

生成 .h 頭文件

在 AS 自帶的 Terminal 命令行窗口中輸入如下幾條指令,回車:

cd app
cd src/main/java
javah -classpath . -jni com.example.shenjiaqi.myapplication.JNIUtils

使用 javac 命令將 JNIUtils.java 進行編譯,然後使用 javah -jni 命令編譯獲取 jni 所需要的頭文件。

這裏我們采用如下命令:

// javah -classpath . -jni 包名.類名。
javah -classpath . -jni com.example.shenjiaqi.myapplication.JNIUtils

註意編譯命令一定得在 java 目錄下下運行。編譯成功沒有遇到坑的話,你就可以在 ···\src\main\java 目錄下看到一個.h文件。

技術分享圖片

接下來在項目中創建一個 jni 目錄,並將剛生成的 .h 文件剪切至這個目錄:

技術分享圖片

我們先來查看一下這個 .h 文件的內容。這裏面用 java 的概念來說就相當於接口內的抽象方法,需要我們創建 .c 文件來實現這些方法同時也就將我們的定義的 native 方法實現了:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_shenjiaqi_myapplication_JNIUtils */

#ifndef _Included_com_example_shenjiaqi_myapplication_JNIUtils
#define _Included_com_example_shenjiaqi_myapplication_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_shenjiaqi_myapplication_JNIUtils
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_shenjiaqi_myapplication_JNIUtils_getString
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

接著,新建一個 c++ 的文件,在 jni 目錄下創建一個 JNIHello.cpp 文件來實現 .h 文件中的抽象方法:

#include "com_example_shenjiaqi_myapplication_JNIUtils.h"
JNIEXPORT jstring JNICALL Java_com_example_shenjiaqi_myapplication_JNIUtils_getString
        (JNIEnv *env, jclass jclass){
return env->NewStringUTF("Hello World From JNI!!!!!");
}

可以看到我們首先需要把原來生成的 JNIUtlis 對應的頭文件引入進來,下面的代碼基本都是從 com_example_shenjiaqi_myapplication_JNIUtils.h 中復制粘貼過來的一部分,然後稍加修改。修改的地方主要有 getString 的兩個參數和裏面的簡單實現,參數方面就是加了 env 和 jclass 兩個字段。函數裏面的實現呢,就是簡單的返回一個字符串 “Hello World From JNI!!!!!” , 大家現在就需要知道如果要在這裏返回一個字符串就必須要通過 env->NewStringUTF("xxxxxx"); 這行代碼

目錄結構如下圖:

技術分享圖片

NDK 配置

接下來我們在 build.gradle 中添加 ndk 配置:

技術分享圖片

運行項目了,發現報錯:

技術分享圖片

不要慌,說是讓我們采用 CMake 或者 ndk-build 方式來捷成。這時候我們打開 build 目錄,如下圖:

技術分享圖片

其中,有個文件叫做 Android.mk ,需要這個來為我們生成 .so 文件,操作步驟如下,先把目錄切換到 Android 視角下,不然會沒有 Link C++ Project with Gradle 這個選項的 :

技術分享圖片

在彈窗中選擇 ndk-build ,找到之前說的 Android.mk 這個文件。

技術分享圖片

這時候,我們再回到 JNIUtils.java ,發現沒有飄紅了。但是運行編譯後會出現錯誤提示:

技術分享圖片

說是沒有找到 getString()的實現方法。在 JNIUtils 中添加如下代碼,即可解決上面的問題。可以發現在 build 中已經生成相應的 .so 文件了。

public class JNIUtils {
    static {
        System.loadLibrary("JNIHello");
    }
    public static native String getString();
}

再次編譯,運行成功:

技術分享圖片 技術分享圖片

demo 地址: Android jni 編程實例

參考文獻:

1、將應用代碼由eclipse導入Android studio的方法NDK-Build和Cmake兩種方法(以android_serialport_api為例)

2、NDK開發(一)————如何在Android Studio下進行NDK開發

3、使用AndroidStudio編寫第一個JNI程序

Android studio 下 JNI 開發實例