1. 程式人生 > >JNI和NDK開發(1)_建立JNI程式

JNI和NDK開發(1)_建立JNI程式

開始學習JNI開發技術,在網上看了很多文章,但講解的都是基礎或者過時的技術,沒有系統的關於JNI和NDK的學習教程,現在我寫《JNI和NDK開發》系列文章,主要是記錄自己從零開始學習遇到的一些問題和知識點,希望對大家也有些幫助。對於文章,本人也是邊學邊寫, 所以可能會更新的慢一點,大家有問題可以留言。

本系列文章主要解決的問題是:

  • JNI和NDK開發常用API的使用
  • JNI和NDK開發常見問題的解決
  • JNI和NDK應用場景實踐

JNI和NDK的概念

JNI全稱Java Native Interface,意為java本地介面,它提供了若干的API實現了Java和其他語言的通訊(主要是C&C++)。從Java1.1開始,JNI標準成為java平臺的一部分,它允許Java程式碼和其他語言寫的程式碼進行互動。

NDK全稱Native Development Kit,是 Android的一個工具開發包 ,在Android開發中使用NDK編寫JNI程式就能實現對C和C++程式碼的互相呼叫。

使用JNI和NDK的好處

  • 效率
    大家都知道,Android的底層是使用C&C++實現的,所以使用C&C++編寫的程式,效率和效能上比java編寫的程式高。
  • 安全
    應用層編寫的java程式碼很容易被反編譯,而通過JNi編譯出的so庫是一個二進位制檔案,對於反編譯難度要大一點,如果在程式中再加上一些反除錯程式碼和簽名驗證,又更提高了反編譯的難度,所以一些核心加密的演算法會使用JNI來編寫。
  • 呼叫Linux函式
    眾所周知,Android的核心是Linux系統,通過java是無法呼叫Linux系統函式的,而使用JNI編寫C&C++程式碼卻可以呼叫Linux的系統函式,例如通過呼叫fork函式建立子程序來實現關鍵程序的保活,使用mmap對映檔案來將日誌寫入到記憶體中等。
  • 使用C&C++編譯的開源庫
    使用C&C++編寫的著名開源庫有很多(例如音視訊處理庫FFmpeg,圖形影象處理庫OpenCV等),而Android應用層想要使用使用這些庫就必須通過JNI程式呼叫。

JNI和NDK應用場景

  • 音視訊處理
    像優酷、Bilibili、愛奇藝、抖音等這些視訊播放和直播軟體都是用的JNI程式呼叫FFmpeg
    開源庫。
  • 圖形影象處理
    例如人臉識別、影象識別技術。
  • 通訊協議加密SDK
    封裝一個Http請求的通訊加密的SDK,加密演算法是在JNI層實現的。
  • Android軟體呼叫硬體裝置
    例如手機軟體

建立第一個JNI程式

網上很多文章講的都是Android Studio2.2之前通過Android.mk來建立編譯JNI程式,因為時間有限,我也沒去學習和研究,所以直接使用Android Studio2.2之後通過CMake來編譯JNI程式,我當前的Android環境配置是:Android studio版本是3.0.1,gradle外掛版本3.0.1,gradle版本是4.1,NDK版本是16。廢話不多說,下面開始建立第一個JNI程式。

1、 NDK環境配置
在Android Studio中檢視Android SDK的SDK Tools配置,如下圖所示,保證這三個SDK要下載。
在這裡插入圖片描述

2、Include C++ support
開啟Android Studio建立一個新工程,如下圖所示,紅框標註的意思是增加JNI的支援,這裡如果選中就會建立一個JNI程式,點選執行下一步。
在這裡插入圖片描述

配置C++編譯環境

  • C++ Standard
    指定編譯庫的環境,其中Toolchain Default使用的是預設的CMake環境;C++ 11也就是C++環境。
  • Exceptions Support
    如果選中複選框,則表示當前專案支援C++異常處理,如果支援,在專案Module級別的build.gradle檔案中會增加一個標識 -fexceptions到cppFlags屬性中,並且在so庫構建時,gradle會把該屬性值傳遞給CMake進行構建。
  • **Runtime Type Information Support**
    選中複選框,專案支援RTTI,屬性cppFlags增加標識-frtti。

在這裡插入圖片描述

建立完成後執行程式, 就會在手機上顯示Hello from C++,從原始碼中可以看到這段文字是從native-lib.cpp中返回的。效果看到了那就開始分析JNI的實現邏輯。

3、載入so庫
在MainActivity中可以看到這段程式碼

static {
  System.loadLibrary("native-lib");
}

這段程式碼的意思是載入名為native-lib的so檔案,那native-lib這個名字是在哪定義的呢,其實是在CMakeLists.txt中。
4、CMakeLists.txt配置
開啟app目錄下的CMakeLists.txt檔案,可以看到很大一堆註釋,讓人看著就不簡單,所以我把註釋去掉,只留下有用的部分,無非就四行。

// 要求使用的cmake最小版本
cmake_minimum_required(VERSION 3.4.1)
// 新增共享庫:命名為native-lib;設定為SHARED共享;檔案路徑是src/main/cpp/native-lib.cpp
add_library( native-lib SHARED src/main/cpp/native-lib.cpp )
// 找到Android自帶的日誌庫log,並賦值給log-lib變數
find_library( log-lib log )
// 將native-lib庫和log-lib庫
target_link_libraries( native-lib ${log-lib} )

簡單說一下CMake的這幾條指令的意思:

  • add_library
    將指定的檔案生成與庫連結的目標檔案。
    -find_library
    查詢庫,並將庫賦值給變數。
  • target_link_libraries
    將目標檔案與生成的庫檔案連結。

5、build.gradle中配置
在Android Studio中CMakeLists.txt檔案是怎麼建立的呢?看一下app下的build.gradle檔案,在android節點下配置

externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
}

這段程式碼的作用就是配置CMakeLists.txt檔案路徑,Android Studio會根據這個路徑找到並建立。
除了CMakeLists.txt檔案路徑的配置,還有一段externalNativeBuild的配置,在defaultConfig節點下:

externalNativeBuild {
    cmake {
        cppFlags ""
    }
}

這段意思是配置CMake編譯環境,在app目錄下可以看到.externalNativeBuild編譯檔案。
6、C&C++檔案
app目錄下,有一個cpp目錄,裡面放的就是我們的JNI源程式,開啟native-lib.cpp檔案,就可以看到JNI程式碼的語法結構。

#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_rzr_jni_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

7、原生介面定義和呼叫
開啟MainActivity找到程式碼如下:

/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();

這段程式碼定義了原生與JNI的介面函式,我們可以通過呼叫這個介面來獲取JNI返回的結果。

// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());

到此,我們就建立和分析了一個帶JNI的Android工程。