Android NDK开发二 cmake脚本编写

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"
jstring
Java_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 __cplusplus
extern "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 JNICALL
Java_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依赖库的路径

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值