如何将底层C/C++库封装成Java层sdk供上层调用

对于Android程序员来说把java代码封装成sdk比把c/c++库包封装成java层的sdk还是要容易,要将c/c++库二次封装成java层sdk需要掌握的知识还是比较多的,既要熟悉java,c/c++还要熟悉ndk的开发,而有这种需求的公司还是比较多的,笔者也曾面试过几家做智能硬件公司,他们有专门的c/c++底层驱动包括java开发的工程师,项目已经有完整的c/c++库,需要找一个能将已有的c/c++ 库封装成java层的sdk提供给合作商使用。
在开发中我们sdk和封装底层框架思想也是大致相同的,要考虑的东西还是很多的,要做到的是尽量的少修改ndk层的代码(每修改一次都要进行重新编译so库),我们要就要细分功能,做到有些共用可扩展,尽量的在java层去做扩展。比如我们有两个功能都对应了写了两个navtive方法去调用,而这两个功能中有调用了相同的c/c++方法。若是以后还要扩展一个功能 里面也会调用到这个共用的c/c++函数,难到我们又要继续写一个对应native方法去完成功能扩展吗?No,如果是这样的话又要从新编译c/c++代码生成新的so库。这里我们要尽量的做到功能的拆分,想办法把功能扩展放到java层。怎样才能做到这样呢,可以照我们Android系统的源码,做到java层对象映射到c/c++层的对象,比如系统Bitmap源码 Parcel(对象序列化)源码,下面我们看看他们是怎么套路的。

//Bitmap.java 部分代码
public final class Bitmap implements Parcelable {
    // Convenience for JNI access
    //方便JNI访问
    private final long mNativePtr;

    ..........
    /**
     * Private constructor that must received an already allocated native bitmap
     * int (pointer).
     */
    // called from JNI
    //jni 调用
    Bitmap(long nativeBitmap, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
        if (nativeBitmap == 0) {
            throw new RuntimeException("internal error: native bitmap is 0");
        }

        mWidth = width;
        mHeight = height;
        mIsMutable = isMutable;
        mRequestPremultiplied = requestPremultiplied;

        mNinePatchChunk = ninePatchChunk;
        mNinePatchInsets = ninePatchInsets;
        if (density >= 0) {
            mDensity = density;
        }

        mNativePtr = nativeBitmap;
        long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
        NativeAllocationRegistry registry = new NativeAllocationRegistry(
            Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
        registry.registerNativeAllocation(this, nativeBitmap);

        if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) {
            sPreloadTracingNumInstantiatedBitmaps++;
            sPreloadTracingTotalBitmapsSize += nativeSize;
        }
    }
    ..........
    private static native void nativeReconfigure(long nativeBitmap, int width, int height,
                                                 int config, boolean isPremultiplied);
    private static native int nativeGetPixel(long nativeBitmap, int x, int y);
    private static native void nativeGetPixels(long nativeBitmap, int[] pixels,
                                               int offset, int stride, int x, int y,
                                               int width, int height);
    ..........
}

这里就不带大家具体去看Bitmap的创建了,他的创建是在native层的,有兴趣的自己去看看具体的创建过程。可以看到Bitmap的构造方法注释 called from JNI 说明该方法是给jni层调用的,成员变量mNativePtr 注释 Convenience for JNI access(方便JNI访问)mNativePtr 是在构造方法中赋值的,可以看出这个long类型的值是从jni层传过来的,在仔细的看看Bitmap中的native方法几乎都要传mNativePtr 这个值过去,这是为什么呢?在Btmap Java对象的时候相应的也创建了native Bitmap 对象 然后把nativeBitmap 首地址传递给Java层Btmap ,这样我们要操作java Btmap时其实我们是通过它的native方法将首地址带入方便我们找到创建的nativeBitmap,这样我们实际操作的是nativeBitmap。
在前几篇文章我们提到了opencv图像处理库,之前一直使用到它的c/c++部分,并没有用到它的java层的sdk。现在我们自己简单的写个例子然后的整理梳理一下sdk
之前写的代码都是这样的,一个模糊图像功能,一个是给图像做掩膜,写了两个对应native的方法

extern "C"
JNIEXPORT jobject JNICALL
Java_com_youyangbo_sdk_BitmapUtils_blur(JNIEnv *env, jclass type, jobject bitmap) {

    Mat src;
    bitmap2Mat(env, src, bitmap);
    // bgr
    Mat bgr;
    cvtColor(src, bgr, COLOR_BGRA2BGR);

    Mat kernel = Mat::ones(Size(15, 15), CV_32FC1) / (15 * 15);

    Mat dst;
    filter2D(bgr, dst, src.depth(), kernel);

    mat2Bitmap(env, dst, bitmap);
    return bitmap;

}

extern "C"
JNIEXPORT jobject JNICALL
Java_com_youyangbo_sdk_BitmapUtils_mask(JNIEnv *env, jclass type, jobject bitmap) {
    Mat src;

    bitmap2Mat(env, src, bitmap);

    Mat kernel = Mat::zeros(Size(3, 3), CV_32FC1);
    kernel.at<float>(0, 1) = -1;
    kernel.at<float>(1, 0) = -1;
    kernel.at<float>(1, 1) = 5;
    kernel.at<float>(1, 2) = -1;
    kernel.at<float>(2, 1) = -1;;
    Mat res;
    filter2D(src, res, src.depth(), kernel);
    jobject new_bitmap = BitmapUtils::createBitmap(env, res.cols, res.rows, res.type());

    mat2Bitmap(env, res, new_bitmap);


    return new_bitmap;

}

在上面的两个功能中我们都调用到了bitmap2Mat() filter2D() mat2Bitmap()这个函数这里我们就可以做到功能的拆分,提供一个java层的bitmap与Mat相互转化的工具类,再提供一个java层filter2D()的工具类,那么我们就差了nativeMat转化成java层的Mat对象。 我们做到nativeMat转化成java层的Mat 那么功能代码就会转移到java层去做了,之后又类似功能扩展不需要动c/c++层在java就可以完成。
接下里看看nativeMat-->javaMat

/**
 * 对应opencv Mat.cpp 对象
 */
public class Mat {

    public final long mNativePtr;  //保存native 对象地址
    int rows;
    int cols;
    Type type;

    public Mat() {
        mNativePtr = nMat();
    }

    public Mat(int rows, int cols, Type type) {
        this.rows = rows;
        this.cols = cols;
        this.type = type;
        mNativePtr = nMatIII(rows, cols, type.value);
    }

    private native long nMatIII(int rows, int cols, int value);

    private native long nMat();


    public void put(int row, int col, int value) {
        nput(mNativePtr,row,col,value);
    }

    private native void nput(long nativePtr, int row, int col, int value);


    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        nDelte(mNativePtr);
    }

    private native void nDelte(long mNativePtr);
}

jni层构建c层Mat对象 并操作Mat

#include "bitmap/native_map.h"
#include <opencv2/opencv.hpp>


using namespace cv;

/**
 * 创建Mat
 */
extern "C"
JNIEXPORT jlong JNICALL
Java_com_youyangbo_sdk_opencv_Mat_nMat(JNIEnv *env, jobject instance) {
    Mat *mat = new Mat();
    return reinterpret_cast<jlong> (mat);

}

extern "C"
JNIEXPORT jlong JNICALL
Java_com_youyangbo_sdk_opencv_Mat_nMatIII(JNIEnv *env, jobject instance, jint rows, jint cols,
                                          jint value) {
    Mat *mat = new Mat(rows, cols, value);
    return reinterpret_cast<jlong> (mat);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_sdk_opencv_Mat_nput(JNIEnv *env, jobject instance, jlong nativePtr, jint row,
                                       jint col, jint value) {

    Mat *mat_ptr = reinterpret_cast<Mat *>(nativePtr);
    mat_ptr->at<float>(row, col) = value;

}

extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_sdk_opencv_Mat_nDelte(JNIEnv *env, jobject instance, jlong nativePtr) {

    Mat *mat_ptr = reinterpret_cast<Mat *>(nativePtr);

    if (mat_ptr != NULL) {
        delete mat_ptr;
        mat_ptr = NULL;
    }
}

Mat与Bitmap转化java层工具类的

public class OpencvUtils {
    public static void mat2Bitmap(Mat mat, Bitmap bitmap) {
        nmat2Bitmap(mat.mNativePtr, bitmap);
    }

    private static native void nmat2Bitmap(long mnativePtr, Bitmap bitmap);

    public static void bitmap2Mat(Bitmap bitmap, Mat mat) {
        nbitmap2Mat(bitmap, mat.mNativePtr);
    }

    private static native void nbitmap2Mat(Bitmap bitmap, long nativePtr);
}
#include "bitmap/NatvieUtils.h"
#include "bitmap/opencvHelp.h"
#include <android/log.h>

using namespace cv;

extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_sdk_opencv_OpencvUtils_nmat2Bitmap(JNIEnv *env, jclass type, jlong mnativePtr,
                                                      jobject bitmap) {


    Mat *mat = reinterpret_cast<Mat *>(mnativePtr);
    
    mat2Bitmap(env, *mat, bitmap);

}


extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_sdk_opencv_OpencvUtils_nbitmap2Mat(JNIEnv *env, jclass type, jobject bitmap,
                                                      jlong nativePtr) {

    Mat *mat = reinterpret_cast<Mat *>(nativePtr);

    bitmap2Mat(env, *mat, bitmap);

}

java层filter2D工具类

public class Imgproc {

    public static void filter2D(Mat src, Mat dst, Mat kernel) {
        nfilter2D(src.mNativePtr, dst.mNativePtr, kernel.mNativePtr);
    }

    private static native void nfilter2D(long mNativePtr, long mNativePtr1, long mNativePtr2);
}

jni层filter2D工具类

#include "bitmap/NImgproc.h"
#include <opencv2/opencv.hpp>

using namespace cv;

extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_sdk_opencv_Imgproc_nfilter2D(JNIEnv *env, jclass type, jlong srcPtr,
                                                jlong resPtr1, jlong kenrlPtr2) {

    Mat *src_ptr = reinterpret_cast<Mat *>(srcPtr);
    Mat *res_ptr = reinterpret_cast<Mat *>(resPtr1);
    Mat *knerl_ptr = reinterpret_cast<Mat *>(kenrlPtr2);

    filter2D(*src_ptr, *res_ptr, src_ptr->depth(), *knerl_ptr);


}

基本的封装架子已经有了,但是还存在细节的处理,比如异常处理,内存的释放,答题思路就是,大体思路就是将异常尽量在java层抛出,jni层的异常要抛给java层。
最后我们继续用一个门面模式将调用代码封装起来,这样调用者就会简单


public class Utils {
    public static Bitmap mask(Bitmap bitmap) {
        if (bitmap == null) {
            throw new NullPointerException("bitmap is null");
        }
        Mat mat = new Mat();
        OpencvUtils.bitmap2Mat(bitmap, mat);

        Mat resMat = new Mat();

        Mat knerl = new Mat(3, 3, Type.CV_32FC1);

        knerl.put(0, 0, 0);
        knerl.put(0, 1, -1);
        knerl.put(0, 2, 0);

        knerl.put(1, 0, -1);
        knerl.put(1, 1, 5);
        knerl.put(1, 2, -1);

        knerl.put(2, 0, 0);
        knerl.put(2, 1, -1);
        knerl.put(2, 2, 0);

        Imgproc.filter2D(mat, resMat, knerl);

        Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());

        OpencvUtils.mat2Bitmap(resMat, newBitmap);

        return newBitmap;
    }
}

最后给张Demo的效果图:


掩膜.png

总体来说要做到C/C++库封装成Java层sdk,除了封装的思想之外还是要熟悉c/c++,java,ndk知识,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343