我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android性能优化之Bitmap图片优化详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android性能优化之Bitmap图片优化详解

前言

在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(

Java.lang.OutofMemoryError
- 内存溢出),本篇博客,我们将一起探讨Bitmap的性能优化。

为什么Bitmap会导致OOM?

1.每个机型在编译ROM时都设置了一个应用堆内存VM值上限

dalvik.vm.heapgrowthlimit
,用来限定每个应用可用的最大内存,超出这个最大值将会报OOM。这个阀值,一般根据手机屏幕dpi大小递增,dpi越小的手机,每个应用可用最大内存就越低。所以当加载图片的数量很多时,就很容易超过这个阀值,造成OOM。

2.图片分辨率越高,消耗的内存越大,当加载高分辨率图片的时候,将会非常占用内存,一旦处理不当就会OOM。例如,一张分辨率为:1920x1080的图片。如果Bitmap使用 ARGB_8888 32位来平铺显示的话,占用的内存是1920x1080x4个字节,占用将近8M内存,可想而知,如果不对图片进行处理的话,就会OOM。

3.在使用ListView, GridView等这些大量加载view的组件时,如果没有合理的处理缓存,大量加载Bitmap的时候,也将容易引发OOM

Bitmap基础知识

一张图片Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数

Bitmap.Config
,正是指定单位像素占用的字节数的重要参数。

其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。

ALPHA_8

表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度

ARGB_4444

表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节

ARGB_8888

表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节

RGB_565

表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节

一张图片Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数

根据以上的算法,可以计算出图片占用的内存,以100*100像素的图片为例

BitmapFactory解析Bitmap的原理

BitmapFactory提供的解析Bitmap的静态工厂方法有以下五种:


Bitmap decodeFile(...)
Bitmap decodeResource(...)
Bitmap decodeByteArray(...)
Bitmap decodeStream(...)
Bitmap decodeFileDescriptor(...)

其中常用的三个:decodeFile、decodeResource、decodeStream。

decodeFile和decodeResource其实最终都是调用decodeStream方法来解析Bitmap

decodeFile方法代码:


 public static Bitmap decodeFile(String pathName, Options opts) {
 Bitmap bm = null;
 InputStream stream = null;
 try {
  stream = new FileInputStream(pathName);
  bm = decodeStream(stream, null, opts);
 } catch (Exception e) {
  
  Log.e("BitmapFactory", "Unable to decode stream: " + e);
 } finally {
  if (stream != null) {
  try {
   stream.close();
  } catch (IOException e) {
   // do nothing here
  }
  }
 }

decodeResource方法的代码:


public static Bitmap decodeResource(Resources res, int id, Options opts) {
 Bitmap bm = null;
 InputStream is = null; 
 try {
  final TypedValue value = new TypedValue();
  is = res.openRawResource(id, value);
  bm = decodeResourceStream(res, value, is, null, opts);
 } catch (Exception e) {
  
 } finally {
  try {
  if (is != null) is.close();
  } catch (IOException e) {
  // Ignore
  }
 }
 if (bm == null && opts != null && opts.inBitmap != null) {
  throw new IllegalArgumentException("Problem decoding into existing bitmap");
 }
 return bm;
 }

decodeStream的逻辑如下:


 public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
 // we don't throw in this case, thus allowing the caller to only check
 // the cache, and not force the image to be decoded.
 if (is == null) {
  return null;
 }
 Bitmap bm = null;
 Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
 try {
  if (is instanceof AssetManager.AssetInputStream) {
  final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
  bm = nativeDecodeAsset(asset, outPadding, opts);
  } else {
  bm = decodeStreamInternal(is, outPadding, opts);
  }
  if (bm == null && opts != null && opts.inBitmap != null) {
  throw new IllegalArgumentException("Problem decoding into existing bitmap");
  }
  setDensityFromOptions(bm, opts);
 } finally {
  Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 }
 return bm;
 }
private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
 // ASSERT(is != null);
 byte [] tempStorage = null;
 if (opts != null) tempStorage = opts.inTempStorage;
 if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
 return nativeDecodeStream(is, tempStorage, outPadding, opts);
 }

从上面的代码可以看出,decodeStream的代码最终会调用以下两个native方法之一


nativeDecodeAsset()
nativeDecodeStream()

这两个native方法只是对应decodeFile和decodeResource、decodeStream来解析的,像decodeByteArray、decodeFileDescriptor也有专门的native方法负责解析Bitmap。

decodeFile、decodeResource的区别在于他们方法的调用路径不同:


decodeFile->decodeStream
decodeResource->decodeResourceStream->decodeStream

decodeResource在解析时多调用了一个decodeResourceStream方法,而这个decodeResourceStream方法代码如下:


public static Bitmap decodeResourceStream(Resources res, TypedValue value,
  InputStream is, Rect pad, Options opts) {
 if (opts == null) {
  opts = new Options();
 }
 if (opts.inDensity == 0 && value != null) {
  final int density = value.density;
  if (density == TypedValue.DENSITY_DEFAULT) {
  opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
  } else if (density != TypedValue.DENSITY_NONE) {
  opts.inDensity = density;
  }
 }
 if (opts.inTargetDensity == 0 && res != null) {
  opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
 }
 return decodeStream(is, pad, opts);
 }

其中对Options进行处理了,在得到

opts.inDensity
属性的前提下,如果我们没有对该属性设定值,那么将
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
赋定这个默认的Density值,这个默认值为160,为标准的dpi比例,即在
Density=160
的设备上1dp=1px,这个方法中还有这么一行


opts.inTargetDensity = res.getDisplayMetrics().densityDpi;

opts.inTargetDensity
进行了赋值,该值为当前设备的densityDpi值,所以说在decodeResourceStream方法中主要做了两件事:

     1.对

opts.inDensity
赋值,没有则赋默认值160

     2.对

opts.inTargetDensity
赋值,没有则赋当前设备的densityDpi值

之后参数将传入decodeStream方法,该方法中在调用native方法进行解析Bitmap后会调用这个方法

setDensityFromOptions(bm, opts);


private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
 if (outputBitmap == null || opts == null) return;
 final int density = opts.inDensity;
 if (density != 0) {
  outputBitmap.setDensity(density);
  final int targetDensity = opts.inTargetDensity;
  if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
  return;
  }
  byte[] np = outputBitmap.getNinePatchChunk();
  final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
  if (opts.inScaled || isNinePatch) {
  outputBitmap.setDensity(targetDensity);
  }
 } else if (opts.inBitmap != null) {
  // bitmap was reused, ensure density is reset
  outputBitmap.setDensity(Bitmap.getDefaultDensity());
 }
 }

主要就是把刚刚赋值过的两个属性inDensity和inTargetDensity给Bitmap进行赋值,不过并不是直接赋给Bitmap就完了,中间有个判断,当inDensity的值与inTargetDensity或与设备的屏幕Density不相等时,则将应用inTargetDensity的值,如果相等则应用inDensity的值。

所以总结来说,setDensityFromOptions方法就是把inTargetDensity的值赋给Bitmap,不过前提

是opts.inScaled = true

进过上面的分析,结论如下:

在不配置Options的情况下:

      1.decodeFile、decodeStream在解析时不会对Bitmap进行一系列的屏幕适配,解析出来的将是原始大小的图

      2.decodeResource在解析时会对Bitmap根据当前设备屏幕像素密度densityDpi的值进行缩放适配操作,使得解析出来的Bitmap与当前设备的分辨率匹配,达到一个最佳的显示效果,并且Bitmap的大小将比原始的大

Bitmap的优化策略

经过上面的分析,我们可以得出Bitmap优化的思路:

      1、BitmapConfig的配置

      2、使用decodeFile、decodeResource、decodeStream进行解析Bitmap时,配置inDensity和inTargetDensity,两者应该相等,值可以等于屏幕像素密度*0.75f

      3、使用inJustDecodeBounds预判断Bitmap的大小及使用inSampleSize进行压缩

      4、对Density>240的设备进行Bitmap的适配(缩放Density)

      5、2.3版本inNativeAlloc的使用

      6、4.4以下版本inPurgeable、inInputShareable的使用

      7、Bitmap的回收

所以我们根据以上的思路,我们将Bitmap优化的策略总结为以下3种:

      1.对图片质量进行压缩

      2.对图片尺寸进行压缩

      3.使用libjpeg.so库进行压缩

对图片质量进行压缩


 public static Bitmap compressImage(Bitmap bitmap){ 
  ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
  //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 
  bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); 
  int options = 100; 
  //循环判断如果压缩后图片是否大于50kb,大于继续压缩 
  while ( baos.toByteArray().length / 1024>50) { 
  //清空baos 
  baos.reset(); 
  bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos); 
  options -= 10;//每次都减少10 
  } 
  //把压缩后的数据baos存放到ByteArrayInputStream中 
  ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray()); 
  //把ByteArrayInputStream数据生成图片 
  Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null); 
  return newBitmap; 
 } 

对图片尺寸进行压缩


 
 public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {
  ByteArrayOutputStream os = new ByteArrayOutputStream();
  bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
  if( os.toByteArray().length / 1024>512) {//判断如果图片大于0.5M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
   os.reset();
   bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//这里压缩50%,把压缩后的数据存放到baos中
  }
  ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
  BitmapFactory.Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true;
  options.inPreferredConfig = Bitmap.Config.RGB_565;
  BitmapFactory.decodeStream(is, null, options);
  options.inJustDecodeBounds = false;
  options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH );
  is = new ByteArrayInputStream(os.toByteArray());
  Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
  return newBitmap;
 }
 
 public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
  int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
  int roundedSize;
  if (initialSize <= 8) {
   roundedSize = 1;
   while (roundedSize < initialSize) {
    roundedSize <<= 1;
   }
  } else {
   roundedSize = (initialSize + 7) / 8 * 8;
  }
  return roundedSize;
 }
 private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
  double w = options.outWidth;
  double h = options.outHeight;
  int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
  int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
  if (upperBound < lowerBound) {
   return lowerBound;
  }
  if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
   return 1;
  } else if (minSideLength == -1) {
   return lowerBound;
  } else {
   return upperBound;
  }
 }

使用libjpeg.so库进行压缩

除了通过设置simpleSize根据图片尺寸压缩图片和通过

Bitmap.compress
方法通过压缩图片质量两种方法外,我们还可以使用libjpeg.so这个库来进行压缩。

libjpeg是广泛使用的开源JPEG图像库,Android所用的是skia的压缩算法,而Skia对libjpeg进行了的封装。
libjpeg在压缩图像时,有一个参数叫optimize_coding,关于这个参数,libjpeg.doc有如下解释:


boolean optimize_coding 
TRUE causes the compressor to compute optimal Huffman coding tables 
for the image. This requires an extra pass over the data and 
therefore costs a good deal of space and time. The default is 
FALSE, which tells the compressor to use the supplied or default 
Huffman tables. In most cases optimal tables save only a few percent 
of file size compared to the default tables. Note that when this is 
TRUE, you need not supply Huffman tables at all, and any you do 
supply will be overwritten.

如果设置optimize_coding为TRUE,将会使得压缩图像过程中基于图像数据计算哈弗曼表,由于这个计算会显著消耗空间和时间,默认值被设置为FALSE。

谷歌的Skia项目工程师们最终没有设置这个参数,optimize_coding在Skia中默认的等于了FALSE,但是问题就随之出现了,如果我们想在FALSE和TRUE时压缩成相同大小的JPEG 图片,FALSE的品质将大大逊色于TRUE的,尽管谷歌工程师没有将该值设置为true,但是我们可以自己编译libjpeg进行图片的压缩。

libjpeg的官网下载地址:http://www.ijg.org/

从官网下载之后,我们必须自己对其进行编译。

编译libjpeg

下载最新的源码,解压后将所有文件放到jni目录中,准备用ndk编译

1、新建config.sh,将ndk中的交叉编译工具加入其中,内容如下:


NDK=/opt/ndk/android-ndk-r10e/
PLATFORM=$NDK/platforms/android-9/arch-arm/
PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86/
CC=$PREBUILT/bin/arm-linux-androideabi-gcc
./configure --prefix=/home/linc/jpeg-9b/jni/dist --host=arm CC="$CC --sysroot=$PLATFORM"

2、执行此脚本


$ sh config.sh 
...
checking whether to build shared libraries... no
checking whether to build static libraries... yes
...
config.status: creating Makefile
config.status: creating jconfig.h

首先,它生成了Makefile,我们可以直接使用此Makefile进行编译;其次,它生成了重要的头文件,jconfig.h.
但是这个Makefile是编译static库而不是共享库的。

此时,我们可以执行构建命令进行编译:


jni$ make install-libLTLIBRARIES
libtool: install: ranlib /home/linc/jpeg-9b/jni/dist/lib/libjpeg.a

3、Android.mk

使用ndk-build指令编译,需要手动编写Android.mk文件,内容如下:


LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_class="lazy" data-src_FILES :=jaricom.c jcapimin.c jcapistd.c jcarith.c jccoefct.c jccolor.c \
  jcdctmgr.c jchuff.c jcinit.c jcmainct.c jcmarker.c jcmaster.c \
  jcomapi.c jcparam.c jcprepct.c jcsample.c jctrans.c jdapimin.c \
  jdapistd.c jdarith.c jdatadst.c jdataclass="lazy" data-src.c jdcoefct.c jdcolor.c \
  jddctmgr.c jdhuff.c jdinput.c jdmainct.c jdmarker.c jdmaster.c \
  jdmerge.c jdpostct.c jdsample.c jdtrans.c jerror.c jfdctflt.c \
  jfdctfst.c jfdctint.c jidctflt.c jidctfst.c jidctint.c jquant1.c \
  jquant2.c jutils.c jmemmgr.c jmemnobs.c
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays \
 -DANDROID -DANDROID_TILE_BASED_DECODE -DENABLE_ANDROID_NULL_CONVERT
LOCAL_MODULE := libjpeg
LOCAL_MODULE_TAGS := optional
# unbundled branch, built against NDK.
LOCAL_SDK_VERSION := 17
include $(BUILD_SHARED_LIBRARY)

其中LOCAL_class="lazy" data-src_FILES后面的源文件可以参考刚刚生成的Makefile。

在jni目录上一级使用ndk-build编译即可。


$ ndk-build
[armeabi] Compile arm : jpeg <= jaricom.c
...
[armeabi] Compile arm : jpeg <= jmemnobs.c
[armeabi] SharedLibrary : libjpeg.so
[armeabi] Install  : libjpeg.so => libs/armeabi/libjpeg.so

在Android项目引入编译好的libjpeg

首先把so库加载到libs中,然后将编译好的头文件拷贝到项目的jni文件夹下,就可以使用Android的具体函数了,具体使用分为如下几步:

     1、将Android的bitmap解码并转换为RGB数据

     2、为JPEG对象分配空间并初始化

     3、指定压缩数据源

     4、获取文件信息

     5、为压缩设定参数,包括图像大小,颜色空间

     6、开始压缩

     7、压缩完毕

     8、释放资源


#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <jni.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>
#include "jpeglib.h"
#include "cdjpeg.h"  
#include "jversion.h"  
#include "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=myerr->pub.jpeg_message_table[myerr->pub.msg_code];
 LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,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) {
 int nComponent = 3;
 struct jpeg_compress_struct jcs;
 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;
   }
  //为JPEG对象分配空间并初始化
 jpeg_create_compress(&jcs);
 //获取文件信息
 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");
 }
 jcs.arith_code = false;
 jcs.input_components = nComponent;
 if (nComponent == 1)
  jcs.in_color_space = JCS_GRAYSCALE;
 else
  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;
 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);
 }
 if (jcs.optimize_coding) {
   LOGI("optimize==ture");
  } else {
   LOGI("optimize==false");
  }
 //压缩完毕
 jpeg_finish_compress(&jcs);
 //释放资源
 jpeg_destroy_compress(&jcs);
 fclose(f);
 return 1;
}
typedef struct {
 uint8_t r;
 uint8_t g;
 uint8_t b;
} rgb;
//将java string转换为char*
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
 char* rtn = NULL;
 jsize alen = (*env)->GetArrayLength(env, barr);
 jbyte* ba = (*env)->GetByteArrayElements(env, barr, 0);
 if (alen > 0) {
  rtn = (char*) malloc(alen + 1);
  memcpy(rtn, ba, alen);
  rtn[alen] = 0;
 }
 (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
 return rtn;
}
//jni方法入口
jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
  jobject thiz, jobject bitmapcolor, int w, int h, int quality,
  jbyteArray fileNameStr, jboolean optimize) {
 AndroidBitmapInfo infocolor;
 BYTE* pixelscolor;
 int ret;
 BYTE * data;
 BYTE *tmpdata;
 char * fileName = jstrinTostring(env, fileNameStr);
 //解码Android bitmap信息,并存储值infocolor中
 if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) {
  LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
  return (*env)->NewStringUTF(env, "0");;
 }
 if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) < 0) {
  LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
 }
 BYTE r, g, b;
 data = NULL;
 data = malloc(w * h * 3);
 tmpdata = data;
 int j = 0, i = 0;
 int color;
 //将bitmap转换为rgb数据
 for (i = 0; i < h; i++) {
  for (j = 0; j < w; j++) {
   color = *((int *) pixelscolor);
   r = ((color & 0x00FF0000) >> 16);
   g = ((color & 0x0000FF00) >> 8);
   b = color & 0x000000FF;
   *data = b;
   *(data + 1) = g;
   *(data + 2) = r;
   data = data + 3;
   pixelscolor += 4;
  }
 }
 AndroidBitmap_unlockPixels(env, bitmapcolor);
 //进行压缩
 int resultCode= generateJPEG(tmpdata, w, h, quality, fileName, optimize);
 free(tmpdata);
 if(resultCode==0){
  jstring result=(*env)->NewStringUTF(env, error);
  error=NULL;
  return result;
 }
 return (*env)->NewStringUTF(env, "1"); //success
}

新建Android.mk,生成可执行文件:


LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_class="lazy" data-src_FILES:= jpeg_compress.cpp
LOCAL_MODULE:= jtest
LOCAL_LDLIBS :=-llog
LOCAL_LDLIBS += $(LOCAL_PATH)/libjpeg.so
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := debug
include $(BUILD_EXECUTABLE)

总结

本篇博客总结了3种图片压缩的方法,大家可以根据自己的情况进行相应的使用,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对编程网的支持。

您可能感兴趣的文章:Android 中对于图片的内存优化方法android内存优化之图片优化Android中RecyclerView 滑动时图片加载的优化Android图片压缩以及优化实例总结Android App内存优化之图片优化Android优化查询加载大数量的本地相册图片Android图片性能优化详解Android中的图片优化完全指南


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android性能优化之Bitmap图片优化详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android性能优化之Bitmap图片优化详解

前言 在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError - 内存溢出),本篇博客,我们将一起探讨Bitmap的性能优化。
2022-06-06

Android性能优化之弱网优化详解

这篇文章主要为大家介绍了Android性能优化之弱网优化示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

解析Android开发优化之:对Bitmap的内存优化详解

1) 要及时回收Bitmap的内存 Bitmap类有一个方法recycle(),从方法名可以看出意思是回收。这里就有疑问了,Android系统有自己的垃圾回收机制,可以不定期的回收掉不使用的内存空间,当然也包括Bitmap的空间。那为什么还
2022-06-06

android内存优化之图片优化

对图片本身进行操作。尽量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,最终都是通过java层的createB
2022-06-06

Android Bitmap详解及Bitmap的内存优化

Android Bitmap详解及Bitmap的内存优化 一、Bitmap: Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。常用方法:pub
2022-06-06

Android性能优化(六)图片压缩

一、压缩图片 文件压缩——内存压缩 二、文件压缩方式 1.质量压缩   2.尺寸压缩   3.格式选择:JPEG/WEBP (4.0以上) 三、压缩原理 /frameworks/base/core/jni/android/graphics/
2022-06-06

Android的bitmap图片优化方法是什么

这篇文章主要讲解了“Android的bitmap图片优化方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android的bitmap图片优化方法是什么”吧!背景Android开发中,
2023-06-25

Android性能优化之网络优化

在移动互联网的快速发展环境下,手机用户日益对网络的使用或体验有着更深度的诉求,因此应用中的网络体验已经显得由此重要。

Android性能优化之网络优化DNS和HttpDNS知识详解

在 App 访问网络的时候,DNS 解析是网络请求的第一步,默认我们使用运营商的 LocalDNS 服务。有数据统计,在这一块 3G 网络下,耗时在 200~300ms,4G 网络下也需要 100ms。

总结Android App内存优化之图片优化

前言 在Android设备内存动不动就上G的情况下,的确没有必要去太在意APP对Android系统内存的消耗,但在实际工作中我做的是教育类的小学APP,APP中的按钮、背景、动画变换基本上全是图片,在2K屏上(分辨率2048*1536)一张
2022-06-06

详解Android性能优化之内存泄漏

综述 内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存。那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity
2022-06-06

Android 图片优化

1. 图片的二次采样,避免图片太大OOMpublic class MainActivity extends AppCompatActivity { private int MY_PERMISSIONS_REQUEST_CALL_PHONE
2022-06-06

详解Android中Bitmap及其内存优化

小编这次要给大家分享的是详解Android中Bitmap及其内存优化,文章内容丰富,感兴趣的小伙伴可以来了解一下,希望大家阅读完这篇文章之后能够有所收获。Android Bitmap详解及Bitmap的内存优化一、Bitmap:Bitmap
2023-05-31

iOS 下的图片处理与性能优化详解

目录图片在计算机世界中怎样被存储和表示?常见的图片格式如何判断图片的格式?UIImageView 的性能瓶颈解决性能瓶颈:强制解码总结图片在计算机世界中怎样被存储和表示?图片和其他所有资源一样,在内存中本质上都是0和1的二进制数据,计算机需
2022-05-23

Android图片缓存之Bitmap详解(一)

前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap、BitmapFactory这两个类。 Bitmap: Bitmap是Android系统中的图像处理的最重要类之一。用它可
2022-06-06

前端性能优化——图片篇

图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以对图片进行优化势在必行。

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录