您的位置:首页 > 移动开发 > Android开发

Android Studio2.2配置MakeList使用cmake编译c文件

2017-02-21 21:59 441 查看
 初次使用cmake来编译c文件在Android项目中,那个快感能传递到骨髓里,欲罢不能.只能说谁用谁知道.如果还没有使用,请赶快用一次,初次体验一共三部:

第一:studio升级到2.2以后的版本,安装NDK,cmake工具

第二:创建新项目勾选 Include C++ Support 选项。

第三:一路下一步,安装运行项目

studio会默认帮你创建一个写好的demo更加详细的请参考:http://blog.csdn.net/wl9739/article/details/52607010或者google官网:https://developer.android.com/ndk/guides/build.html

我今天以自己学到的使用c代码压缩图片为例子,用cmake来编译这个项目来分享下学到的东西,如果使用cmake来编译项目一共有两种实现方式:

第一种就是上面那样,创建项目时勾选Include C++ Support 让系统生成配置,然后自己再添加c代码,

第二种是不是新建的项目,这时候要引用c代码,那就要自己去配置文件,本文就要讲后面这种实现方式.

先看看效果图:



这两张是从相册倒出来的,压缩前压缩后的



右图原图1.67M   左图压缩后57.54K不到原来的1/10,不放大的情况,用肉眼很难分辨出来差别.

要把这个c代码图片压缩集成到项目中需要四部:

第一:准备2.2以上版本studio,配置安装ndk和cmake工具



第二:导入c++文件,修改方法名



  在项目module目录下新建cpp文件夹,一定要注意不要建错地方了,最终绝对路径是app\src\main\cpp,把native-lib.cpp这个文件导入到你自己的cpp文件夹中

native-lib.cpp文件如下

#include <stdio.h>
#include <string>
#include <android/bitmap.h>
#include <android/log.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>

extern "C" {
#include "jpeglib.h"
#include "jversion.h"
#include "cdjpeg.h"
#include "android/config.h"

#define LOG_TAG "jni"
#define LOGW(...)  __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#define true 1
#define false 0

typedef uint8_t BYTE;
char *error;
struct my_error_mgr {
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};

typedef struct my_error_mgr *my_error_ptr;

METHODDEF(void)
my_error_exit(j_common_ptr cinfo) {
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message)(cinfo);
error = (char *) myerr->pub.jpeg_message_table[myerr->pub.msg_code];
// LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
//  LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
//  LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
longjmp(myerr->setjmp_buffer, 1);
}

int generateJPEG(BYTE *data, int w, int h, int quality,
const char *outfilename, jboolean optimize) {

//jpeg的结构体,保存的比如宽、高、位深、图片格式等信息,相当于java的类
struct jpeg_compress_struct jcs;

//当读完整个文件的时候就会回调my_error_exit这个退出方法。setjmp是一个系统级函数,是一个回调。
struct my_error_mgr jem;
jcs.err = jpeg_std_error(&jem.pub);
jem.pub.error_exit = my_error_exit;
if (setjmp(jem.setjmp_buffer)) {
return 0;
}

//初始化jsc结构体
jpeg_create_compress(&jcs);
//打开输出文件 wb:可写byte
FILE *f = fopen(outfilename, "wb");
if (f == NULL) {
return 0;
}
//设置结构体的文件路径
jpeg_stdio_dest(&jcs, f);
jcs.image_width = w;//设置宽高
jcs.image_height = h;
//	if (optimize) {
//		LOGI("optimize==ture");
//	} else {
//		LOGI("optimize==false");
//	}

//看源码注释,设置哈夫曼编码:/* TRUE=arithmetic coding, FALSE=Huffman */
jcs.arith_code = false;
int nComponent = 3;
/* 颜色的组成 rgb,三个 # of color components in input image */
jcs.input_components = nComponent;
//设置结构体的颜色空间为rgb
jcs.in_color_space = JCS_RGB;
//	if (nComponent == 1)
//		jcs.in_color_space = JCS_GRAYSCALE;
//	else
//		jcs.in_color_space = JCS_RGB;

//全部设置默认参数/* Default parameter setup for compression */
jpeg_set_defaults(&jcs);
//是否采用哈弗曼表数据计算 品质相差5-10倍
jcs.optimize_coding = optimize;
//设置质量
jpeg_set_quality(&jcs, quality, true);
//开始压缩,(是否写入全部像素)
jpeg_start_compress(&jcs, TRUE);

JSAMPROW row_pointer[1];
int row_stride;
//一行的rgb数量
row_stride = jcs.image_width * nComponent;
//一行一行遍历
while (jcs.next_scanline < jcs.image_height) {
//得到一行的首地址
row_pointer[0] = &data[jcs.next_scanline * row_stride];

//此方法会将jcs.next_scanline加1
jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:写入的行数
}
jpeg_finish_compress(&jcs);//结束
jpeg_destroy_compress(&jcs);//销毁 回收内存
fclose(f);//关闭文件

return 1;
}

/**
* byte数组转C的字符串
*/
char *jstrinTostring(JNIEnv *env, jbyteArray barr) {
char *rtn = NULL;
jsize alen = env->GetArrayLength(barr);
jbyte *ba = env->GetByteArrayElements(barr, 0);
if (alen > 0) {
rtn = (char *) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}

JNIEXPORT jstring JNICALL
Java_com_example_c_1demo_NativeUtil_compressBitmap__Landroid_graphics_Bitmap_2IIILbyte_3_093_2Z(
JNIEnv *env, jclass type, jobject bit, jint w, jint h, jint quality,
jbyteArray fileNameBytes_, jboolean optimize) {
jbyte *fileNameBytes = env->GetByteArrayElements(fileNameBytes_, NULL);

// TODO

env->ReleaseByteArrayElements(fileNameBytes_, fileNameBytes, 0);

return env->NewStringUTF("1");
}

JNIEXPORT jstring JNICALL
Java_com_example_c_1demo_NativeUtil_compressBitmaps(JNIEnv *env, jclass type,
jobject bitmapcolor, jint w, jint h,
jint quality,
jbyteArray fileNameStr,
jboolean optimize) {
jbyte *fileNameBytes = env->GetByteArrayElements(fileNameStr, NULL);

BYTE *pixelscolor;
//1.将bitmap里面的所有像素信息读取出来,并转换成RGB数据,保存到二维byte数组里面
//处理bitmap图形信息方法1 锁定画布
// AndroidBitmap_lockPixels(env, bitmapcolor, (void **) &pixelscolor);
AndroidBitmap_lockPixels(env,bitmapcolor,(void **)&pixelscolor);
//2.解析每一个像素点里面的rgb值(去掉alpha值),保存到一维数组data里面
BYTE *data;
BYTE r, g, b;
data = (BYTE *) malloc(w * h * 3);//每一个像素都有三个信息RGB
BYTE *tmpdata;
tmpdata = data;//临时保存data的首地址
int i = 0, j = 0;
int color;
for (i = 0; i < h; ++i) {
for (j = 0; j < w; ++j) {
//解决掉alpha
//获取二维数组的每一个像素信息(四个部分a/r/g/b)的首地址
color = *((int *) pixelscolor);//通过地址取值
//0~255:
//			a = ((color & 0xFF000000) >> 24);
r = ((color & 0x00FF0000) >> 16);
g = ((color & 0x0000FF00) >> 8);
b = ((color & 0x00000
c9fc
0FF));
//改值!!!----保存到data数据里面
*data = b;
*(data + 1) = g;
*(data + 2) = r;
data = data + 3;
//一个像素包括argb四个值,每+4就是取下一个像素点
pixelscolor += 4;
}
}
//处理bitmap图形信息方法2 解锁
AndroidBitmap_unlockPixels(env, bitmapcolor);
char *fileName = jstrinTostring(env, fileNameStr);
//调用libjpeg核心方法实现压缩
int resultCode = generateJPEG(tmpdata, w, h, quality, fileName, optimize);
if (resultCode == 0) {
jstring result = env->NewStringUTF("-1");
return result;
}
env->ReleaseByteArrayElements(fileNameStr, fileNameBytes, 0);

return env->NewStringUTF("11");
}

}


然后导入NativeUtil工具类

package com.example.c_demo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
* Created by Administrator on 2017/2/11 0011.
*/

public class NativeUtil {

static {
System.loadLibrary("native-lib");
System.loadLibrary("jpegbither");
}

public static void compressBitmap(Bitmap image, String filePath) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 20;
NativeUtil.saveBitmap(image, options, filePath, true);
}

public static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) {
// String s=  compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
compressBitmaps(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
}

public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
boolean optimize);

public static native String compressBitmaps(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
boolean optimize);

public static void compressBitmap(String curFilePath, String targetFilePath) {
// 最大图片大小 150KB
int maxSize = 150;
//根据地址获取bitmap
Bitmap result = getBitmapFromFile(curFilePath);
//        ByteArrayOutputStream baos = new ByteArrayOutputStream();
//        // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int quality = 45;
//        result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
//        // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
//        while (baos.toByteArray().length / 1024 > maxSize) {
//            // 重置baos即清空baos
//            baos.reset();
//            // 每次都减少10
//            quality -= 10;
//            // 这里压缩quality,把压缩后的数据存放到baos中
//            result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
//        }
// JNI保存图片到SD卡 这个关键
NativeUtil.saveBitmap(result, quality, targetFilePath, true);
// 释放Bitmap
if (!result.isRecycled()) {
result.recycle();
}

}

/**
* 通过文件路径读获取Bitmap防止OOM以及解决图片旋转问题
*
* @param filePath
* @return
*/
public static Bitmap getBitmapFromFile(String filePath) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;//只读边,不读内容
BitmapFactory.decodeFile(filePath, newOpts);
int w = newOpts.outWidth;
int h = newOpts.outHeight;
// 获取尺寸压缩倍数
newOpts.inSampleSize = NativeUtil.getRatioSize(w, h);
newOpts.inJustDecodeBounds = false;//读取所有内容
newOpts.inDither = false;
newOpts.inPurgeable = true;
newOpts.inInputShareable = true;
newOpts.inTempStorage = new byte[32 * 1024];
Bitmap bitmap = null;
File file = new File(filePath);
FileInputStream fs = null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
if (fs != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fs.getFD(), null, newOpts);
//旋转图片
int photoDegree = readPictureDegree(filePath);
if (photoDegree != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(photoDegree);
// 创建新的图片
bitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fs != null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}

/**
* 计算缩放比
*
* @param bitWidth  当前图片宽度
* @param bitHeight 当前图片高度
* @return int 缩放比
* @author XiaoSai
* @date 2016年3月21日 下午3:03:38
* @version V1.0.0
*/
public static int getRatioSize(int bitWidth, int bitHeight) {
// 图片最大分辨率
int imageHeight = 1280;
int imageWidth = 768;
// 缩放比
int ratio = 1;
// 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
//        if (bitWidth > bitHeight && bitWidth > imageWidth) {//如果是横图
//            // 如果图片宽度比高度大,以宽度为基准
//            ratio = bitWidth / imageWidth;
//        } else if (bitWidth < bitHeight && bitHeight > imageHeight) {//如果是竖图
//            // 如果图片高度比宽度大,以高度为基准
//            ratio = bitHeight / imageHeight;
//        }

int heightScale = bitWidth/imageHeight;
int widthScale = bitHeight/imageWidth;
// 三元运算符  取两者之间的大值
ratio = heightScale>widthScale?heightScale:widthScale;
// 最小比率为1
if (ratio <= 0)
ratio = 1;
return ratio;
}
 /**
     *
     * 读取图片属性:旋转的角度
     * @param path 图片绝对路径
     * @return degree旋转的角度
     */

    public static int readPictureDegree(String path) {
        int degree = 0;
        try {
            ExifInterface exifInterface = new ExifInterface(path);
            int orientation = exifInterface.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return degree;
    }

}


这个时候会报错,cpp文件会报错是因为没有头文件,没有类库,nativeUtily也会报错是因为cpp文件中找不到方法

在nativeUtil那两个报错的方法上alt+enter生成方法,然后把在cpp文件中生成的方法名替换掉原来老的Java_com_example_c_1demo_NativeUtil_compressBitmaps(),这个nativy方法名 的构成是java_包名_类名_方法名的格式,换完保存的两个方法名然后要导入类库,cpp报错先不管



第三:导入类库,导入头文件

在project视图下复制lib文件夹下的头文件和类库到你自己的项目



第四:配置CMakelist.text,配置build.grandle

CMakelist.text原封不动的复制到app的根目录下



# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

cmake_minimum_required(VERSION 3.4.1)
#引用已近有的库
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 )
#资源文件夹的位置libs
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../libs)
#导入类库,只是作为引用,不编译
add_library( jpegbither
SHARED
IMPORTED )
#引用目标类库是本地类库位置在libs/armeabi-v7a/libjpegbither.so
set_target_properties( jpegbither
PROPERTIES IMPORTED_LOCATION
../../../../libs/armeabi-v7a/libjpegbither.so )
#添加类库位置在src/main/cpp/native-lib.cpp需要编译
add_library(native-lib
SHARED
src/main/cpp/native-lib.cpp )

#引入头文件目录位置
include_directories(libs/jpeg)
#将预构建库与你本地库相关联
target_link_libraries( # Specifies the target library.
native-lib jpegbither  jnigraphics

# Links the target library to the log library
# included in the NDK.
${log-lib} )


配置build.grandle

红色为添加到你自己的项目中的配置,然后sync同步项目,这时候就不会报错了.

apply plugin: 'com.android.application'

android {
tasks.whenTaskAdded { task ->
if (task.name.contains("lint")//如果instant run不生效,把clean这行干掉
||task.name.equals("clean")//项目中有用到aidl则不可以舍弃这个任务
||task.name.contains("Aidl")//用不到测试的时候就可以先关闭
||task.name.contains("mockableAndroidJar")
||task.name.contains("UnitTest")
||task.name.contains("AndroidTest")//用不到NDK和JNI的也关闭掉
|| task.name.contains("Ndk")
|| task.name.contains("Jni")
) {
task.enabled = false
}
}
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.example.c_demo"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { //讲这个配置复制到你的grandle文件
cmake {
cppFlags ""
}
}
ndk {//创建过滤器,只生成相关cpu相关的so库
abiFilters "armeabi", "armeabi-v7a"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {//添加cmake文件
cmake {
path "CMakeLists.txt"
}
}
sourceSets {//添加资源文件
main {
jniLibs.srcDirs = ['libs']
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.1'
testCompile 'junit:junit:4.12'
}
之后只需在MainActivity中要调用NativeUtil.compressBitmap(String curFilePath, String targetFilePath)传入图片的路径和需要保存的位置就行了.

new Thread(){
@Override
public void run() {
NativeUtil.compressBitmap(path, path1);
getContentResolver().notifyChange(Uri.parse("content://pic"), null);
}
}.start();


项目demo下载:http://download.csdn.net/download/qq_35599978/9760191
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: