JNI是Java Native Interface的縮寫,它提供了若干的API實(shí)現(xiàn)了Java和其他語言的通信(主要是C&C++)。從Java1.1開始,JNI標(biāo)準(zhǔn)成為Java平臺(tái)的一部分,它允許Java代碼和其他語言寫的代碼進(jìn)行交互。
這里是一篇關(guān)于JNI開發(fā)的基本知識(shí)的辨析,我覺得很不錯(cuò),講清楚了java,NDK, 安卓,JNI幾個(gè)的聯(lián)系與區(qū)別,但需要注意的是,這篇文章里主要講的是通過NDK來創(chuàng)建JNI調(diào)用,本文主要講的是通過cmake來完成JNI調(diào)用。但還是要做一下應(yīng)有的辨析。
NDK 簡介這部分內(nèi)容參考了這里,在介紹 NDK 之前還是首推 Android 官方 NDK 文檔。
官方文檔分別從以下幾個(gè)方面介紹了 NDK
首先先用簡單的話分別解釋下 JNI、NDK, 以及分別和 Android 開發(fā)、c/c++ 開發(fā)的配合。在解釋過程中會(huì)對(duì) Android.mk、Application.mk、ndk-build、CMake、CMakeList 這些常見名詞進(jìn)行掃盲。
JNI(Java Native Interface):Java本地接口。是為了方便Java調(diào)用c、c++等本地代碼所封裝的一層接口(也是一個(gè)標(biāo)準(zhǔn))。大家都知道,Java的優(yōu)點(diǎn)是跨平臺(tái),但是作為優(yōu)點(diǎn)的同時(shí),其在本地交互的時(shí)候就編程了缺點(diǎn)。Java的跨平臺(tái)特性導(dǎo)致其本地交互的能力不夠強(qiáng)大,一些和操作系統(tǒng)相關(guān)的特性Java無法完成,于是Java提供了jni專門用于和本地代碼交互,這樣就增強(qiáng)了Java語言的本地交互能力。
NDK(Native Development Kit) : 原生開發(fā)工具包,即幫助開發(fā)原生代碼的一系列工具,包括但不限于編譯工具、一些公共庫、開發(fā)IDE等。
NDK 工具包中提供了完整的一套將 c/c++ 代碼編譯成靜態(tài)/動(dòng)態(tài)庫的工具,而 Android.mk 和 Application.mk 你可以認(rèn)為是描述編譯參數(shù)和一些配置的文件。比如指定使用c++11還是c++14編譯,會(huì)引用哪些共享庫,并描述關(guān)系等,還會(huì)指定編譯的 abi。只有有了這些 NDK 中的編譯工具才能準(zhǔn)確的編譯 c/c++ 代碼。
ndk-build 文件是 Android NDK r4 中引入的一個(gè) shell 腳本。其用途是調(diào)用正確的 NDK 構(gòu)建腳本。其實(shí)最終還是會(huì)去調(diào)用 NDK 自己的編譯工具。
那 CMake 又是什么呢。脫離 Android 開發(fā)來看,c/c++ 的編譯文件在不同平臺(tái)是不一樣的。Unix 下會(huì)使用 makefile 文件編譯,Windows 下會(huì)使用 project 文件編譯。而 CMake 則是一個(gè)跨平臺(tái)的編譯工具,它并不會(huì)直接編譯出對(duì)象,而是根據(jù)自定義的語言規(guī)則(CMakeLists.txt)生成 對(duì)應(yīng) makefile 或 project 文件,然后再調(diào)用底層的編譯。
在Android Studio 2.2 之后,工具中增加了 CMake 的支持,你可以這么認(rèn)為,在 Android Studio 2.2 之后你有2種選擇來編譯你寫的 c/c++ 代碼。一個(gè)是 ndk-build + Android.mk + Application.mk 組合,另一個(gè)是 CMake + CMakeLists.txt 組合。這2個(gè)組合與Android代碼和c/c++代碼無關(guān),只是不同的構(gòu)建腳本和構(gòu)建命令。本篇文章主要會(huì)描述后者的組合。(也是Android現(xiàn)在主推的)
ABI 是什么ABI(Application binary interface)應(yīng)用程序二進(jìn)制接口。不同的CPU 與指令集的每種組合都有定義的 ABI (應(yīng)用程序二進(jìn)制接口),一段程序只有遵循這個(gè)接口規(guī)范才能在該 CPU 上運(yùn)行,所以同樣的程序代碼為了兼容多個(gè)不同的CPU,需要為不同的 ABI 構(gòu)建不同的庫文件。當(dāng)然對(duì)于CPU來說,不同的架構(gòu)并不意味著一定互不兼容。
armeabi設(shè)備只兼容armeabi;
armeabi-v7a設(shè)備兼容armeabi-v7a、armeabi;
arm64-v8a設(shè)備兼容arm64-v8a、armeabi-v7a、armeabi;
X86設(shè)備兼容X86、armeabi;
X86_64設(shè)備兼容X86_64、X86、armeabi;
mips64設(shè)備兼容mips64、mips;
mips只兼容mips;
當(dāng)我們開發(fā) Android 應(yīng)用的時(shí)候,由于 Java 代碼運(yùn)行在虛擬機(jī)上,所以我們從來沒有關(guān)心過這方面的問題。但是當(dāng)我們開發(fā)或者使用原生代碼時(shí)就需要了解不同 ABI 以及為自己的程序選擇接入不同 ABI 的庫。(庫越多,包越大,所以要有選擇)
總結(jié)一下:
android 調(diào)用JNI 分為靜態(tài)調(diào)用與動(dòng)態(tài)調(diào)用(不論動(dòng)態(tài)還是靜態(tài)前提都是NDK環(huán)境已經(jīng)配置好的前提下)
一、靜態(tài)主要就是將c(.c)或者c++(cpp)的源文件直接加到項(xiàng)目中進(jìn)行調(diào)用,然后在CMakeLists.txt中進(jìn)行配置。
二、動(dòng)態(tài)調(diào)用
動(dòng)態(tài)調(diào)用使用已經(jīng)編譯好的動(dòng)態(tài)庫.so文件
以下是創(chuàng)建具有Native OpenCV 支持的新 Android Studio 項(xiàng)目的步驟:
從主菜單中選擇File ->New ->New Project...
。
單擊手機(jī)和平板選項(xiàng)卡,選擇Native C++,然后單擊下一步。
選擇一個(gè)應(yīng)用程序名稱,選擇語言(Kotlin 或 Java),選擇最低 API 級(jí)別(此處為 28),然后選擇下一步。
選擇 Toolchain default as C++ standard 并單擊 Finish。
創(chuàng)建成功后,直接build運(yùn)行,手機(jī)界面出現(xiàn) Hello from C++
build后會(huì)生產(chǎn).so文件,一般在build->intermediates->cmake->debug->obj下,這里即為把本地的c++文件打包成了so庫
include ':opencv'
project(':opencv').projectDir = new File(opencvsdk + '/sdk')
opencvsdk=/Users/Example/Downloads/OpenCV-android-sdk
File ->New ->Improt Module
D:\你的路徑\OpenCV-android-sdk\sdk
implementation project(path: ':opencv')
添加到依賴項(xiàng)部分:dependencies {...
implementation project(path: ':opencv')
}
或者可以點(diǎn)擊File ->Project Structure ->Dependencies ->app ->+ ->Module Dependency ->opencv
會(huì)自動(dòng)在build.gradle中添加依賴
File ->Sync Project with Gradle Files
.android ->defaultConfig ->externalNativeBuild ->cmake
部分,放入以下三行:cppFlags "-frtti -fexceptions"
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
arguments "-DOpenCV_DIR=" + opencvsdk + "/sdk/native"
add_library
之前,添加以下三行:include_directories(${OpenCV_DIR}/jni/include)
add_library( lib_opencv SHARED IMPORTED )
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${OpenCV_DIR}/libs/${ANDROID_ABI}/libopencv_java4.so)
target_link_libraries
指令參數(shù)中,添加以下行:lib_opencv
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# 引入頭文件搜索路徑,這里找到的是opencv2
# OpenCV_DIR在app的buildgradle設(shè)置的
include_directories(${OpenCV_DIR}/jni/include)
# 起名字為lib_opencv,SHARED,動(dòng)態(tài)庫 STATIC,靜態(tài)庫, IMPORTED外部導(dǎo)入
add_library( lib_opencv SHARED IMPORTED )
# 二進(jìn)制文件ANDROID_ABI也在app的buildgradle設(shè)置的
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${OpenCV_DIR}/libs/${ANDROID_ABI}/libopencv_java4.so)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
# 本地庫
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
# link本地庫和動(dòng)態(tài)鏈接庫
target_link_libraries( # Specifies the target library.
native-lib
lib_opencv
# Links the target library to the log library
# included in the NDK.
${log-lib})
如果你對(duì)其中的具體細(xì)節(jié)有不清楚的地方,這里的附錄是CMake的使用
package com.example.nativeopencvandroidtemplate;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import android.util.Log;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;
public class MainActivity extends Activity implements CvCameraViewListener2 {private static final String TAG = "MainActivity";
private static final int CAMERA_PERMISSION_REQUEST = 1;
private CameraBridgeViewBase mOpenCvCameraView;
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {@Override
public void onManagerConnected(int status) {if (status == LoaderCallbackInterface.SUCCESS) {Log.i(TAG, "OpenCV loaded successfully");
// Load native library after(!) OpenCV initialization
System.loadLibrary("native-lib");
mOpenCvCameraView.enableView();
} else {super.onManagerConnected(status);
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {Log.i(TAG, "called onCreate");
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Permissions for Android 6+
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.CAMERA},
CAMERA_PERMISSION_REQUEST
);
setContentView(R.layout.activity_main);
mOpenCvCameraView = findViewById(R.id.main_surface);
mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
mOpenCvCameraView.setCvCameraViewListener(this);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NotNull String[] permissions, @NotNull int[] grantResults) {if (requestCode == CAMERA_PERMISSION_REQUEST) {if (grantResults.length >0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {mOpenCvCameraView.setCameraPermissionGranted();
} else {String message = "Camera permission was not granted";
Log.e(TAG, message);
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
} else {Log.e(TAG, "Unexpected permission request");
}
}
@Override
public void onPause() {super.onPause();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
@Override
public void onResume() {super.onResume();
if (!OpenCVLoader.initDebug()) {Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, mLoaderCallback);
} else {Log.d(TAG, "OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
@Override
public void onDestroy() {super.onDestroy();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
@Override
public void onCameraViewStarted(int width, int height) {}
@Override
public void onCameraViewStopped() {}
@Override
public Mat onCameraFrame(CvCameraViewFrame frame) {// get current camera frame as OpenCV Mat object
Mat mat = frame.gray();
// native call to process current camera frame
adaptiveThresholdFromJNI(mat.getNativeObjAddr());
// return processed frame for live preview
return mat;
}
private native void adaptiveThresholdFromJNI(long mat);
}
#include#include#include#include#define TAG "NativeLib"
using namespace std;
using namespace cv;
extern "C" {void JNICALL
Java_com_example_nativeopencvandroidtemplate_MainActivity_adaptiveThresholdFromJNI(JNIEnv *env,
jobject instance,
jlong matAddr) {// get Mat from raw address
Mat &mat = *(Mat *) matAddr;
clock_t begin = clock();
cv::adaptiveThreshold(mat, mat, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 21, 5);
// log computation time to Android Logcat
double totalTime = double(clock() - begin) / CLOCKS_PER_SEC;
__android_log_print(ANDROID_LOG_INFO, TAG, "adaptiveThreshold computation time = %f seconds\n",
totalTime);
}
}
補(bǔ)充圖文教程關(guān)于圖文教程,網(wǎng)上有很多,但是隨著opencv和android studio的更新,有一些已經(jīng)不適用了,所以上面主要寫的著重于Cmakelist.txt
的配置,build.gradle
對(duì)于cmake配置,gradle.properties
的配置以及它們之間的關(guān)系,學(xué)會(huì)了之后就不是照搬,可以舉一反三。
具體的圖文教程我覺得也有一篇不錯(cuò)的,但是肯定會(huì)遇到問題,所以在那邊遇到問題的時(shí)候可以到這邊來參考。
OpenCV 在 Android Studio 的使用教程
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧