随着Android移动开发深入了解,Java编译执行效率对比起来C或者C++确实不高。在项目中对时效性和底层功能要求比较严格情况下,还是优先选择C或者C++实现比较合理。
所以在学习过程中,发现C库或者好的C++库,如何使用Android JNI配合NDK开发。做如下记录。
基础概念不复数
在网络上发现比较好的图片处理原生C库,有大神帮忙整理。进行下载photoprocessing。下载后是一堆.c.h文件。如何在Android应用中使用呢?
按照如下步骤:
在module或者app中的build.gradle中增加Ndk配置信息,C或者C++源码在module中就配置module的build.gradle。C或者C++源码在app中就配置app的build.gradle,配置位置错误会编译失败。
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
externalNativeBuild {
cmake {
cFlags "-DSTDC_HEADERS"
cppFlags "" //如果使用 C++11 标准,则改为 "-std=c++11"
// 生成.so库的目标平台
// abiFilters "armeabi-v7a", "arm64-v8a", "x86"
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
在配置的src/main/cpp/CMakeLists.txt中建立对应CMakeLists.txt文件,用来指定打包后的依赖库名称,打包哪些文件等信息。
# 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.10.2)
# Declares and names the project.
# 项目信息
#project(hahah)
# 指定生成目标
#add_executable(Demo main.cc)
# 批量add_executable文件
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
#aux_source_directory(. DIR_SRCS)
# 指定生成目标
#add_executable(Demo ${DIR_SRCS})
project("OTAMedia")
# 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.
file(GLOB photoprocessing photoprocessing/*.c)
include_directories(photoprocessing)
file(GLOB metac metac/*.c)
include_directories(metac)
file(GLOB meta meta/*.cpp)
include_directories(meta)
aux_source_directory(. DIR_SRCS)
add_library( # Sets the name of the library.
dyMedia
SHARED
${meta}
${metac}
${photoprocessing}
${DIR_SRCS})
# 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.
target_link_libraries( # Specifies the target library.
dyMedia
# Links the target library to the log library
# included in the NDK.
${log-lib})
其中
file(GLOB photoprocessing photoprocessing/*.c) include_directories(photoprocessing)
是找到并针对photoprocessing文件夹中所有的.c文件进行编译生成.o文件。
注意:多个功能模块,需要注意打包顺序。依赖必须前置打包。否则报各种未定义错误
整个目录结构如下图:
根据CMakeLists.txt中的配置,知道最后生成的so文件名称为dyMedia,所以java代码中按照该名称进行库导入。
package com.darly.utils.ndk;
public class NdkDyMedia {
static {
System.loadLibrary("dyMedia");
}
public static native String media();
public static native void inputTypeMedia();
public static native void inputHasMedia(int width, int height);
public static native void inputRow(int y, int[] pixels);
public static native int inputBitmap(byte[] jpegData, int size, int maxPixels);
public static native int checkChar(char y);
public static native int nativeInitBitmap(int width, int height);
public static native void nativeGetBitmapRow(int y, int[] pixels);
public static native void nativeSetBitmapRow(int y, int[] pixels);
public static native int nativeGetBitmapWidth();
public static native int nativeGetBitmapHeight();
public static native void nativeDeleteBitmap();
public static native int nativeRotate90();
public static native void nativeRotate180();
public static native void nativeFlipHorizontally();
......
}
上述java代码就是定义好的JNI接口,提供java调用。直接调用这里的方法,就会根据系统规则。找到C++中对应的方法。进行C或者C++代码执行。
jni动态注册代码如下
#include "jni.h"
#include "iostream"
#include "stdio.h"
#include "stdlib.h"
#include "dmedia.h"
#include "meta/meta.h"
extern "C" {
#include "metac/ctsc.h"
#include "photoprocessing/photo_processing.h"
}
#define JNIREG_CLASS "com/darly/utils/ndk/NdkDyMedia" //Java类的路径:包名+类名
#define NUM_METHOES(x) ((int) (sizeof(x) / sizeof((x)[0]))) //获取方法的数量
static JNINativeMethod method_table[] = {
// 第一个参数a 是java native方法名,
// 第二个参数 是native方法参数,括号里面是传入参的类型,外边的是返回值类型,
// 第三个参数 是c/c++方法参数,括号里面是返回值类型,
{"media", "()Ljava/lang/String;", (jstring *) media},
{"inputTypeMedia", "()V", (void *) inputTypeMedia},
{"inputHasMedia", "(II)V", (void *) inputHasMedia},
{"inputRow", "(I[I)V", (void *) inputRow},
{"inputBitmap", "([BII)I", (jint *) inputBitmap},
{"checkChar", "(C)I", (jint *) checkChar},
{"initBitmap", "(II)I", (jint *) initBitmap},
{"nativeInitBitmap", "(II)I", (jint *) nativeInitBitmap},
{"nativeGetBitmapRow", "(I[I)V", (void *) nativeGetBitmapRow},
{"nativeSetBitmapRow", "(I[I)V", (void *) nativeSetBitmapRow},
{"nativeGetBitmapWidth", "()I", (jint *) nativeGetBitmapWidth},
{"nativeGetBitmapHeight", "()I", (void *) nativeGetBitmapHeight},
{"nativeDeleteBitmap", "()V", (void *) nativeDeleteBitmap},
{"nativeFlipHorizontally", "()V", (void *) nativeFlipHorizontally},
{"nativeRotate90", "()I", (jint *) nativeRotate90},
{"nativeRotate180", "()V", (void *) nativeRotate180},
{"nativeApplyInstafix", "()V", (void *) nativeApplyInstafix},
{"nativeApplyAnsel", "()V", (void *) nativeApplyAnsel},
{"nativeApplyTestino", "()V", (void *) nativeApplyTestino},
{"nativeApplyXPro", "()V", (void *) nativeApplyXPro},
{"nativeApplyRetro", "()V", (void *) nativeApplyRetro},
{"nativeApplyBW", "()V", (void *) nativeApplyBW},
{"nativeApplySepia", "()V", (void *) nativeApplySepia},
{"nativeApplyCyano", "()V", (void *) nativeApplyCyano},
{"nativeApplyGeorgia", "()V", (void *) nativeApplyGeorgia},
{"nativeApplySahara", "()V", (void *) nativeApplySahara},
{"nativeApplyHDR", "()V", (void *) nativeApplyHDR},
{"nativeLoadResizedJpegBitmap", "([BII)I", (jint *) nativeLoadResizedJpegBitmap},
{"nativeResizeBitmap", "(II)I", (jint *) nativeResizeBitmap},
};
static int registerMethods(JNIEnv *env, const char *className,
JNINativeMethod *gMethods, int numMethods) {
jclass clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
//注册native方法
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jint
JNI_OnLoad(JavaVM
*vm,
void *reserved
) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return
JNI_ERR;
}
// 注册native方法
if (!
registerMethods(env,
JNIREG_CLASS, method_table, NUM_METHOES(method_table))) {
return
JNI_ERR;
}
return
JNI_VERSION_1_6;
}
其中
extern "C" { #include "metac/ctsc.h" #include "photoprocessing/photo_processing.h" }
这里的含义是从C++处调用C代码,还是比较厉害,C++可以直接通过这种方式调用C语言。但是C语言不能直接调用C++。
#define JNIREG_CLASS "com/darly/utils/ndk/NdkDyMedia" 这句是关联Java类,有了这句C或C++才能找到对应的JavaJNI类。否则Java调用JNI方法报错。
JVM中提供了JNI_OnLoad方法,在so库被加载时调用,同样被卸载时会调用JNI_OnUnload函数。没有JNIEnv对象,使用GetEnv进行获取。
当so加载完成时,method_table中的所有方法都被注册关联到了Java对象中,方便后续调用。但是:如果入口方法超多,动态注册会影响查找方法的时间。建议使用静态方法注册。
在上述代码配置成功后。进行编译打包,开始实测。
出现问题汇总:
问题一:method_table: error: undefined reference to 'initBitmap(_JNIEnv*, _jobject*, int, int)'
出现这种情况原因是C++直接调用C,当C++引用C的.h文件时,直接引用是无法调用C中的方法,就会出现这些方法未定义的情况。
解决方案:给引用的.h文件增加外壳 extern "C" {},明确告知我就是去调用C,不服气也要给我强制沟通。这样问题解决。
问题二:error: undefined reference to 'hsbToRgb'
C代码中的有些方法没有声明。
出现原因是CMakeLists.txt中配置的打包文件顺序错误,导致依赖的文件未先打包。导致引用错误。
解决方案:调整打包顺序即可。
问题三:photoprocessing/colour_space.c:93: error: undefined reference to 'convert'
原因代码中这样定义,
inline unsigned char convert(float val) { return floorf((255 * val) + 0.5f); }
C直接调用Math.h找不到floorf方法。需要调用#include
JNI、NDK开发需要开发了解C或者C++语言,实现Android应用内部直接调用C。完成对应功能。在这个过程中。如果有公司核心代码,为防止代码破解,使用C或者C++编写。源码不易被破解