JNI學習筆記之ndk-build手動編譯並整合流程
參考
一天掌握Android JNI本地程式設計 快速入門
Android開發實踐:常用NDK命令列引數
Secrets of Android.mk
JNI
JNI是啥?
JNI(Java Native Interface):Java本地開發介面,JNI是一個協議,用來溝通Java程式碼和外部的原生程式碼(c/c++),外部的c/c++程式碼也可以呼叫Java程式碼
為什麼使用JNI
- 效率上C/C++是本地語言,比Java更高效
- 程式碼移植,如果之前用C語言開發過模組,可以複用已經存在的C程式碼
- Java反編譯比C語言更容易,一般加密演算法都是用C語言編寫,不容易被反編譯
Java基本資料型別與C語言基本資料型別的對應
Java引用型別對應
堆記憶體和棧記憶體的概念
棧記憶體
系統自動分配和釋放,儲存全域性,靜態,區域性變數,在棧上分配記憶體叫今天記憶體,大小一般是固定的。
堆記憶體
程式設計師手動分配(mallc/new)和釋放(free/java不用手動釋放,有GC回收),在堆上分配記憶體叫動態分配,一般硬體記憶體有多大記憶體就有多大。
交叉編譯
交叉編譯的概念
交叉編譯記載一個平臺,編譯出另一個平臺能夠執行的二進位制程式碼
主流平臺:Windows,Mac os,Linux
主流處理器:X86,arm,mips
交叉編譯的原理
即在一個平臺上,模擬其它平臺的特性
編譯的流程:原始碼》編譯》連結》可執行程式
交叉編譯的工具鏈
多個工具的集合,一個工具使用完後接著呼叫下一個工具
常見的交叉編譯工具
NDK(Native Development Kit):開發JNI必備工具,就是模擬其它平臺特性類編譯程式碼的工具
CDP(C/C++ Development Tools):是Eclipse開發C語言的一個外掛,高亮顯示C語言的語法
Cygwin:一個Windows平臺的Unix模擬器
NDK的目錄結構
這裡我下的ndk版本是15.2.4203891 比較新了,配置NDK環境變數大家應該都會,這裡我就不提了
ndk-build方式手動編譯出so庫檔案
一個簡單的例子
1.編寫java程式碼
這個直接在工程目錄下正常編輯你的程式碼,比如我的這個JNIUtils是在com.newtrekwang.ndkpractice包下的,這個類宣告瞭一個方法,功能就是獲取從C層傳來的字串。方法的具體實現當然是在C層實現啦。所以這個就跟java的介面定義差不多。
package com.newtrekwang.ndkpractice;
public class JNIUtils {
public static native String getStringFromC();
}
2.編寫c層程式碼
這裡如果想快速寫一個與之關聯的標頭檔案的話,(java7)可以使用javah命令生成,不過命令要在你的類的所在包的根路徑執行
比如我這個:
可以看下標頭檔案的程式碼:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_newtrekwang_ndkpractice_JNIUtils */
#ifndef _Included_com_newtrekwang_ndkpractice_JNIUtils
#define _Included_com_newtrekwang_ndkpractice_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_newtrekwang_ndkpractice_JNIUtils
* Method: getStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_newtrekwang_ndkpractice_JNIUtils_getStringFromC
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
可以看到裡面已經幫我們寫好了一個與Java類getStringFromC對應的Native函式Java_com_newtrekwang_ndkpractice_JNIUtils_getStringFromC,命名格式就是Java加完整類名加方法名
然後我把這個標頭檔案剪下到另一個新建的資料夾prac1裡,這個資料夾專門存放c/c++真個編譯過程的產物,比如最後我要得到so檔案,就從這裡面拿
然後我們新建一個hello.c實現標頭檔案裡面的函式
#include<com_newtrekwang_ndkpractice_JNIUtils.h>
#include<stdio.h>
#include<stdlib.h>
JNIEXPORT jstring JNICALL Java_com_newtrekwang_ndkpractice_JNIUtils_getStringFromC
(JNIEnv* env, jclass obj){
char* str="Hello from C!";
jstring result=(*env)->NewStringUTF(env,str);
return result;
}
(1)JNIEXPORT :在Jni程式設計中所有本地語言實現Jni介面的方法前面都有一個"JNIEXPORT",這個可以看做是Jni的一個標誌,至今為止沒發現它有什麼特殊的用處。
(2)void :這個學過程式設計的人都知道,當然是方法的返回值了。
(3)JNICALL :這個可以理解為Jni 和Call兩個部分,和起來的意思就是 Jni呼叫XXX(後面的XXX就是JAVA的方法名)。
(4)Java_com_test01_Test_firstTest:這個就是被上一步中被呼叫的部分,也就是Java中的native 方法名,這裡起名字的方式比較特別,是:包名+類名+方法名。
(5)JNIEnv * env:這個env可以看做是Jni介面本身的一個物件,在上一篇中提到的jni.h標頭檔案中存在著大量被封裝好的函式,這些函式也是Jni程式設計中經常被使用到的,要想呼叫這些函式就需要使用JNIEnv這個物件。例如:env->GetObjectClass()。(詳情請檢視jni.h)
3.編寫Android.mk
Android.mk 裡面就是些設定編譯配置的指令碼
例如我的這個:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE :=hello
LOCAL_SRC_FILES := hello.c com_newtrekwang_ndkpractice_JNIUtils.h
include $(BUILD_SHARED_LIBRARY)
常見Android.mk語句解釋
-
LOCAL_PATH := $(call my-dir)
一個Android.mk檔案必須以LOCAL_PATH變數的定義開始,它用於在開發樹中查詢原始檔。在這個例子中,構建系統提供的巨集函式'my-dir'被用來返回 當前目錄的路徑(即包含Android.mk檔案本身的目錄)。 -
include $(CLEAR_VARS)
CLEAR_VARS變數由構建系統提供,並指向一個特殊的GNU Makefile,它將清除許多LOCAL_XXX變數 (例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等),但除了LOCAL_PATH,這個是需要的,因為所有的構建控制檔案在單個GNU Make執行上下文中解析,其中所有變數都是全域性的。 -
LOCAL_MODULE :=hello
定義LOCAL_MODULE變數來標識您在Android.mk中描述的每個模組,構建系統為自動為模組新增字首和字尾,例如我這個是hello,他最終會生成libhello.so -
LOCAL_SRC_FILES := hello.c com_newtrekwang_ndkpractice_JNIUtils.h
LOCAL_SRC_FILES變數必須包含C和/或C ++原始檔的列表,這些檔案將被構建並組裝到一個模組中。這裡可以只列出原始檔,不需要標頭檔案,標頭檔案構建系統會自動查詢
更多Android.mk功能請見Secrets of Android.mk
然後我的prac1檔案現在有這個三個檔案了
然後在此目錄下執行ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk
如圖:
然後再看我們的資料夾就是這樣了:
常見ndk-build命令列引數
- NDK_LOG=1:配置log級別,列印ndk編譯時的詳細輸出資訊
- NDK_PROJECT_PATH=.:制定NDK編譯的程式碼路徑為當前目錄,如果不配置,則必須把工程程式碼放到Android工程的jni目錄下
- APP_BUILD_SCRIPT=./Android.mk:指定NDK編譯使用的Android.mk檔案
- NDK_APP_APPLICATION_MK=./Application.mk:指定NDK編譯使用的Application.mk檔案
- CLEAN:清除所有編譯出來的臨時檔案和目標檔案
- -B:強制重新編譯已經編譯完成的的程式碼
- NDK_DEBUG=1:執行 debug build
- NDK_DEBUG=0:執行release build
- NDK_OUT=./mydir:指定編譯生成的檔案的存放位置
- -C /opt/myTest/:到指定目錄編譯native程式碼
4.在Android專案中整合so檔案
首先把那些so檔案拷到Android工程的libs目錄下
gradle配置庫檔案目錄
在android塊下設定sourceSets塊即可,比如我這裡設定的是libs資料夾,說明我的so庫檔案在libs資料夾裡。
sourceSets {
main {
jniLibs.srcDirs=["libs"] //指定庫檔案的目錄,java程式碼編譯時連結用
}
}
然後gradle同步一下,即可在Android模式目錄下看到jniLibs檔案包,證明gradle已經識別了so庫檔案
完善java程式碼
就是在原來的基礎上加了個靜態塊,裡面的System.loadLibrary("hello")功能就是載入hello這個庫檔案,不然下面的native方法不能正常執行
package com.newtrekwang.ndkpractice;
public class JNIUtils {
static {
System.loadLibrary("hello");
}
public static native String getStringFromC();
}
然後就可以愉快的使用調方法了
比如我這裡這個TextView顯示的字串就是就是從JNIUtils類的native方法調出來的。
相關文章
- JNI學習筆記之AS+ndk+gradle自動編譯出so並整合流程筆記Gradle編譯
- Java JNI 學習筆記Java筆記
- 【編譯openjdk學習筆記】編譯JDK筆記
- redis 學習筆記(1)-編譯、啟動、停止Redis筆記編譯
- Google Protocol buffer 學習筆記.下篇-動態編譯GoProtocol筆記編譯
- (轉)redis 學習筆記(1)-編譯、啟動、停止Redis筆記編譯
- ZYNQ學習筆記(一): uboot 編譯筆記boot編譯
- 彙編學習筆記之轉移指令筆記
- Kaldi學習手記(一):Kaldi的編譯安裝編譯
- 編譯、連結學習筆記(一)簡述編譯連結過程編譯筆記
- 彙編學習筆記筆記
- 深度學習讀書筆記之AE(自動編碼AutoEncoder)深度學習筆記
- webpack 學習筆記:實戰之 babel 編碼Web筆記Babel
- 【筆記】動手學深度學習-預備知識筆記深度學習
- CAS學習筆記五:SpringBoot自動/手動配置方式整合CAS單點登出筆記Spring Boot
- Solidity語言學習筆記————2、使用編譯器Solid筆記編譯
- GCC/G++學習筆記 - 1 - 執行預編譯GC筆記編譯
- 機器學習整合學習—Apple的學習筆記機器學習APP筆記
- SAP學習筆記--整合與核算筆記
- 越獄手記:手動編譯安裝 Electra編譯
- java反射之動態代理學習筆記Java反射筆記
- SSM學習筆記3——整合 SpringMVC、整合SSMSSM筆記SpringMVC
- go 學習筆記之走進Goland編輯器筆記GoLand
- 【動手學深度學習】第十二章筆記:非同步計算、資料並行深度學習筆記非同步並行
- jni編譯出錯!急!!編譯
- Activiti 學習(三)—— Activiti 流程啟動並完成
- ndk-build 編譯多個CPU架構的動態連結庫UI編譯架構
- 學習筆記分享之彙編---2.彙編指令/語法筆記
- NDK學習筆記-NDK開發流程筆記
- Activiti 學習筆記五:流程變數筆記變數
- SpingBoot_學習筆記整合boot筆記
- JNI學習
- 【學習筆記】並查集應用筆記並查集
- 彙編基礎學習筆記筆記
- 《圖解 Google V8》編譯流水篇——學習筆記(二)圖解Go編譯筆記
- Node.js 設計模式 學習筆記 之 流程式設計Node.js設計模式筆記程式設計
- Linux驅動開發筆記(三):基於ubuntu的驅動、makefile編寫以及編譯載入流程Linux筆記Ubuntu編譯
- Activiti學習筆記三:管理流程定義筆記