【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )

简介: 【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )

文章目录

一、Bitmap 图像数据处理

二、Java 层 Bitmap 对象转为 JNI 层 bitmap 对象

三、获取 bitmap 中的图像数据

四、过滤 bitmap 中的图像数据 ( 获取 RGB 数据 剔除 A 通道数据 )

五、释放资源

六、Bitmap 图像数据处理



在上一篇博客 【Android 内存优化】libjpeg-turbo 函数库交叉编译与使用 ( 交叉编译脚本编写 | 函数库头文件拷贝 | 构建脚本配置 | Android Studio 测试函数库 ) 中 对 libjpeg-turbo 函数库进行了交叉编译 , 拷贝了相应的头文件和静态库到 Android Studio 项目中 , 并配置了 CMakeList.txt 构建脚本 , 和 build.gradle 构建脚本 , 本篇博客中开始进行代码编写 ;






一、Bitmap 图像数据处理


Bitmap 图像数据处理 :



① 获取 Bitmap 图像对象 : Java 传递到 JNI 层的是 jobject 对象 , 需要将其转为 JNI 中的 bitmap 对象 ;


② 数据提取 : 从 bitmap 图像中提取 RGB 像素值 , 也就是剔除 ALPHA 通道 ( 透明度 ) 的数据 ;


③ 使用 libjpeg-turbo 压缩图片 : 调用 libjpeg-turbo 函数库 , 对上述提取的图片 RGB 像素数据进行压缩 ;


④ 释放资源 : 图片压缩完毕后 , 释放相关资源 ;






二、Java 层 Bitmap 对象转为 JNI 层 bitmap 对象


1. Bitmap 信息 : 在 AndroidBitmapInfo 结构体中 , 封装了图像宽度 , 图像高度 , 像素格式等信息 ;


/** Bitmap info, see AndroidBitmap_getInfo(). */
typedef struct {
    /** 图像像素宽度. */
    uint32_t    width;
    /** 图像像素高度. */
    uint32_t    height;
    /** 每行字节数. */
    uint32_t    stride;
    /** 像素格式. See {@link AndroidBitmapFormat} */
    int32_t     format;
    /** 保留位. */
    uint32_t    flags;      // 0 for now
} AndroidBitmapInfo;


2. 获取 Bitmap 信息 : 调用 bitmap.h 中的 AndroidBitmap_getInfo 方法 , 可以从 jbitmap 中获取对应的信息 ;


int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
                          AndroidBitmapInfo* info);


3. 代码示例 :


   // 声明 位图信息, 该变量作为返回值使用

// 声明 位图信息, 该变量作为返回值使用
    // 引用自 bitmap.h
    AndroidBitmapInfo info;
    // 从 bitmap 中获得信息位图信息 AndroidBitmapInfo
    AndroidBitmap_getInfo(env, jbitmap, &info);

 



三、获取 bitmap 中的图像数据


调用 AndroidBitmap_lockPixels 方法 , 即可从 Java 的 Bitmap 对象中获取数据的首地址 ; 向该函数中传入一个二维指针 , 该二维指针参数作为返回值使用 , 该二维指针最终指向的内存就是图像数据内存 ;



1. AndroidBitmap_lockPixels 函数作用 : 从给定 Java Bitmap 对象中 , 获取其对应的像素数据地址 ; 锁定可以保证像素数据内存是固定不变的 , 直到调用解除锁定方法 , 清除相关数据 ; 该方法必须与 AndroidBitmap_unlockPixels 方法成对使用 , 之后 addrPtr 地址不应该再被使用到 ; 如果执行成功 , *addrPtr 会指向图像像素数据的首地址 , 如果方法失败 , 那么该二维指针是无效的指针 ;




2. AndroidBitmap_lockPixels 函数原型 :



① JNIEnv* env 参数 : 注意这里用到了 JNIEnv* env 参数 , 主线程调用可以直接使用, 子线程调用的话 , 需要使用 JavaVM 调用 AttachCurrentThread 方法 , 传入 JNIEnv 指针 , 然后该 JNIEnv 就是线程对应的 JNI 环境 , 使用完毕后解除绑定 ;


参考 【Android NDK 开发】JNI 线程 ( JNI 线程创建 | 线程执行函数 | 非 JNI 方法获取 JNIEnv 与 Java 对象 | 线程获取 JNIEnv | 全局变量设置 ) 博客 ;


② jobject jbitmap 参数 : Java 中的 Bitmap 对象 ;


③ void** addrPtr 参数 : 二维指针 , 执行成功后指向图像像素数据的首地址 , 同时用于当做返回值 , 让用户可以调用到像素数据 ;


int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);



3. 代码示例 :


 

// 该类型最终类型是 unsigned char, 相当于 Java 中的 byte
    // 这是个 byte 指针, 指向一个数组
    // 此处作为返回值使用
    uint8_t *addrPtr;
    // 注意该获取的信息中包含透明度信息, 像素格式是 ARGB
    AndroidBitmap_lockPixels(env, jbitmap, (void **) &addrPtr);





四、过滤 bitmap 中的图像数据 ( 获取 RGB 数据 剔除 A 通道数据 )


1. 数据过滤需求 : 之前已经获取到了图像数据 , 存储在了 addrPtr 指针中 , 现在需要将 RGB 数据取出, 剔除 ALPHA 透明度通道数据 , 只保留 RGB 通道数据 ;



2. 两块内存 : uint8_t* addrPtr 指针指向的内存是源数据 , uint8_t* data 指针指向的的数据是目标数据 , 最终要压缩的数据是 data 目标数据 ;



3. 像素格式 : 源数据中存储的 BGRA 像素格式的数据 , 目标数据中存储的是 BGR 像素格式的数据 ;



4. 循环像素数据 : 开启循环 , 直接循环遍历每个像素点 , 注意像素点存放格式是 BGRA , 然后将数据存储到另一块内存中 , 存储顺序是 BGR ; 注意每次循环后 , 都需要移动对应的指针 ;


   // JPEG 格式的图片, 没有透明度信息, 像素格式是 RGB

// JPEG 格式的图片, 没有透明度信息, 像素格式是 RGB
    // 这里需要去掉透明度信息
    // 获取图片的像素宽度
    int width = info.width;
    // 获取图片的像素高度
    int height = info.height;
    // 存储 RGB 数据
    uint8_t* data = (uint8_t *) malloc(width * height * 3);
    // data 指针在后续发生了移动, 这里保存下初始的指针, 方便在之后释放该指针
    uint8_t* temp = data;
    // JPEG 像素中的 RGB 三原色, 红绿蓝
    uint8_t red, green, blue;
    // 遍历从 Bitmap 内存 addrPtr 中读取 BGRA 数据, 然后向 data 内存存储 BGR 数据中
    for(int i = 0; i < height; i++){
        for (int j = 0; j < width; ++j) {
          // 处理 i 行 j 列像素点信息 
            // 在 Bitmap 中内存存储序列是 BGRA
            blue = *( addrPtr );
            green = *( addrPtr + 1 );
            red = *( addrPtr + 2 );
            // libturbojpeg 中 JPEG 图像内存排列格式是 BGR
            *data = blue;
            *( data + 1 ) = green;
            *( data + 2 ) = red;
            // 移动 data 指针
            data += 3;
            //移动 addrPtr 指针, 为下一次读取数据做准备
            addrPtr +=4;
        }
    }// 截止到此处, 已经读取出 JPEG 图片所需的数据, 在 data 指针中

 




五、释放资源


之前还有个步骤是压缩 jpeg 格式图片 , 这个过程比较复杂单开一个博客讲解 , 该章节讲解压缩完毕后的内存释放操作 ;



1. 锁定 / 解锁 像素数据 : AndroidBitmap_unlockPixels 方法与 AndroidBitmap_lockPixels 方法成对使用 , 表示之后不再需要使用 Bitmap 对象的数据了 ;



2. 释放压缩数据 : 释放掉存储要压缩的 JPEG 图片 RGB 数据的内存 , 此时已经压完毕 , 可以将之前申请的内存都释放掉了 ; 注意之前申请的 data 指针 , 在拷贝数据过程中 , 将该指针移动过了 , 不能释放 data 指针 , 只能释放之前 data 内存申请后的备份指针 , 否则报错 ;



3. 释放字符串 : env->GetStringUTFChars 创建的字符串是局部引用 , 这里需要释放掉 , 及时回收内存是个好习惯 ;


// 解锁
    AndroidBitmap_unlockPixels(env,jbitmap);
    // 注意要释放 temp 指针 , 不要释放成 data 指针, 否则会出错
    free(temp);
    // 释放局部引用, 不释放, GC 也会回收, 但是有延迟
    env->ReleaseStringUTFChars(path, filePath);





六、Bitmap 图像数据处理


GitHub 项目地址 : han1202012/PictureCompression



libjpeg-turbo 压缩 JPEG 代码示例 :


#include <jni.h>
#include <string>
#include <jpeglib.h>
#include <android/bitmap.h>
#include <malloc.h>
#include <android/log.h>
#include <bitset>
#include <iosfwd>
// 声明函数
void compressJpegFile(uint8_t *data, int imageWidth, int imageHeight,
                      jint compressQuality, const char *filename);
extern "C" JNIEXPORT jstring JNICALL
Java_kim_hsl_pc_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    // 测试 libturbojpeg.a 函数库
    jpeg_compress_struct jcs;
    __android_log_print(ANDROID_LOG_INFO, "JPEG", "jpeg_compress_struct jcs = %d", jcs.image_width);
    hello = hello + " , jpeg_compress_struct jcs = " + std::to_string(jcs.image_width);
    return env->NewStringUTF(hello.c_str());
}
/**
 * 图片压缩方法
 */
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_pc_MainActivity_native_1pictureCompress(JNIEnv *env, jobject thiz,
                                                     jobject jbitmap,
                                                     jint quality, jstring path) {
    // 将 Java 字符串转为 C 字符串, 注意这是局部引用
    const char *filePath = env->GetStringUTFChars(path, 0);
    // 声明 位图信息, 该变量作为返回值使用
    // 引用自 bitmap.h
    AndroidBitmapInfo info;
    // 从 bitmap 中获得信息位图信息 AndroidBitmapInfo
    AndroidBitmap_getInfo(env, jbitmap, &info);
    // 该类型最终类型是 unsigned char, 相当于 Java 中的 byte
    // 这是个 byte 指针, 指向一个数组
    // 此处作为返回值使用
    uint8_t *addrPtr;
    // 注意该获取的信息中包含透明度信息, 像素格式是 ARGB
    AndroidBitmap_lockPixels(env, jbitmap, (void **) &addrPtr);
    // JPEG 格式的图片, 没有透明度信息, 像素格式是 RGB
    // 这里需要去掉透明度信息
    // 获取图片的像素宽度
    int width = info.width;
    // 获取图片的像素高度
    int height = info.height;
    //rgb
    uint8_t* data = (uint8_t *) malloc(width * height * 3);
    // data 指针在后续发生了移动, 这里保存下初始的指针, 方便在之后释放该指针
    uint8_t* temp = data;
    // JPEG 像素中的 RGB 三原色, 红绿蓝
    uint8_t red, green, blue;
    // 遍历从 Bitmap 内存 addrPtr 中读取 BGRA 数据, 然后向 data 内存存储 BGR 数据中
    for(int i = 0; i < height; i++){
        for (int j = 0; j < width; ++j) {
            // 在 Bitmap 中内存存储序列是 BGRA
            blue = *( addrPtr );
            green = *( addrPtr + 1 );
            red = *( addrPtr + 2 );
            // libturbojpeg 中 JPEG 图像内存排列格式是 BGR
            *data = blue;
            *( data + 1 ) = green;
            *( data + 2 ) = red;
            // 移动 data 指针
            data += 3;
            //移动 addrPtr 指针, 为下一次读取数据做准备
            addrPtr +=4;
        }
    }// 截止到此处, 已经读取出 JPEG 图片所需的数据, 在 data 指针中
    // 将 data 指针中的数据压缩到 JPEG 格式图片中
    compressJpegFile(temp, width, height, quality, filePath);
    // 解锁
    AndroidBitmap_unlockPixels(env,jbitmap);
    // 注意要释放 temp 指针 , 不要释放成 data 指针, 否则会出错
    free(temp);
    // 释放局部引用, 不释放, GC 也会回收, 但是有延迟
    env->ReleaseStringUTFChars(path, filePath);
}


目录
相关文章
|
1月前
|
存储 前端开发 Java
Android MVVM架构模式下如何避免内存泄漏
Android采用MVVM架构开发项目,如何避免内存泄漏风险?怎样避免内存泄漏?
85 1
|
24天前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
46 2
|
2月前
|
Java 测试技术 Android开发
Android性能测试——发现和定位内存泄露和卡顿
本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
153 4
Android性能测试——发现和定位内存泄露和卡顿
|
2月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
62 11
|
2月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
93 11
|
2月前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
68 0
|
3月前
|
编解码 Android开发 UED
【性能狂飙!】揭秘Android应用极速变身秘籍:内存瘦身+用户体验升级,打造丝滑流畅新境界!
【8月更文挑战第12天】构建高效Android应用需全方位优化,尤其重视内存管理和用户体验。通过弱引用降低内存占用,懒加载资源减少启动负担。运用Kotlin协程确保UI流畅不阻塞,响应式设计适配多屏需求。这些策略共同提升了应用性能与用户满意度。
55 1
|
3月前
|
缓存 监控 Android开发
构建高效的Android应用:从内存优化到用户体验
【7月更文挑战第57天】 在竞争激烈的移动市场中,一个高效、流畅且具有优秀用户体验的Android应用是成功的关键。本文将深入探讨如何通过内存管理和界面优化来提升应用性能,包括实用的编程技巧和策略,以及如何利用Android系统提供的工具进行调试和性能监控。读者将学习到如何识别和解决常见的性能瓶颈,以及如何设计出既美观又实用的用户界面。
|
4月前
|
消息中间件 Android开发 开发者
🔍深度剖析Android内存泄漏,让你的App远离崩溃边缘,稳如老狗!🐶
【7月更文挑战第28天】在 Android 开发中,内存管理至关重要。内存泄漏可悄无声息地累积,最终导致应用崩溃或性能下滑。它通常由不正确地持有 Activity 或 Fragment 的引用引起。常见原因包括静态变量持有组件引用、非静态内部类误用、Handler 使用不当、资源未关闭及集合对象未清理。使用 Android Studio Profiler 和 LeakCanary 可检测泄漏,修复方法涉及使用弱引用、改用静态内部类、妥善管理 Handler 和及时释放资源。良好的内存管理是保证应用稳定性的基石。
77 4