JNI是Java和C/C++創建鏈接的橋樑,開發者能夠將關鍵的加密算法經過用C/C++實現以提升被反編譯的難度,保護APK的數據安全。又或者經過C/C++獲取一些手機數據,以此來繞過相似Xposed這類做弊工具,提升數據的準確性。
源碼地址:https://github.com/gnaix92/as-ndkjava
傳送門: NDK開發 - JNI開發流程android
在上一篇搭建的環境下,就能夠進行NDK的開發。NDK開發主要分爲一下幾個步驟:c++
java層入口代碼編寫git
java代碼編譯github
C/C++頭文件生成算法
C/C++代碼編寫shell
運行編譯SO安全
新建一個NativeMethod
的Class,java層代碼主要負責兩件事:架構
Load操做很簡單,其中包名爲build.gradle
中定義的ndk名,代碼以下:app
android.ndk { moduleName = "native" //設置庫(so)文件名稱 // CFlags.add("-DCUSTOM_DEFINE") ldLibs.addAll(["log", "android", "EGL", "GLESv1_CM"]) // ldFlags.add("-L/custom/lib/path") stl = "stlport_static" }
static{ System.loadLibrary("native"); }
JNI的接口名以native
字符修飾,而且不須要實現。
package com.example.gnaix.ndk; /** * 名稱: NativeMethod * 描述: * * @author xiangqing.xue * @date 16/3/10 */ public class NativeMethod { static{ System.loadLibrary("native"); } /** * 基礎類型 * @param i * @return */ public static native int getInt(int i); /** * string * @param str * @return */ public static native String getString(String str); /** * array * @param data * @return */ public static native byte[] getByteArray(Byte[] data); /** * 調用java對象 * @param name * @param age */ public static native void invokeJobject(String name, int age); /** * 調用java靜態方法 */ public static native void invokeStaticFieldAndMethod(); /** * 獲取結構體 * @return */ public static native Person[] getPersons(); }
到這裏java層的活就幹完了。
在生成C/C++的頭文件前須要把上面寫的class編譯成.class
文件。Android Studio中編譯很簡單點一下右上角的<img width=40px height=35px src="http://gnaix92.github.io/blog... style="display:inline-block"/>按鈕就能夠了。生成的class文件在app/build/intermediates/calsses/all/debug/xx.xx.xx/xx.class
。
JNI開發對C/C++的頭文件有命名的格式要求,因此咱們可使用jdk
提供的javah
命令生成規定的C/C++頭文件。爲了方即可以先切換到app/src/main
目錄下。
cd app/src/main
再執行javah命令。(根據實際替換須要生成的class文件)
javah -d jni -classpath ../../build/intermediates/classes/all/debug com.example.gnaix.ndk.NativeMethod
參數說明:
classpath:類搜索路徑
d:將生成的頭文件放到當前的 jni 目錄下
o: 指定生成的頭文件名稱,默認以類全路徑名生成(包名+類名.h)
執行完會發如今main目錄下生成了一個jni目錄,裏面有個com_example_gnaix_ndk_NativeMethod.h
的頭文件。裏面有生成好的頭文件。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_gnaix_ndk_NativeMethod */ #ifndef _Included_com_example_gnaix_ndk_NativeMethod #define _Included_com_example_gnaix_ndk_NativeMethod #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_gnaix_ndk_NativeMethod * Method: getInt * Signature: (I)I */ JNIEXPORT jint JNICALL Java_com_example_gnaix_ndk_NativeMethod_getInt (JNIEnv *, jclass, jint); /* * Class: com_example_gnaix_ndk_NativeMethod * Method: getString * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_gnaix_ndk_NativeMethod_getString (JNIEnv *, jclass, jstring); /* * Class: com_example_gnaix_ndk_NativeMethod * Method: getByteArray * Signature: ([Ljava/lang/Byte;)[B */ JNIEXPORT jbyteArray JNICALL Java_com_example_gnaix_ndk_NativeMethod_getByteArray (JNIEnv *, jclass, jobjectArray); /* * Class: com_example_gnaix_ndk_NativeMethod * Method: invokeJobject * Signature: (Ljava/lang/String;I)V */ JNIEXPORT void JNICALL Java_com_example_gnaix_ndk_NativeMethod_invokeJobject (JNIEnv *, jclass, jstring, jint); /* * Class: com_example_gnaix_ndk_NativeMethod * Method: invokeStaticFieldAndMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_com_example_gnaix_ndk_NativeMethod_invokeStaticFieldAndMethod (JNIEnv *, jclass); /* * Class: com_example_gnaix_ndk_NativeMethod * Method: getPersons * Signature: ()[Lcom/example/gnaix/ndk/Person; */ JNIEXPORT jobjectArray JNICALL Java_com_example_gnaix_ndk_NativeMethod_getPersons (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif
生成頭文件後就能夠寫具體的C++的實現代碼,這裏只寫了一小段實現代碼,具體的調用開發細節下一篇再說。 實現了getInt
方法,獲取了android的serialno,並用log打印出來。
// // Created by 薛祥清 on 16/3/10. // #include "com_example_gnaix_ndk_NativeMethod.h" #include <android/log.h> #include <sys/system_properties.h> using namespace std; #define ENABLE_DEBUG 1 #if ENABLE_DEBUG #define TAG "NDK_NATIVE" #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG,TAG, fmt, ##args) #define DEBUG_PRINT(format, args...) \ LOGD(format, ##args) #else #define DEBUG_PRINT(format, args...) #endif JNIEXPORT jint JNICALL Java_com_example_gnaix_ndk_NativeMethod_getInt (JNIEnv *env, jclass object, jint num) { int len; char buf[1024]; __system_property_get("ro.serialno", buf); LOGD("name : %s", buf); return num; }
通過上面的步驟,已經能夠編譯了,在NDK開發中除了本身apk須要,更多時候是做爲第三方包提供給客戶使用。Android Studio也爲咱們打包好了,能夠在module目錄下找到。
PS:
這裏須要注意debug模式編譯會產生gdb.steup gdbserver這兩個是gdb調試工具,在正式發佈必需要用release模式編譯。
舊版本android-ndk-r9d
只能編譯armeabi
armeabi-v7a
mips
x86
四種cpu架構的,如今大多數手機已是64位架構了,舊版本已經不能支持全部手機了。因此在android-ndk-r10e
中增長了arm64-v8a
mips64
x86_64
三種架構。
目前最新的NDK版本爲android-ndk-r11b