Android jni开发记录---动态注册、导入第三方C


前言

随着Android移动开发深入了解,Java编译执行效率对比起来C或者C++确实不高。在项目中对时效性和底层功能要求比较严格情况下,还是优先选择C或者C++实现比较合理。

所以在学习过程中,发现C库或者好的C++库,如何使用Android JNI配合NDK开发。做如下记录。

一、JNI、NDK是什么?

基础概念不复数

二、操作步骤

在网络上发现比较好的图片处理原生C库,有大神帮忙整理。进行下载photoprocessing。下载后是一堆.c.h文件。如何在Android应用中使用呢?
按照如下步骤:


1、配置基础目录

在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文件。

注意:多个功能模块,需要注意打包顺序。依赖必须前置打包。否则报各种未定义错误

整个目录结构如下图:

 2、入口说明

根据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对象中,方便后续调用。但是:如果入口方法超多,动态注册会影响查找方法的时间。建议使用静态方法注册。

3、编译打包

在上述代码配置成功后。进行编译打包,开始实测。

出现问题汇总:

问题一: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 <stdlib.h>替换math.h,去掉方法的inline即可。


总结

JNI、NDK开发需要开发了解C或者C++语言,实现Android应用内部直接调用C。完成对应功能。在这个过程中。如果有公司核心代码,为防止代码破解,使用C或者C++编写。源码不易被破解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值