Android NDK开发二 cmake脚本编写

来源:互联网 发布:java action是什么 编辑:程序博客网 时间:2024/05/24 06:55

1 前言

上一篇已经简单的介绍了Cmake的基本使用以及编写规则了,这一篇博客我们简单介绍在NDK开发中常见的一些场景时如何编写cmake

2 NDK中cmake的使用场景

一般在NDK开发中,我们编译so库或者使用外部的so库,cmake的编写规则场景主要有以下几种。

1 cmake 编译单个c/cpp文件为so
这种单个的c/cpp文件一般为JNI层的文件,例如在我们新建一个支持C++的工程中native-lib.cpp:

#include <jni.h>#include <string>extern "C"jstringJava_com_qiyei_essayjoke_MainActivity_stringFromJNI(        JNIEnv *env,        jobject /* this */) {    std::string hello = "Hello from C++";    return env->NewStringUTF(hello.c_str());}

关于这里我们将native-lib.cpp编译成libnative-lib.so,我们只需要在cMakeLists.txt中如下配置:

add_library( # Sets the name of the library.             native-lib             # Sets the library as a shared library.             SHARED             # Provides a relative path to your source file(s).             # Associated headers in the same location as their source             # file are automatically included.             src/main/cpp/native-lib.cpp )

这里add_library就表示添加一个库,native-lib表示库名称,可以随便取,SHARED表示添加的动态库,后面的src/main/cpp/native-lib.cpp表示要编译成native-lib库的源文件

2 使用外部的so库 + c/cpp
这种使用的场景很多,在NDK开发中,我们往往会使用别人现成的so库,我们在编写自己的c/cpp代码时,需要调用别人so库的函数,这个时候的cmake就需要如下编写了。以使用libjpeg.so为例,先来看我们自己的c/cpp代码:
compress_image.h

/** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/7/23. * Version: 1.0 * Description: */#ifndef ESSAYJOKE_COMPRESS_IMAGE_H#define ESSAYJOKE_COMPRESS_IMAGE_H#include <jni.h>#include <string>/** * Log打印 */#define LOG_TAG "jni"#define LOG_W(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)#define LOG_I(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)#define LOG_E(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)#define true 1#define false 0/** * 统一编译方式 */#ifdef __cplusplusextern "C"{#endif#include "jpeg/jpeglib.h"#include "jpeg/cdjpeg.h"        /* Common decls for cjpeg/djpeg applications */#include "jpeg/jversion.h"        /* for version message */#include "jpeg/jconfig.h"JNIEXPORT jint JNICALL        Java_com_qiyei_sdk_util_ImageUtil_jpegCompressBitmap(JNIEnv *env, jclass type, jobject bitmap,jint quality, jstring path_);#ifdef __cplusplus};#endif#endif //ESSAYJOKE_COMPRESS_IMAGE_H

compress_image.cpp

/** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/7/23. * Version: 1.0 * Description: */#include "compress_image.h"#include <string.h>#include <android/bitmap.h>#include <android/log.h>#include <stdio.h>#include <setjmp.h>#include <math.h>#include <stdint.h>#include <time.h>/** * 定义BYTE 类型为u_int8_t */typedef u_int8_t BYTE;char *error;/** * error 结构体 */struct  error_mgr {    struct jpeg_error_mgr pub;    jmp_buf setjmp_buffer;};/** * 结构体类型定义 */typedef struct error_mgr *error_ptr;/** * 错误退出函数 */METHODDEF(void) error_exit(j_common_ptr info){    //获取j_common_ptr中的error    error_ptr  err = (error_ptr)info->err;    //调用error中的output_message输出打印信息    (*info->err->output_message)(info);    error = (char *)err->pub.jpeg_message_table[err->pub.msg_code];    LOG_E("jpeg error_exit,jpeg_message_table[%d]:%s",err->pub.msg_code,err->pub.jpeg_message_table[err->pub.msg_code]);    //    longjmp(err->setjmp_buffer,1);}/** * jpeg压缩图片 */jint compress_jpeg(BYTE * data,int width,int height,int quality,jboolean optimize,char * file_name){    struct jpeg_compress_struct jcs;    //当读完整个文件的时候就会回调my_error_exit这个退出方法。    struct error_mgr jpeg_err;    jcs.err = jpeg_std_error(&jpeg_err.pub);    jpeg_err.pub.error_exit = error_exit;    //setjmp是洗衣歌系统级函数,是一个回调    if (setjmp(jpeg_err.setjmp_buffer)){        return -1;    }    //初始化jcs结构体    jpeg_create_compress(&jcs);    //打开输出文件 wb 可写 rb 可读,最终会把压缩的图像保存到该文件中    FILE *f = fopen(file_name,"wb");    if ( f == NULL){        return -1;    }    //设置jcs的文件路径以及宽高    jpeg_stdio_dest(&jcs,f);    jcs.image_width = width;    jcs.image_height = height;    jcs.arith_code = false; //true 算数编码,flase 霍夫曼编码    ///* 颜色的组成 rgb,三个 # of color components in input image */    int nComponent = 3;    jcs.input_components = nComponent; //颜色组成RGB    jcs.in_color_space = JCS_RGB;    jpeg_set_defaults(&jcs);    //是否采用霍夫曼编码    jcs.optimize_coding = optimize;    //设置质量    jpeg_set_quality(&jcs,quality,true);    //开始压缩    jpeg_start_compress(&jcs,TRUE);    JSAMPROW row_pointer[1];    int row_stride = jcs.image_width * nComponent;    //依次按照每一行循环扫描    while (jcs.next_scanline < jcs.image_height){        //得到一行的首地址。然后写入        row_pointer[0] = &data[jcs.next_scanline * row_stride];        jpeg_write_scanlines(&jcs,row_pointer,1);    }    //压缩结束    jpeg_finish_compress(&jcs);    //销毁回收内存    jpeg_destroy_compress(&jcs);    //关闭文件    fclose(f);    return 0;}JNIEXPORT jint JNICALLJava_com_qiyei_sdk_util_ImageUtil_jpegCompressBitmap(JNIEnv *env, jclass type, jobject bitmap,                                                 jint quality, jstring path_) {    //1. 解析RGB    //1.1 获取bitmap信息,w,h.format    AndroidBitmapInfo bitmapInfo;    //java你调用完方法往往返回的是对象,而C往往是参数    AndroidBitmap_getInfo(env,bitmap,&bitmapInfo);    //获取bitmap信息    int bitmap_width = bitmapInfo.width;    int bitmap_height = bitmapInfo.height;    int bitmap_format = bitmapInfo.format;    //对于不是ARGB8888格式的图片不支持    if (bitmap_format != ANDROID_BITMAP_FORMAT_RGBA_8888){        return -1;    }    LOG_W("bitmap,width = %d,height = %d",bitmap_width,bitmap_height);    //1.2 把bitmap解析到数组中,数组中保存的是rgb --> YCbCr    //1.2.2 锁定画布    BYTE * pixel_color;    //将bitmap数组导出到pixel_color中    AndroidBitmap_lockPixels(env,bitmap,(void **) &pixel_color);    //1.2.2 解析数据    BYTE *data;    BYTE r,g,b;    //申请内存,因为不需要保存A通道,所以只需要申请 bitmap_width * bitmap_height * 3    data = (BYTE *) malloc(bitmap_width * bitmap_height * 3);    //数组指针指向首地址,因为这块内存需要释放,所以先保存下    BYTE * head = data;    int i = 0;    int j = 0;    int color;    for (i = 0 ; i < bitmap_width ;i++){        for (j = 0;j < bitmap_height;j++){            color = *((int *)pixel_color);            //将rgb取出来 ARGB 每个字节占八位            r = (color & 0x00FF0000) >> 16;            g = (color & 0x0000FF00) >> 8;            b = (color & 0x000000FF) >> 0;            //保存到data中            *data = b;            *(data + 1) = g;            *(data + 2) = r;            data = data + 3;            // 一个像素点包括argb四个值,每+4下就是取下一个像素点            pixel_color = pixel_color + 4;        }    }    //1.2.3 解锁画布    AndroidBitmap_unlockPixels(env,bitmap);    //1.2.4 还差一个参数,jstring -> char*    char * file_name = (char *)env->GetStringUTFChars(path_,NULL);    int result = compress_jpeg(head,bitmap_width,bitmap_height,quality, true,file_name);    //释放内存    free(head);    env->ReleaseStringUTFChars(path_,file_name);    //释放bitmap,调用bitmap的recycle    //找到Bitmap类    jclass clazz = env->GetObjectClass(bitmap);    jmethodID jmethod_ID = env->GetMethodID(clazz,"recycle","()V");    //调用Bitmap的recycle()方法释放对象    env->CallVoidMethod(bitmap,jmethod_ID);    LOG_W("result = %d",result);    //4 返回结果    return result;}

比如,在compress_image.cpp中我们使用大量的libjpeg.so库中的方法,例如jpeg_set_quality,jpeg_start_compress等。这个时候我们的cmake一般来说需要做如下修改:

#设置生成的so动态库最后输出的路径set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})add_library( # Sets the name of the library.             compressimg             # Sets the library as a shared library.             SHARED             # Provides a relative path to your source file(s).             # Associated headers in the same location as their source             # file are automatically included.             src/main/cpp/compress_image.cpp )# 添加libjpeg.so依赖库add_library(jpeg SHARED IMPORTED)#设置libjpeg.so依赖库路径set_target_properties(  jpeg  PROPERTIES IMPORTED_LOCATION  ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpeg.so)

解释如下:
1 还是添加compressimg.so,源文件为src/main/cpp/compress_image.cpp
2 添加jpeg.so,因为是导入别人的,因此是IMPORTED
3 设置查找jpeg.so的路径 “${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpeg.so”,其中${PROJECT_SOURCE_DIR}表示工程源文件目录,与src文件这一目录同级,${ANDROID_ABI}表示armeabi, armeabi-v7a,arm64-v8a等目录
4 最后将jpeg.so库链接到compressimg.so中

3 多个c/cpp文件编译成so库
这种情况也比较多见,一般来说如果我们下载了某个so的源码,或者我们编写了多个c/cpp文件 ,需要将这些源文件编译成一个so库,这个情况实际和第一种差不多。
例如,我们需要将以下源文件编译成so库
这里写图片描述

这个时候我们的cmake如下:

#配置头文件路径#include_directories(${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/)#添加libbspatch 库add_library( # Sets the name of the library.             bspatch             SHARED             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/huffman.c             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/randtable.c             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/crctable.c             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/blocksort.c             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/bzip2.c             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/compress.c             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/decompress.c             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/bzlib.c             ${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/bspatch.c)

解释如下:
1 include_directories添加头文件,主要告诉cmake,去哪个目录下找对应的头文件
2 由于我这里不需要将${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/文件夹下所有的c/cpp文件编译成so库,因此我将文件单独添加,实际中不建议这么做,可使用变量将某个路径全部编译,详情请见下一节内容

3 cmake常用脚本

在编写cmake的过程中,我们往往会发现一些特别有用的脚本或者变量,现在记录下来,以备不时之需,也会做不定时的更新。

1 cmake中常用的变量
STATIC: 表示静态的.a的库。
SHARED: 表示.so的库,动态库
CMAKE_LIBRARY_OUTPUT_DIRECTORY: so库或者静态库的最后输出路径
${ANDROID_NDK}: Android Studio已经定义好的变量,可以直接使用它指定的是NDK源代码的根目录
${PROJECT_SOURCE_DIR}: 表示工程源文件目录,与src文件这一目录同级
${ANDROID_ABI}: 表示armeabi, armeabi-v7a,arm64-v8a等目录
${CMAKE_SOURCE_DIR}: 表示CMakeLists.txt的当前文件夹路径。

2 cmake中常用的脚本

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY path)

设置生成的so动态库最后输出的路径

include_directories(path)

配置头文件搜索路径

#添加源文件路径aux_source_directory(${PROJECT_SOURCE_DIR}/src/main/cpp/bsdiff/ CORE_SRC_LIST)list(APPEND SRC_LIST ${CORE_SRC_LIST})

添加源文件路径,这样${SRC_LIST}就表示src/main/cpp/bsdiff/目录了

set_target_properties(  xxx  PROPERTIES IMPORTED_LOCATION  path)

设置libxxx.so依赖库的路径

原创粉丝点击