咳咳,作爲一名android愛好者(其實是爲了錢錢),之前一直在使用Android Sdk進行開發,同時也一直知道有個ndk的開發方式,知道全名是native development kit,基原生開發工具集,模糊的知道應該是和c/c++開發有關係的,然後就沒有深入一點的瞭解了。
目前階段想系統性的收一下自己的android技能,整理成一個比較系統的知識體系,於是乎ndk就成了一個繞不過去的技術,這篇文章就簡單的說說android開發中jni/ndk技術的使用。
1、關於JNI技術的說明
JNI全稱Java Native Interface,其實也不是啥新鮮的東西,簡單的說也就是java中調用c/c++類庫的技術,也可以理解爲”java + c”的一門技術,至於爲什麼要使用”java + c”的技術,難道c能實現的功能還有java做不到的麼,其實不也全不是這樣,在實際項目中使用JNI的主要目的有
a、增強安全性,防止被反編譯後被不法分子分析應用的邏輯(增強安全性是相對的);
b、java不能直接與硬件交互的時候,需要c/c++上場幹活;
c、有些功能可以通過c/c++打包後,供多種平臺和語言調用,也就提高了程序的可移植性;
而JNI的開發流程大致分爲六步
第一步: 編寫聲明瞭 native 方法的 Java 類
第二步: 將 Java 源代碼編譯成 class 字節碼文件
第三步: 用 javah -jni 命令生成.h頭文件(javah 是 jdk 自帶的一個命令,-jni 參數表示將 class 中用native 聲明的函數生成 JNI 規則的函數)
第四步: 用本地代碼實現.h頭文件中的函數
第五步: 將本地代碼編譯成動態庫(Windows:\*.dll,linux/unix:\*.so,mac os x:\*.jnilib)
第六步: 拷貝動態庫至 java.library.path 本地庫搜索目錄下,並運行 Java 程序
關於這六步的具體實現,JNI 開發流程這篇文章中有很好的講解,這裏就不多說了。
2、關於NDK技術的說明
第一小節講了JNI,那麼這個NDK又是什麼東西呢,其實這個NDK不是什麼新鮮技術,它只是一個Native開發的工具集,讓開發者更方便的進行JNI開發而已。
JNI的不方便之處可以用下面的話來表述
Android SDK 文檔裏,找不到任何 JNI 方面的幫助。即使第三方應用開發者使用 JNI 完成了自己的 C 動態鏈接庫(so)開發,但是 so 如何和應用程序一起打包成 apk 併發布?這裏面也存在技術障礙。比如程序更加複雜,兼容性難以保障,無法訪問Framework API,Debug 難度更大等。
而NDK的出現就是爲了解決上面描述的一些問題的,NDK作爲工具集,有以下功能
NDK 集成了交叉編譯器,並提供了相應的 mk 文件隔離 CPU、平臺、ABI 等差異,開發人員只需要簡單修改 mk 文件(指出“哪些文件需要編譯”、“編譯特性要求”等),就可以創建出 so。
NDK 可以自動地將 so 和 Java 應用一起打包,極大地減輕了開發人員的打包工作。
NDK 提供了一份穩定、功能有限的 API 頭文件聲明
根據第一小節我們知道,JNI開發有六個步驟,而當前的NDK版本提供便利表現在第五和第六個步驟,第五步的便利表現在ndk提供了ndk-build命令可以直接將c/c++代碼編譯爲so包,而第六步則體現在可通過gradle中的ndk配置來直接加載對應的so包(這裏不懂彆着急,後面會講ndk的使用,到時便可以體會)。
3、關於ABI的說明
可能有童鞋到這裏不明白了,好好的講JNI/NDK,怎麼又講到ABI了,這是個什麼玩意。這裏就來說說爲什麼要說這個ABI,ABI的全稱是Application Binary Interface,知道全稱其實也不知道是幹嘛的吧。
ABI(應用程序二進制接口)定義了二進制文件(尤其是.so文件)如何運行在相應的系統平臺上,從使用的指令集,內存對齊到可用的系統函數庫。而Andriod作爲一種操作系統,它得去適應不同的CPU架構,早期的Android系統幾乎只支持ARMv5的CPU架構,現在Android已經支持7中CPU架構了,分別是ARMv5,ARMv7 (從2010年起),x86 (從2011年起),MIPS (從2012年起),ARMv8,MIPS64和x86_64 (從2014年起),每一種CPU架構都關聯着一個相應的ABI。學會彙編的同學都知道CPU不一樣,對應的指令集就不一樣,這裏的ABI就可以理解爲指令集。那麼不同的CPU架構自然就需要不同的ABI,所以與7種CPU架構相對應的就有其中ABI,分別是armeabi,armeabi-v7a,x86,mips,arm64- v8a,mips64,x86_64。
還是有同學說了,你說這麼多還是沒有說清楚爲啥講JNI/SDK要講這個ABI啊。童鞋們,我們編寫的so包最終是要打包到android手機上的也就是會執行在android cpu上,那麼必然需要不同的指令集,所以我們寫的c/c++代碼就得根據不同的ABI進行編譯,最後編譯出針對不同cpu的so包來,也就是我們寫的一份c/c++代碼,最多可以編譯出7個so包,然後一起打包到apk中,apk會根據當前的cpu架構去調用對應的so包。
我想到這裏算是說清楚了吧。
4、NDK的使用
我爲了嚐鮮kotlin已經把開發工具升級到android studio 3.0版本了,但是感覺這個ndk開發和ide版本沒有太大關係。
實際上使用NDK開發JNI還是蠻簡單的,但是我還是走了不少彎路,剛開始便是依據NDK-JNI實戰教程(一) 在Android Studio運行第一個NDK程序這篇文章進行開發,但是怎麼都不成功。
後面換了個思路,改依據Android studio運行JNI程序以及生成.so文件(Windows下)這篇文章後成功。
這裏還是來說說吧。
4.1 基本配置
首先得配置好ndk,這裏的配置包括兩方面的,
一是在android stuido中配置好
project structure中的配置
local.properties文件中的配置
二是系統環境變量中的配置
配置NDK_HOME
在系統環境變量中添加NDK_HOME 然後值爲你的ndk路徑
配置Path
path環境變量中添加%NDK_HOME%\
配置好後,cmd中運行ndk-build,出來正常提示就表示配置成功了。
4.2 生成.h頭文件
在android 項目中添加NdkUtil.java文件
package com.yjing.ndk.utils;
public class NdkUtil {
public native String helloFromJni();
public native void callBackJavafromC();
}
然後編譯項目,到build\intermediates\classes\debug這個目錄下查看,是否成功編譯出NdkUitl.class文件,有的話表示編譯成功,然後繼續
cmd中cd到build\intermediates\classes\debug這個目錄下,然後執行
javah -jni com.yjing.ndk.utils.NdkUtil
或者
javah -classpath . -jni com.yjing.ndk.utils.NdkUtil
總之目的就是在debug目錄下生成一個針對NdkUtil.class文件的.h文件就是了。
4.3 jni文件夾中代碼完善
在main文件目錄下創建jni目錄
將4.2中的頭文件,拷貝到jni目錄中,同時在jni目錄中創建hello.c文件,對該文件做實現
#include "com_yjing_ndk_utils_NdkUtil.h"
/*
* Class: com_yjing_ndk_utils_NdkUtil
* Method: helloFromJni
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_yjing_ndk_utils_NdkUtil_helloFromJni
(JNIEnv *env, jobject obj){
char* c ="hello from c";
return (*env)->NewStringUTF(env, c);
}
/*
* Class: com_yjing_ndk_utils_NdkUtil
* Method: callBackJavafromC
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_yjing_ndk_utils_NdkUtil_callBackJavafromC
(JNIEnv *env, jobject obj){
// 1.通過反射找到類
jclass clazz = (*env) -> FindClass(env, "com/wangjin/hellondkdemo/MainActivity");
// 2. 找到方法ID
jmethodID methodId = (*env) -> GetMethodID(env, clazz, "logout", "()V");
// 3.調用方法,obj就是調用的類實例,所以不用再次創建了
(*env)->CallVoidMethod(env, obj, methodId);
}
然後在jni目錄中創建Android.mk以及Application.mk兩個文件,內容分別爲
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_SRC_FILES := hello.c
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := all
這裏的Android.mk類似linux中的makefile文件用來指定c/c++文件之間的依賴關係,而Application.mk文件用來指定ABI類型,這裏指定我們的so包要適配所有的CPU架構。
Android.mk中的LOCAL_MODULE對應的是build.gradle文件中的ndk配置
到這裏jni目錄下的東西就算是講完了
4.4 build.gradle文件中的配置
實際上4.3小節都提到了build.gradle文件的配置,但是不全面,這裏單獨用一小節來說明
build.gradle中首先要如4.3小節中的那樣配置ndk模塊,其次還需要指定so包編譯後的存放路徑
sourceSets{
main{
jni.srcDirs = ['libs']
}
}
這裏指定so報的路徑爲main目錄下的libs目錄
4.5 使用ndk-build命令
我們配置好後,便到了生成so包的時候了,生成so包很簡單,只需要藉助ndk-build工具便可。
cmd 中cd到項目的main目錄路徑,然後執行ndk-build便能在main/libs目錄下生成各種CPU架構的so包了。
4.6 項目中使用so包
編輯NdkUtil.java文件,添加加載so包的代碼後,NdkUtil.java內容如下
package com.yjing.ndk.utils;
public class NdkUtil {
static{
System.loadLibrary("hello");
}
public native String helloFromJni();
public native void callBackJavafromC();
}
然後在代碼中條用helloFromJni以及callBackJavafromC方法便能夠成功調用到native代碼中去。
這裏給出測試的MainActivity代碼吧
package com.yjing.ndk;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.yjing.ndk.utils.NdkUtil;
public class MainActivity extends AppCompatActivity {
private Button btnNdk;
private TextView tvNdk;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.initViews();
this.initEvents();
}
private void initViews(){
this.btnNdk = (Button)findViewById(R.id.btnNdk);
this.tvNdk = (TextView)findViewById(R.id.tvNdk);
}
private void initEvents(){
this.btnNdk.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NdkUtil ndkUtil = new NdkUtil();
ndkUtil.callBackJavafromC();
}
});
}
public void logout() {
System.out.println("hahahahahahahahahah===");
}
}
運行後提示錯誤
couldn't find "libhello.so
只需要修改build.gradle文件配置如下,便能解決問題
sourceSets{
main{
jni.srcDirs = ['libs']
jniLibs.srcDirs 'src/main/libs'
}
}
從代碼中可以看出java能夠調用native方法,而native也能夠調用java方法。
到這裏關於Android開發中的JNI/NDK基本總結就算完成了。
5、參考文獻
2、Android studio運行JNI程序以及生成.so文件(Windows下)
3、Android 中arm64-v8a、armeabi-v7a、armeabi、x86簡介~