目前基于OpenCV 图像处理的开发都是Windows或LInux下直接C++算法实现,如何将已有的C++实现用在Android上,这就需要用到交叉编译了。本节主要对CMake V3.10 官网教程 Cross Compiling for Android 的简单翻译以及如何将交叉编译后部署在Android上的讲解。
Cross Compiling for Android
通过在编译工具链配置文件中设置CMAKE_SYSTEM_NAME变量为Android可以配置Android交叉编译,更多配置取决与Android开发环境的使用。
对于用CMake生成Makefile或Ninja,CMake要求配置NDK或Standalone Toolchain。
CMake通过如下步骤来配置NDK或NinJa:
1.假如CMake中设置了CMAKE_ANDROID_NDK的变量,CMake将会使用指定路径的NDK。
2.假如CMake中设置了CMAKE_ANDROID_STANDALONE_TOOLCHAIN的变量,CMake将会使用指定路径的Standalone Toolchain。
3.假如以<ndk>/platforms/android-<api>/arch-<arch>的格式赋值给CMAKE_SYSROOT的变量,则<ndk>部分将会用CMAKE_ANDROID_NDK替换并且将使用NDK方式编译。
4.假如以<standalone-toolchain>/sysroot的格式赋值给CMAKE_SYSROOT的变量,则<standalone-toolchain>部分将会用CMAKE_ANDROID_STANDALONE_TOOLCHAIN替换并且将使用Standalone Toolchain将被使用。 5.假如cmake中设置了ANDROID_NDK的变量,这将被当成CMAKE_ANDROID_NDK使用,工程将使用NDK编译。
6.假如cmkae中设置了ANDROID_STANDALONE_TOOLCHAIN的变量,这将被当成CMAKE_ANDROID_STANDALONE_TOOLCHAIN使用,将使用Standalone Toolchain编译。
7.假如设置了环境变量ANDROID_NDK_ROOT或ANDROID_NDK,这将被当成CMAKE_ANDROID_NDK,将使用NDK编译。
8.假如设置了环境变量ANDROID_STANDALONE_TOOLCHAIN,这将被当成CMAKE_ANDROID_STANDALONE_TOOLCHAIN,将使用Standalone Toolchain编译。
9.通过错误可以判断工程是否设置了NDK或Standalone Toolchain。
Cross Compiling for Android with the NDK
编译文件可以配置Android交叉编译生成的Makefile或Ninja文件。
通过如下变量配置来说明如何使用Android NDK:
1.CMAKE_SYSTEM_NAME
设置成Android。必须的,用于开启Android交叉编译。
2.CMAKE_SYSTEM_VERSION
用于设置Android API级别,假如未特别指定,该值将通过如下方式决定:
a.假如设置了CMAKE_ANDROID_API,该值将被作为Android API的级别。
b.假如设置了CMKAE_SYSROOT变量,Android API级别由NDK目录下的sysroot决定。
c.否则将在NDK中使用最新的API级别。
CMAKE_ANDROID_ARCH_ABI
用于设置Android ABI(构架),假如未具体指定,该变量将被设置为默认的armabi。CMAKE_ANDROID_ARCH将会由CMAKE_ANDROID_ARCH_ABI自动推断出来。
CMAKE_ANDROID_NDK
以Android NDK根目录绝对路径的方式赋值${CMKAE_ANDROID_NDK}/platforms目录必须存在。假如未指定将赋默认值。
CMKAE_ANDROID_NDK_DEPRECATED_HEADERS
设置为true将不会使用标准头文件而使用弃用的per-api-level头文件。未指定将默认为false,除非使用的NDK不存在标准头文件。
CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION
设置编译器的NDK工具链版本,未指定则使用最新的GCC工具链。
CMKAE_ANDDROID_STL_TYPE
设置使用的C++标准库。
对于官网的基本介绍就这些了,下面讲解一以下自己的简单使用,主要是用c++编写处理程序,然后用交叉编译编译出Android平台的动态库.so,接着使用JNI调用。
1.新建mymath.h、mymath.cpp
#ifndef _MYMATH_H #define _MYMATH_H int myadd(int a, int b); int mysub(int a, int b); int mymul(int a, int b); #endif mymath.cpp #include<iostream> #include"mymath.h" int myadd(int a, int b) { return a + b; } int mysub(int a, int b) { return a - b; } int mymul(int a, int b) { return a * b; }2.编写编译文件CMakeLists.txt,编译生成mymath.so:
# this is required set(CMAKE_SYSTEM_NAME Android) # API level set(CMAKE_SYSTEM_VERSION 21) # specify the cross compiler set(CMAKE_ANDROID_ARCH_ABI armeabi-v7a) set(CMAKE_ANDROID_ARM_NEON ON) set(CMAKE_ANDROID_NDK android-ndk-r16b) set(CMAKE_ANDROID_STL_TYPE gnustl_static) cmake_minimum_required(VERSION 3.10) #project must after android set project(mymath) add_library(mymath SHARED mymath.h mymath.cpp)3.新建一个AS工程,选中app/src/main右键New->Folder->JNI Folder,会在app/src/main下生成1个jni文件夹,选中app/src/main/jni 右键New->Directory,文件名为prebuilt,在选中app/src/main/jni/prebuilt右键New->Directory,文件名为include,将步骤(1)的mymath.h拷贝到app/src/main/jni/prebuilt/include,将mymath.so拷贝到app/src/main/jni/prebuilt下,选中app/src/main/jni/prebuilt右键New->File,文件名为Android.mk,内容如下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := mymath LOCAL_SRC_FILES := libmymath.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include include $(PREBUILT_SHARED_LIBRARY)4.选中app/src/main/java/xinyi61.ndkbuild 新建一个mymath的类,
package xinyi61.ndkbuild; public class mymath { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("mymath"); } public native int jnimathAdd(int a, int b); public native int jnimathSub(int a, int b); public native int jnimathMul(int a, int b); }根据OpenCV移动端之android JNI第6步生成对应的头文件xinyi61_ndkbuild_mymath.h,并新建xinyi61_ndkbuild_mymath.cpp完成其实现:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class xinyi61_ndkbuild_mymath */ #ifndef _Included_xinyi61_ndkbuild_mymath #define _Included_xinyi61_ndkbuild_mymath #ifdef __cplusplus extern "C" { #endif /* * Class: xinyi61_ndkbuild_mymath * Method: jnimathAdd * Signature: (II)I */ JNIEXPORT jint JNICALL Java_xinyi61_ndkbuild_mymath_jnimathAdd (JNIEnv *, jobject, jint, jint); /* * Class: xinyi61_ndkbuild_mymath * Method: jnimathSub * Signature: (II)I */ JNIEXPORT jint JNICALL Java_xinyi61_ndkbuild_mymath_jnimathSub (JNIEnv *, jobject, jint, jint); /* * Class: xinyi61_ndkbuild_mymath * Method: jnimathMul * Signature: (II)I */ JNIEXPORT jint JNICALL Java_xinyi61_ndkbuild_mymath_jnimathMul (JNIEnv *, jobject, jint, jint); #ifdef __cplusplus } #endif #endif // // Created by xinyi61 on 18-7-6. // #include <android/log.h> #include "xinyi61_ndkbuild_mymath.h" #include "prebuilt/include/mymath.h" #define LOG_TAG "MYJNI" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_xinyi61_ndkbuild_mymath_jnimathAdd (JNIEnv *env, jobject jobj, jint a, jint b) { LOGI("11111111111111111111111111111111"); return myadd(a, b); } JNIEXPORT jint JNICALL Java_xinyi61_ndkbuild_mymath_jnimathSub (JNIEnv *env, jobject jobj, jint a, jint b) { LOGI("22222222222222222222222222222222"); return mysub(a, b); } JNIEXPORT jint JNICALL Java_xinyi61_ndkbuild_mymath_jnimathMul (JNIEnv *env, jobject jobj, jint a, jint b) { LOGI("33333333333333333333333333333333"); return mymul(a, b); } #ifdef __cplusplus } #endif并在app/src/main/jni下新建Android.mk和Application.mk
#Android.mk LOCAL_PATH:=$(call my-dir) #include $(CLEAR_VARS) #LOCAL_MODULE := mymath #LOCAL_SRC_FILES := $(LOCAL_PATH)/prebuilt/libmymath.so #LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/prebuilt/include #include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := jniMymath LOCAL_SRC_FILES:=xinyi61_ndkbuild_mymath.cpp LOCAL_LDLIBS += -llog LOCAL_SHARED_LIBRARIES := mymath include $(BUILD_SHARED_LIBRARY) include $(LOCAL_PATH)/prebuilt/Android.mk #Application.mk APP_ABI := armeabi-v7a APP_STL := stlport_static APP_CPPFLAGS:=-frtti -fexceptions5.打开AS Terminal进入app/src/main/jni,运行ndk-build生成最终的.so.
6.修改app/src/main/java/xinyi61.ndkbuid下的MainActivity
package xinyi61.ndkbuild; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import android.util.Log; public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); System.loadLibrary("jniMymath"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = (TextView) findViewById(R.id.sample_text); Log.e("mmm", "11111111111111111"); mymath a = new mymath(); Log.e("mmm", "22222222222222222"); tv.setText(stringFromJNI() + a.jnimathSub(100, 10)); Log.e("mmm", "33333333333333333"); } /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI(); }修改app/src下的build.gradle,红色部分增加部分:
apply plugin: 'com.android.application' android { compileSdkVersion 27 defaultConfig { applicationId "xinyi61.ndkbuild" minSdkVersion 21 targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { cppFlags "" } } ndk { moduleName "jniMymath" ldLibs "log" abiFilters "armeabi-v7a" } } sourceSets{ main{ jni.srcDirs = [] jniLibs.srcDirs 'src/main/libs' } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } externalNativeBuild { cmake { path "CMakeLists.txt" } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }最终效果如下:
参考:
android.mk
