JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为Java平台的一部分,它允许Java代码和其他语言写的代码进行交互。
这里是一篇关于JNI开发的基本知识的辨析,我觉得很不错,讲清楚了java,NDK, 安卓,JNI几个的联系与区别,但需要注意的是,这篇文章里主要讲的是通过NDK来创建JNI调用,本文主要讲的是通过cmake来完成JNI调用。但还是要做一下应有的辨析。
这部分内容参考了这里,在介绍 NDK 之前还是首推 Android 官方 NDK 文档。
官方文档分别从以下几个方面介绍了 NDK
首先先用简单的话分别解释下 JNI、NDK, 以及分别和 Android 开发、c/c++ 开发的配合。在解释过程中会对 Android.mk、Application.mk、ndk-build、CMake、CMakeList 这些常见名词进行扫盲。
JNI(Java Native Interface):Java本地接口。是为了方便Java调用c、c++等本地代码所封装的一层接口(也是一个标准)。大家都知道,Java的优点是跨平台,但是作为优点的同时,其在本地交互的时候就编程了缺点。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了jni专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。
NDK(Native Development Kit) : 原生开发工具包,即帮助开发原生代码的一系列工具,包括但不限于编译工具、一些公共库、开发IDE等。
NDK 工具包中提供了完整的一套将 c/c++ 代码编译成静态/动态库的工具,而 Android.mk 和 Application.mk 你可以认为是描述编译参数和一些配置的文件。比如指定使用c++11还是c++14编译,会引用哪些共享库,并描述关系等,还会指定编译的 abi。只有有了这些 NDK 中的编译工具才能准确的编译 c/c++ 代码。
ndk-build 文件是 Android NDK r4 中引入的一个 shell 脚本。其用途是调用正确的 NDK 构建脚本。其实最终还是会去调用 NDK 自己的编译工具。
那 CMake 又是什么呢。脱离 Android 开发来看,c/c++ 的编译文件在不同平台是不一样的。Unix 下会使用 makefile 文件编译,Windows 下会使用 project 文件编译。而 CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,然后再调用底层的编译。
在Android Studio 2.2 之后,工具中增加了 CMake 的支持,你可以这么认为,在 Android Studio 2.2 之后你有2种选择来编译你写的 c/c++ 代码。一个是 ndk-build + Android.mk + Application.mk 组合,另一个是 CMake + CMakeLists.txt 组合。这2个组合与Android代码和c/c++代码无关,只是不同的构建脚本和构建命令。本篇文章主要会描述后者的组合。(也是Android现在主推的)
ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。
armeabi设备只兼容armeabi;
armeabi-v7a设备兼容armeabi-v7a、armeabi;
arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
X86设备兼容X86、armeabi;
X86_64设备兼容X86_64、X86、armeabi;
mips64设备兼容mips64、mips;
mips只兼容mips;
当我们开发 Android 应用的时候,由于 Java 代码运行在虚拟机上,所以我们从来没有关心过这方面的问题。但是当我们开发或者使用原生代码时就需要了解不同 ABI 以及为自己的程序选择接入不同 ABI 的库。(库越多,包越大,所以要有选择)
总结一下:
android 调用JNI 分为静态调用与动态调用(不论动态还是静态前提都是NDK环境已经配置好的前提下)
一、静态主要就是将c(.c)或者c++(cpp)的源文件直接加到项目中进行调用,然后在CMakeLists.txt中进行配置。
二、动态调用
动态调用使用已经编译好的动态库.so文件
以下是创建具有Native OpenCV 支持的新 Android Studio 项目的步骤:
从主菜单中选择File -> New -> New Project...
。
单击手机和平板选项卡,选择Native C++,然后单击下一步。
选择一个应用程序名称,选择语言(Kotlin 或 Java),选择最低 API 级别(此处为 28),然后选择下一步。
选择 Toolchain default as C++ standard 并单击 Finish。
创建成功后,直接build运行,手机界面出现 Hello from C++
build后会生产.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')
添加到依赖项部分:dependencies {
...
implementation project(path: ':opencv')
}
或者可以点击 File -> Project Structure -> Dependencies -> app -> + -> Module Dependency -> opencv
会自动在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
指令参数中,添加以下行: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设置的
include_directories(${OpenCV_DIR}/jni/include)
# 起名字为lib_opencv,SHARED,动态库 STATIC,静态库, IMPORTED外部导入
add_library( lib_opencv SHARED IMPORTED )
# 二进制文件ANDROID_ABI也在app的buildgradle设置的
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本地库和动态链接库
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})
如果你对其中的具体细节有不清楚的地方,这里的附录是CMake的使用
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-feature android:name="android.hardware.camera.front"/>
<uses-feature android:name="android.hardware.camera.front.autofocus"/>
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);
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<org.opencv.android.JavaCameraView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/main_surface"
app:show_fps="true" />
</LinearLayout>
#include <jni.h>
#include <android/log.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#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);
}
}
关于图文教程,网上有很多,但是随着opencv和android studio的更新,有一些已经不适用了,所以上面主要写的着重于Cmakelist.txt
的配置,build.gradle
对于cmake配置, gradle.properties
的配置以及它们之间的关系,学会了之后就不是照搬,可以举一反三。
具体的图文教程我觉得也有一篇不错的,但是肯定会遇到问题,所以在那边遇到问题的时候可以到这边来参考。
OpenCV 在 Android Studio 的使用教程