稍微說個為何要在安卓使用C語言開發東西,大學專題有用到手機寫影像辨識,利用camera2 api擷取到影像,直接用android sdk的bitmap + get/set pixel 硬幹,後果當然是慘不忍睹,效能說能多慢就有多慢,好像一張640*480的影像轉成灰階就要幾分鐘,而那時候採取的臨時解決方案是用get/set pixels ,這個也是sdk提供的原始方法之一,是利用陣列先做完運算再一次處理,減少了反覆呼叫方法的次數,結果速度有了很大的提升,就一直用這個方法到畢業展了,但我知道如果做影像處理,遲早還是要和底層打交道,而唸研究所的時間,打算來面對。
日前在github上有看到一個照片濾鏡的專案,核心照片濾鏡處理是一個library,透過JNI call底層C語言來加速,我覺得很重要,所以這篇分成兩個方式實作Android呼叫jni (javah、CMake)來測試看看
濾鏡專案 :
https://github.com/Zomato/AndroidPhotoFilters
第一種 : 利用javah、ndkbuild
1. AS裡面 -> SDK Manager -> SDK Tools -> 先把CMake、NDK 裝起來,沒問題的話IDE會自動設置套件路徑
2. AS裡面 -> File -> Settings -> Tools -> External Tools
3. 上面 +號 新增Tools (這個動作主要是把指令和參數寫成工具,以後要使用可以直接套用)
4. 新增 javah
參數 :
C:\Program Files\Java\jdk1.8.0_201\bin\javah.exe (這裡要對應自己jdk的路徑 需要注意的是用jdk10以下的,以上的沒有javah)
-v -jni -d $ModuleFileDir$/src/main/jni $FileClass$
$SourcepathEntry$
5. 新增 ndkbuild
參數 :
D:\Android\Sdk\ndk-bundle\ndk-build.cmd (這裡要對應自己ndk的路徑)
NDK_PROJECT_PATH=$ModuleFileDir$/build/intermediates/ndk NDK_LIBS_OUT=$ModuleFileDir$/src/main/jniLibs
NDK_APPLICATION_MK=$ModuleFileDir$/src/main/jni/Application.mk APP_BUILD_SCRIPT=$ModuleFileDir$/src/main/jni/Android.mk V=1
$ProjectFileDir$
6. 在Main方法下新增一個java類別 myNDK , 裡面寫兩個對應c語言要呼叫的function
public class myNDK {
static {
System.loadLibrary("myJNI");
}
public native String getMycstring();
public native int getJniAdd(int a, int b);
}
7. 切成Project模式 -> 在src/main下面新增 jni 資料夾
8. 設定app的build.gradle (這裡的modeulName 要和在 myNDK loadLibrary寫的一樣,jniLibs是之後ndkbuild放置so檔的地方)
9. 對剛剛新增的myNDK按右鍵 -> External Tools -> javah ,就會發現指令會自動執行,jni目錄下會產生header檔
裡面分別有兩個對應myNDK的function
10. 接著就是在jni目錄下,新增c++檔案 ,檔名必須和myNDK裡面loadLibrary那個名字一樣,所以取作myJNI.cpp
*注意,這裡的function要根據header的方法參數寫,不然會找不到拋錯
#include "com_aaron_jnitester3_myNDK.h"
JNIEXPORT jstring JNICALL Java_com_aaron_jnitester3_myNDK_getMycstring
(JNIEnv *env, jobject){
return (*env).NewStringUTF("MY !! NDKString!!");
}
JNIEXPORT jint JNICALL Java_com_aaron_jnitester3_myNDK_getJniAdd
(JNIEnv *env, jobject object, jint a, jint b) {
return a + b;
}
11. 接著就是在jni目錄下新增File Android.mk 和 Application.mk
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := myJNI
LOCAL_SRC_FILES := myJNI.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := all
12. 對jni目錄按右鍵 -> External Tools -> ndkBuild , 會發現指令自動執行,並且新增一個叫jniLibs的資料夾,裡面放置手機版本的so檔
13. 撰寫demo程式
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.textView);
myNDK myndk = new myNDK();
textView.setText(myndk.getMycstring() + myndk.getJniAdd(8,7)); //呼叫NDK方法
}
}
DEMO :
Source Code :
https://github.com/tks3589/BloggerCode/tree/master/android/jniTester3
第二種 : 利用CMake,直接建立預設支援C++專案
1. 直接 File -> New -> New Project -> Native C++
2. 裡面的檔案結構長這樣,基本上要寫自己的function,只要在Main裡面直接加,Alt + Enter 就會自動幫你在cpp檔裡面補方法了
DEMO:
CMakeLists.txt裡面應該是更改library的參數,沒用過先記錄
build出來的模擬器apk有 so檔案
Source Code :
https://github.com/tks3589/BloggerCode/tree/master/android/jniTester4
結論 :
第二種方法明顯快速許多,但陽春點的做法能更理解原理也是好事 ,還不是很熟悉,有問題的地方還請大家多多提出,謝謝。