1. JNI文件编译后生成头文件
a)首先,需要定义好头文件内容,例如
public class ImageUtil {
public static native String hello(String s);
public static native int[] disjoint(float[] pm, float[] lm, int w, int h, int neighbour);
}
b)然后通过javah命令,生成头文件(*.h)命令:
javah -classpath D:\\project\\app\\src\\main\\java -jni com.example.util.ImageUtil
而对于java对象生成jni头文件时可能会报错,比如Bitmap有可能找不到签名的错误,需要加上依赖包
javah -jni -classpath C:\\Users\\Administrator-Local1\\AppData\\Local\\Android\\Sdk\\platforms\\android-28\\android.jar;. com.example.util.ImageUtil
注意:需要进入main/java所在的目录,这样javah才能找到包名对应的路径,否则有可能遇到下面的错误
Exception in thread "main" java.lang.IllegalArgumentException: Not a valid class
name: java/com/example/util/ImageUtil
这种错误解决办法可参考:https://blog.csdn.net/github_37847975/article/details/79939895
生成完后就会有如下的头文件定义,格式为java开头,后面跟上包名,然后再加上类名,最后加上定义的方法名:
JNIEXPORT jstring JNICALL Java_com_example_util_ImageUtil_hello
(JNIEnv *, jclass, jstring);
接下来需要在C++实现Java_com_example_util_ImageUtil_hello方法,关于如何从C++中返回java对象,如C++返回HashMap<A ,B>是需要注意的一个问题,A,B可以是任意对象,需要熟悉JNI的反射机制,返回值的签名可参考http://gityuan.com/2016/05/28/android-jni/,
例如,HashMap<A ,B>的签名为
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"
例如,C++返回int[][],可通过返回jObjectArray对象时,其签名为
env->FindClass("[I")
通过
env->SetObjectArrayElement中设置每个元素对象是一个jintArray对象。
c)然后,当然java调用C++,实现方法内容
d)编译整个工程,目前cmake是主流,NDK慢慢被抛弃,下面会相信说明cmake的详细过程,编译工程的过程中也会按照编写好的cmake文件编译C++文件,编译完后在project->app->build->intermediates->cmake->debug->obj->arm64-v8a目录下生成so文件
2. cmake 编译
a)NEW->Folder->JNI Folder建立JNI文件夹,可以在该文件夹下放C++文件
b)CMakeLists.txt文件内容编写,主要是环境的相关配置内容,主要有以下内容
set(ANDROID_ABI armeabi-v7a)
# 设置opencv的路径
set(pathToOpenCv D:/OpenCV-android-sdk/sdk)
# 设置opencv头文件路径
include_directories(${pathToOpenCv}/sdk/native/jni/include)
#动态方式加载库文件
add_library(lib_opencv STATIC IMPORTED )
#引入libopencv_java4.so文件
#set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION #${PROJECT_SOURCE_DIR}/src/main/jni/${ANDROID_ABI}/libopencv_java4.so )
# 将当前 "./src/main/cpp" 目录下的所有源文件保存到 "NATIVE_SRC" 中,然后在 add_library 方法调用。
aux_source_directory(src/main/cpp NATIVE_SRC )
# 配置opencv,将opencv的库文件拷贝到jniLibs目录下,并配置头文件目录
#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs)
#set(OpenCV_DIR D:/OpenCV-android-sdk/sdk/native/jni)
#find_package(OpenCV REQUIRED)
include_directories(${CMAKE_SOURCE_DIR} D:/OpenCV-android-sdk/sdk/native/jni/include)
# ========================= import opencv lib start =============================
add_library(libopencv_highgui STATIC IMPORTED )
set_target_properties(libopencv_highgui PROPERTIES
IMPORTED_LOCATION "${lib}/${ANDROID_ABI}/libopencv_highgui.a")
add_library(libopencv_imgcodecs STATIC IMPORTED )
set_target_properties(libopencv_imgcodecs PROPERTIES
IMPORTED_LOCATION "${lib}/${ANDROID_ABI}/libopencv_imgcodecs.a")
add_library(libopencv_imgproc STATIC IMPORTED )
set_target_properties(libopencv_imgproc PROPERTIES
IMPORTED_LOCATION "${lib}/${ANDROID_ABI}/libopencv_imgproc.a")
add_library(libopencv_core STATIC IMPORTED )
set_target_properties(libopencv_core PROPERTIES
IMPORTED_LOCATION "${lib}/${ANDROID_ABI}/libopencv_core.a")
# ======================================================================
file(GLOB native_srcs "src/main/cpp/*.cpp")
# 指定生成的库名
add_library(hello_lib SHARED
hello.cpp)
find_library( # Defines the name of the path variable that stores the
# location of the NDK library.
log-lib
# Specifies the name of the NDK library that
# CMake needs to locate.
log )
target_link_libraries(hello_lib2 ${log-lib}
# ${OpenCV_LIBS}
${jnigraphics-lib} ${z-lib}
libopencv_imgproc libopencv_core
libopencv_highgui libopencv_imgcodecs)
gradle配置:
externalNativeBuild {
cmake {
arguments "--graphviz=./target_deps_graphviz"
cppFlags "-std=c++11 -s -lz -fvisibility=hidden -Os -fvisibility-inlines-hidden"
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs'] # 打包so到apk配置
}
}
c)加载so库,需要在程序的最开始初始化so库,可以放在static代码中,加载方式如下
static {
try {
OpenCVLoader.initDebug();
System.loadLibrary("hello_lib");
} catch (UnsatisfiedLinkError e) {
Log.e(TAG,"Native library not found, so library is unavailable.");
}
}
d) 对生成的so库进行压缩:
编译时使用的是ANDROID_NDK中的android.toolchain.cmake配置进行编译的,进行so压缩可以在该文件中修改相关配置参数进行编译,也可以在gradle配置文件中控制编译的相关参数,经过压缩后so从10.3M缩小到了4.09M,缩小了2.5倍,所以,在移动端有限的资源下,对so压缩有多重要就可想而知了,下面就是压缩的方法的一些总结。
1. 找到android_ndk->build->cmake->android.toolchain.cmake文件去掉-g -funwind-tables编译选项,同时去掉-Wl,--build-id链接 器选项,可以压缩1倍以上
2. 在gradle中设置cmake的cppFlags "-std=c++11 -s -lz"中加上-s,有可能导致so库不可用
3. 不要启用 Exceptions 和 RTTI
4. 不要使用 iostream(没有测试过,但是代码没有输出iostream)
5. 使用 -fvisibility=hidden(亲测,有效)
6. 使用 gc-sections 丢弃未使用的函数(nkd默认带有该参数)
7. 使用 –icf=safe 移除重复代码(测试发现,不起作用)
8. 修改交叉编译工具链的默认标记位
9. 限制编译的ABI
JNI可参考:https://www.jianshu.com/p/08dcc910b088
so压缩参考文档: Android NDK: How to Reduce Binaries Size
so文件的压缩可参考:https://zhuanlan.zhihu.com/p/72475595,尤其是-g选项
注意问题:1. ndk的版本非常非常重要,我是用ndkr16, 使用更高的版本不知道为什么没有编译过去
2. cmake的门槛有点高,对cmake不熟悉的不要随便修改编译的参数,除非你确定这个参数是正确的,有可能就是这 些参数设置的不对才编译不过去