擁抱 C/C++ : Android JNI 的使用

編譯工具 CMake

在Android Studio 2.2 之后铝穷,工具中增加了 CMake 的支持,于是我們有兩種選擇來編譯 c/c++ 代碼源哩。一個是 ndk-build + Android.mk + Application.mk 組合要出,另一個是 CMake + CMakeLists.txt 組合。這2個組合與 Android 代碼和 c/c++ 代碼無關(guān)牲阁,只是不同的構(gòu)建腳本和構(gòu)建命令。

環(huán)境配置

Android Studio 的 SDK Tools 安裝

  • CMake
  • LLDB
  • NDK

Hello World

先新建一個項目壤躲,記得要勾選 C++ support城菊,看一下 Android Studio 自動生成的使用了 JNI 的項目是什么樣子的。

目錄結(jié)構(gòu)

可以看到碉克,與普通 Android 項目不同的是凌唬,支持 C++ 的項目在 app 目錄下多了一個 .externalNativeBuild 編譯目錄與 CMakeLists.txt,main 目錄下多了 cpp 目錄漏麦。

CMakeLists 文件

關(guān)于 CMakeLists 文件的作用客税,我的理解是它指定了編譯 c++ 庫時所用到的一些配置况褪,先來看看項目里 CMakeList.txt 文件:

// 去掉注釋
cmake_minimum_required(VERSION 3.4.1)
add_library(native-lib SHARED src/main/cpp/native-lib.cpp )
find_library(log-lib log)
target_link_libraries(native-lib ${log-lib} )
  • cmake_minimum_required(VERSION 3.4.1)
    允許構(gòu)建的最低版本
  • add_library(name path)
    生成鏈接庫,SHARED 表示生成動態(tài)庫更耻, STATIC表示生成靜態(tài)庫测垛。并指定了參與編譯的文件路徑
  • find_library(log-lib log)
    添加在編譯本地文件時依賴的庫(log),并指定別名(log-lib)
  • target_link_libraries(lib1 lib2 ...)
    鏈接庫秧均,這里鏈接了我們自己的庫 native-lib 與 log 庫

默認的 so 庫輸出目錄為 app/build/intermediates/cmake/debug/obj/${abi} 下食侮,可以在 CMakeLists 中指定輸出目錄

#設置生成的so動態(tài)庫最后輸出的路徑
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

再來看 cpp 目錄下的 native-lib.cpp 文件:

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_yazhidev_cmakedemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

第一次看到 .cpp 格式的源文件肯定有點摸不著頭腦,Java 與 C++ 是如何通信的呢目胡?答案就是 JNI锯七。

JNI 規(guī)范

JNI (Java Native Interface,Java本地接口)是一種編程框架,使得 Java 虛擬機中的 Java 程序可以調(diào)用本地應用/或庫,也可以被其他程序調(diào)用。

從上面的 native-lib.cpp 文件我們可以一窺 JNI 中 C/C++ 的使用規(guī)范:

  • #include
    C 語音中使用 #include <> 直接到系統(tǒng)指定的目錄下查找文件誉己,我將其理解為類似 Java 中的導包眉尸。JNI 中首先頭部需要引入 <jni.h> ,由于使用到了字符串巨双,還導入了 <string>噪猾。

  • JNIEXPORT
    JNIEXPORT 和 JNICALL 都是 JNI 的關(guān)鍵字,表示此函數(shù)是要被 JNI 調(diào)用的炉峰。

  • jstring
    是 JNI 中作為中介使 JAVA 的 String 與 C/C++ 的 String 交互的數(shù)據(jù)類型,JNI的數(shù)據(jù)類型包含兩種脉执,分別是基本類型和引用類型疼阔。

  • jobject
    指代調(diào)用該方法的對象。如果 Java 中該 native 方法是靜態(tài)的半夷,則指代該類婆廊,即 XXX.class。

  • JNIEnv
    這個env可以看做是 JNI 接口本身的一個對象巫橄,在頭部引入的 jni.h 頭文件中存在著大量被封裝好的函數(shù)淘邻,這些函數(shù)也是 JNI 編程中經(jīng)常被使用到的,要想調(diào)用這些函數(shù)就需要使用JNIEnv這個對象湘换。除了上面使用到的傳遞返回值給 Java宾舅,還有獲取類的 class 類型:evn->GetObjectClass(),改變 Java 中對象的某個變量的值 evn-> SetIntField(...) 等方法彩倚。

  • 命名方式
    Java_com_yazhidev_cmakedemo_MainActivity_stringFromJNI()筹我,JNI 中對命名有規(guī)定,命名規(guī)范為:Java_包名_class_函數(shù)名帆离,包名中的 . 也要改為 _蔬蕊,對應的,在 Java 中引用 Native 函數(shù)也需要聲明關(guān)鍵字 native哥谷。

  • std::string岸夯、NewStringUTF
    這是 C++ 中字符串的一些寫法麻献,需要用時去翻一下語法,不多延伸猜扮。

以上只提到了項目里使用到的一些規(guī)范和注意點勉吻。下面通過寫個 demo 實際操作一下。

實戰(zhàn)

通過 JNI 對圖片做變色處理破镰。

jnigraphics 庫

這里要使用到 NDK 里提供的 jnigraphics 庫餐曼,該庫
提供了基于 C/C++ 的接口,可以訪問 Android 中的 Bitmap 的像素緩沖區(qū)(bitmap buffers)鲜漩。

頭文件中引入 android/bitmap.h源譬,其典型用法如下(摘至 android/bitmap.h 詳解):

a) 用 AndroidBitmap_getInfo() 函數(shù)從位圖句柄(從JNI得到)獲得信息(寬度、高度孕似、像素格式)
b) 用 AndroidBitmap_lockPixels() 對像素緩存上鎖踩娘,即獲得該緩存的指針。
c) 用C/C++ 對這個緩沖區(qū)進行讀寫
d) 用 AndroidBitmap_unlockPixels() 解鎖

我們利用該用法對 bitmap 做處理喉祭。

新建 Module

首先新建個 module养渴,并新建類 BitmapUtil:

static {
  // 不要忘記加載庫
  System.loadLibrary("bitmap-util");
}

public class BitmapUtil {
    public static native void processBitmap(Bitmap bitmap;
}

并在 main 目錄下新建 cpp 目錄,新建類 bitmap-util.cpp:

#include <jni.h>
#include <android/bitmap.h>

extern "C"

JNIEXPORT void JNICALL
Java_com_yazhidev_ndkdemo_BitmapUtil_processBitmap(JNIEnv *env, jobject /* this */, jobject bitmap) {
    //構(gòu)造 AndroidBitmapInfo
    AndroidBitmapInfo info = {0};
    //將 bitmp 的信息填充給 info
    AndroidBitmap_getInfo(env, bitmap, &info);
    int *buf=NULL;
    //對 bitmap 解碼并獲取解碼后的像素保存在內(nèi)存中的地址指針泛烙,賦值給 srcBuf
    AndroidBitmap_lockPixels(env, bitmap, (void **) &buf);
    //處理像素
    int w = info.width;
    int h = info.height;
    int32_t *srcPixs = (int32_t *) buf;
    int alpha = 0xFF << 24;
    int i, j;
    int color;
    int red;
    int green;
    int blue;
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            // get the color of per pixel
            color = srcPixs[w * i + j];
            red = ((color & 0x00FF0000) >> 16);
            green = ((color & 0x0000FF00) >> 8);
            blue = color & 0x000000FF;
            color = (red + green + blue) / 3;
            color = alpha | (color << 16) | (color << 8) | color;
            srcPixs[w * i + j] = color;
        }
    }
    //釋放鎖定理卑,顯示出被修改的像素數(shù)據(jù)
    AndroidBitmap_unlockPixels(env, bitmap);
}

module 根目錄下新建 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.4.1)
add_library(bitmap-util SHARED src/main/cpp/bitmap-util.cpp )
# 鏈接 jnigraphics 庫
target_link_libraries(native-lib jnigraphics)

在 module 的 build.gradle 中引用 CMakeLists 文件:

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

Java 中調(diào)用:

// Kotlin image -> ImageView(android:id="@+id/image")
val drawable = resources.getDrawable(R.mipmap.google) as BitmapDrawable
val bitmap = drawable.bitmap
BitmapUtil.processBitmap(bitmap)
image.setImageBitmap(bitmap)

遇到的問題

java.lang.UnsatisfiedLinkError: No implementation found

extern "C"

擴展

項目中導入 so 庫

在使用 JNI 時有時可能只有編譯好的 so 庫,那么如何在項目中使用 so 庫呢蔽氨?

右鍵 app 目錄藐唠,選擇 new - Folder -JNI Folder,新建一個 JNI 目錄用于存放 so 文件鹉究。

so 庫(CPU)的兼容

使用 CMake 編譯 so 庫時宇立,可通過配置 gradle 文件指定編譯的 so 庫架構(gòu)

android {
  defaultConfig {
    externalNativeBuild {
      cmake {
        cppFlags ""
        // 生成.so庫的目標平臺
        abiFilters "armeabi-v7a", "armeabi", "x86"
      }
    }
  }
}

對于CPU來說,不同的架構(gòu)并不意味著一定互不兼容自赔,根據(jù)目前Android共支持七種不同類型的CPU架構(gòu)妈嘹,其兼容特點可總結(jié)如下:

armeabi設備只兼容armeabi;
armeabi-v7a設備兼容armeabi-v7a绍妨、armeabi润脸;
arm64-v8a設備兼容arm64-v8a、armeabi-v7a他去、armeabi津函;
X86設備兼容X86、armeabi孤页;
X86_64設備兼容X86_64尔苦、X86、armeabi;
mips64設備兼容mips64允坚、mips魂那;
mips只兼容mips;

根據(jù)以上的兼容總結(jié)稠项,我們還可以得到一些規(guī)律:
armeabi的SO文件基本上可以說是萬金油涯雅,它能運行在除了mips和mips64的設備上,但在非armeabi設備上運行性能還是有所損耗展运;
64位的CPU架構(gòu)總能向下兼容其對應的32位指令集活逆,如:x86_64兼容X86露该,arm64-v8a兼容armeabi-v7a舀寓,mips64兼容mips。

更多 so 文件的信息可參考:Android SO文件的兼容和適配

參考

AndroidStudio項目CMakeLists解析
JNI技術(shù)規(guī)范
Android NDK之旅——圖片高斯模糊
Jni接口-深入研究參數(shù)的傳遞(一)
android/bitmap.h 詳解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柑潦,一起剝皮案震驚了整個濱河市埂软,隨后出現(xiàn)的幾起案子锈遥,更是在濱河造成了極大的恐慌,老刑警劉巖勘畔,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件所灸,死亡現(xiàn)場離奇詭異,居然都是意外死亡炫七,警方通過查閱死者的電腦和手機爬立,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來万哪,“玉大人侠驯,你說我怎么就攤上這事∪榔裕” “怎么了陵霉?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵琅轧,是天一觀的道長伍绳。 經(jīng)常有香客問我,道長乍桂,這世上最難降的妖魔是什么冲杀? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮睹酌,結(jié)果婚禮上权谁,老公的妹妹穿的比我還像新娘。我一直安慰自己憋沿,他們只是感情好旺芽,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般采章。 火紅的嫁衣襯著肌膚如雪运嗜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天悯舟,我揣著相機與錄音担租,去河邊找鬼。 笑死抵怎,一個胖子當著我的面吹牛奋救,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播反惕,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼尝艘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了承璃?” 一聲冷哼從身側(cè)響起利耍,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盔粹,沒想到半個月后隘梨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡舷嗡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年轴猎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片进萄。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡捻脖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出中鼠,到底是詐尸還是另有隱情可婶,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布援雇,位于F島的核電站矛渴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惫搏。R本人自食惡果不足惜具温,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筐赔。 院中可真熱鬧铣猩,春花似錦、人聲如沸茴丰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峦椰,卻和暖如春失仁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背们何。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工萄焦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冤竹。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓拂封,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鹦蠕。 傳聞我的和親對象是個殘疾皇子冒签,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容