【0193】Android 性能优化之Bitmap内存管理及优化
1.Bitmap的加载
图片存在的几种形式:File、Stream、Bitmap内存的形式
常用的三个方法:最后都是调用的是decodeStream
static Bitmap decodeFile(String pathName, BitmapFactory.Options opts) static Bitmap decodeResource(Resources res, int id, BitmapFactory.Options opts) static Bitmap decodeStream(InputStream is)
【源码】BitmapFactory.java
情况加载:如果是Assets目录下的资源(AssetManager.AssetInputStream)则会调用Nativite方法
1 public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { 2 // we don't throw in this case, thus allowing the caller to only check 3 // the cache, and not force the image to be decoded. 4 if (is == null) { 5 return null; 6 } 7 validate(opts); 8 9 Bitmap bm = null; 10 11 Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); 12 try { 13 if (is instanceof AssetManager.AssetInputStream) { 14 final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); 15 bm = nativeDecodeAsset(asset, outPadding, opts); //调用的是本地方法cpp,详见Bitmap.cpp 16 } else { 17 bm = decodeStreamInternal(is, outPadding, opts); 18 } 19 20 if (bm == null && opts != null && opts.inBitmap != null) { 21 throw new IllegalArgumentException("Problem decoding into existing bitmap"); 22 } 23 24 setDensityFromOptions(bm, opts); 25 } finally { 26 Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); 27 } 28 29 return bm; 30 }
2.BitmapFactory的option参数
BitmapFactory在decode的时候还会参考inDensity和inTargetDensity两个属性
先认识下两个Bitmap的参数:
【inSampleSize】注意inSampleSize只能是2的幂,如不是2的幂下转到最大的2的幂,而且inSampleSize>=1
【inDensity】Bitmap的像素密度;Options中的inDensity属性会根据drawable文件夹的分辨率来赋值
如果放置图片的drawable文件夹后跟”-xxxhdpi”字样, 那么decode这张图片时候inDensity属性就是640



px = dp*Density
【inTargetDensity】Bitmap最终的像素密度,inTartgetDensity会根据屏幕的像素密度来赋值
通过下面的代码可以获取屏幕的inTargentDensity;
1 //屏幕的density可由此代码获得,这个就是inTargetDensity 2 DisplayMetrics metrics = new DisplayMetrics(); 3 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 4 wm.getDefaultDisplay().getMetrics(metrics); 5 int inTargetDensity = metircs.densityDpi;
【非常重要的公式】计算仅针对于drawable文件夹的图片来说
而对于一个file或者stream那么inDensity和inTargetDensity是不考虑的! 他们默认就是0
【重要公式】输出图片的宽高= 原图片的宽高 / inSampleSize * (inTargetDensity / inDensity)
3.decodeResource的优化源码
调用的关系如下:
BitmapFactory.decodeResource();
-->decodeResourceStream()
【源码】
首先看inDesity的值设置:在inDesity值为0时,会根据TypeValue中类型进行赋值。类型为TypedValue.DENSITY_DEFAULT,inDensity赋值为160,否则不为空的状态下设置为传入的TypeValue类的density值。
再次获取该res文件夹对应的inTargetDensity值,传入res的目的也是为了获取该参数
最后再调用decodeStream()方法处理
1 public static Bitmap decodeResourceStream(Resources res, TypedValue value, 2 InputStream is, Rect pad, Options opts) { 3 validate(opts); 4 if (opts == null) { 5 opts = new Options(); 6 } 7 8 if (opts.inDensity == 0 && value != null) { 9 final int density = value.density; 10 if (density == TypedValue.DENSITY_DEFAULT) { 11 opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; 12 } else if (density != TypedValue.DENSITY_NONE) { 13 opts.inDensity = density; 14 } 15 } 16 17 if (opts.inTargetDensity == 0 && res != null) { 18 opts.inTargetDensity = res.getDisplayMetrics().densityDpi; 19 } 20 21 return decodeStream(is, pad, opts); 22 }
4.图片的质量压缩
【原理】通过算法抠掉(同化)了图片中的一些某个些点附近相近的像素,达到降低质量介绍文件大小的目的。降低的图片质量,减小了图片的信息。
形象的说法:相邻四个像素值:#FFCCFF #FFCCFE #FFCCFD #FFCCFC 经过压缩之后都变为了#FFCCFF。在信息存储上大小会改变,同样,存储4个不同的值和存储4个一样的值,当然是存储4个一样的值更省空间。
【注意】它其实只能实现对file的影响,对加载这个图片出来的bitmap内存是无法节省的,只改变图像的存储空间的大小,不会改变图片的分辨率
因为bitmap在内存中的大小是按照像素计算的,也就是width*height,对于质量压缩,并不会改变图片的真实的像素(像素大小不会变)
public boolean compress (Bitmap.CompressFormat format, int quality, OutputStream stream)
【参数1】【format】压缩之后的图片的格式
【参数2】【quality】范围:0-100。 0表示小尺寸压缩,100表示压缩以获得最高质量。 某些格式,如无损的PNG,忽略quality设置。
【参数3】【stream】压缩之后的输出流
【返回值】true 压缩成功并将数据成功写入到指定的输出流
Bitmap压缩之后写入指定的输出流,如下面的实例实现了此功能。
如果返回true,则可以通过将相应的输入流传递给BitmapFactory.decodeStream()来重建Bitmap。
注意:并非所有Formats都直接支持所有位图配置,因此BitmapFactory返回的位图的位深会改变,可能丢失了每像素的alpha(例如,JPEG仅支持不透明像素)。
使用此方法可能比较耗时,因此需要开辟子线程。
1 public class MainActivity extends AppCompatActivity { 2 3 private File imageFile; 4 private File sdFile; 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.activity_main); 10 sdFile = Environment.getExternalStorageDirectory(); 11 imageFile = new File(sdFile, "Chrysanthemum2.jpg"); 12 } 13 24 public void qualityCompress(View v){ 25 26 BitmapFactory.Options options = new BitmapFactory.Options(); 27 Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); 28 //压缩图片 29 compressImageToFile(bitmap, new File(sdFile,"qualityCompress.jpeg")); 30 } 31 32 public static void compressImageToFile(Bitmap bmp,File file){ 33 //0~100 34 int quality = 50; //设置图判断图片的压缩质量值 35 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 36 bmp.compress(Bitmap.CompressFormat.JPEG, quality , baos ); 37 try { 38 FileOutputStream fos = new FileOutputStream(file); 39 fos.write(baos.toByteArray()); 40 fos.flush(); 41 fos.close(); 42 } catch (Exception e) { 43 // TODO Auto-generated catch block 44 e.printStackTrace(); 45 } 46 }
质量压缩之后的图片的大小的改变对比:

5.图片的分辨率的压缩
压缩之后的分辨率宽高分别缩小为原来的1/4。

【源码】
1 public void sizeCompress(View v){ 2 File sdFile = Environment.getExternalStorageDirectory(); 3 File imageFile = new File(sdFile, "Chrysanthemum2.jpg"); 4 BitmapFactory.Options options = new BitmapFactory.Options(); 5 Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); 6 compressBitmapToFileBySize(bitmap, new File(sdFile,"sizeCompress.jpeg")); 7 8 } 9 10 /** 11 * 2.尺寸压缩 12 * 通过减少单位尺寸的像素值,正真意义上的降低像素。1020*8880-- 13 * 使用场景:缓存缩略图的时候(头像处理) 14 * 15 * @param bmp 16 * @param file 17 */ 18 public static void compressBitmapToFileBySize(Bitmap bmp, File file){ 19 //压缩尺寸倍数,值越大,图片的尺寸就越小 20 int ratio = 4;
//[1]新建了一个空的bitmap对象,设置参数 21 Bitmap result = Bitmap.createBitmap(bmp.getWidth()/ratio, bmp.getHeight()/ratio, 22 Bitmap.Config.ARGB_8888); 23 //【2】将空的bitmap作为参数,新建一个canvas 24 Canvas canvas = new Canvas(result); 25 RectF rect = new RectF(0, 0, bmp.getWidth()/ratio, bmp.getHeight()/ratio);
//【3】 以原bitmap,在画布上画bitmap 26 canvas.drawBitmap(bmp, null, rect , null); 27 28 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 29 /**【4】输出bitmap 30 * 从此处着手,可以增加质量压缩 31 */ 32 result.compress(Bitmap.CompressFormat.JPEG, 100, baos); 33 try { 34 FileOutputStream fos = new FileOutputStream(file); 35 fos.write(baos.toByteArray()); 36 fos.flush(); 37 fos.close(); 38 } catch (Exception e) { 39 e.printStackTrace(); 40 } 41 }
6.采样率压缩
1 /** 2 * 设置图片的采样率,降低图片像素 3 * @param filePath 4 * @param file 5 */ 6 public static void compressBitmap(String filePath, File file){ 7 // 数值越高,图片像素越低 8 int inSampleSize = 8; 9 BitmapFactory.Options options = new BitmapFactory.Options(); 10 options.inJustDecodeBounds = false; 11 // options.inJustDecodeBounds = true;//为true的时候不会真正加载图片,而是得到图片的宽高信息。 12 //采样率 13 options.inSampleSize = inSampleSize; 14 Bitmap bitmap = BitmapFactory.decodeFile(filePath, options); 15 16 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 17 // 把压缩后的数据存放到baos中 18 bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos); 19 try { 20 if(file.exists()) 21 { 22 file.delete(); 23 } 24 else { 25 file.createNewFile(); 26 } 27 FileOutputStream fos = new FileOutputStream(file); 28 fos.write(baos.toByteArray()); 29 fos.flush(); 30 fos.close(); 31 } catch (Exception e) { 32 e.printStackTrace(); 33 } 34 }
7.图片性能增加哈夫曼算法
7.1 引子
【问】IOS拍照1M的图片要比安卓拍照排出来的5M的图片还要清晰。
都是在同一个环境下,保存的都是JPEG。为什么?
【答】与图像处理引擎相关
95年 JPEG处理引擎,用于最初的在PC上面处理图片的引擎。
05年 skia开源的引擎, 开发了一套基于JPEG处理引擎的第二次开发。便于浏览器的使用。
07年, 安卓上面用的什么引擎?
skia引擎,阉割版。
谷歌拿了skia 思考了半天做了一个决定,去掉一个编码算法---哈夫曼算法。采用定长编码算法。
但是解码还是保留了哈夫曼算法。
导致了图片处理后文件变大了。
理由:当时由于CPU和内存在手机上都非常吃紧 性能差,由于哈夫曼算法非常吃CPU,被迫用了其他的算法。
我们的优化:绕过安卓Bitmap API层,来自己编码实现----修复使用哈夫曼算法
【jpeg官网地址】https://libjpeg-turbo.org/
【jpeg引擎】主要工作:jpeg的编码和解码

【webp格式】摘自百度百科
WebP格式,谷歌(google)开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3,并能节省大量的服务器宽带资源和数据空间。Facebook Ebay等知名网站已经开始测试并使用WebP格式。
但WebP是一种有损压缩。相较编码JPEG文件,编码同样质量的WebP文件需要占用更多的计算资源。
桌面版Chrome可打开WebP格式。
7.2 算法简述
【参考文章】https://blog.csdn.net/liufeng_king/article/details/8720896
argb
一个像素点包涵四个信息:alpha,red,green,blue
a b c d e
abcde acdbe bacde ……
101010100011100
a:001
b:010
c:011
d:100
e:101
用3位来表示一个字符信息,属于定长编码的最优。
abcde
001 010 011 100 101
加权信息编码
a:80%
b:10%
c:10%
d:0%
e:0%
这种情况,编码就可以优化了
a:01
b:10
c:11
优化后的abc:01 10 11
优化前的abc:001 010 011
由上面的讲述可知,Android去掉编码算法---哈夫曼算法,但是解码还是保留了哈夫曼算法。因此在改进的时候只需要做编码工作即可。
7.3 代码实现
【1】下载库:http://www.ijg.org/

微信中使用的也是jpeg库,是经过自己改良的。但是,在最新版本微信apk解压之后没有看到该库。

目录结构:

步骤流程:
1.导入库文件libjpegbither.so
2.导入头文件
3.写mk文件
Android.mk
Applicatoin.mk
4.写代码
C++: XX.cpp
C: XX.c
1.将android的bitmap解码,并转换成RGB数据
一个图片信息---像素点(argb)
alpha去掉
2.JPEG对象分配空间以及初始化
3.指定压缩数据源
4.获取文件信息
5.为压缩设置参数,比如图像大小、类型、颜色空间
boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */
6.开始压缩
jpeg_start_compress()
7.压缩结束
jpeg_finish_compress()
8.释放资源
【Android.mk】
1 LOCAL_PATH := $(call my-dir) 2 include $(CLEAR_VARS) 3 LOCAL_MODULE :=jpegbither //定义一个模块的名字 4 LOCAL_SRC_FILES :=libjpegbither.so //引用的库.so 5 include $(PREBUILT_SHARED_LIBRARY) 6 include $(CLEAR_VARS) 7 LOCAL_MODULE :=bitherjni 8 LOCAL_SRC_FILES :=bitherlibjni.cpp //书写源码的文件 9 LOCAL_SHARED_LIBRARIES :=jpegbither 10 LOCAL_LDLIBS := -ljnigraphics -llog 11 include $(BUILD_SHARED_LIBRARY)
【Application.mk】
1 APP_ABI := armeabi-v7a armeabi #表示 编译目标 ABI(应用二进制接口) 2 APP_PLATFORM := android-9
【bitherlibjni.cpp】部分截取
1 jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env, 2 jclass thiz, jobject bitmapcolor, int w, int h, int quality, 3 jbyteArray fileNameStr, jboolean optimize) { 4 BYTE *pixelscolor; 5 //1.将bitmap里面的所有像素信息读取出来,并转换成RGB数据,保存到二维byte数组里面 6 //处理bitmap图形信息方法1 锁定画布 7 AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor); 8 9 //2.解析每一个像素点里面的rgb值(去掉alpha值),保存到一维数组data里面 10 BYTE *data; 11 BYTE r,g,b; 12 data = (BYTE*)malloc(w*h*3);//每一个像素都有三个信息RGB 13 BYTE *tmpdata; 14 tmpdata = data;//临时保存data的首地址 15 int i=0,j=0; 16 int color; 17 for (i = 0; i < h; ++i) { 18 for (j = 0; j < w; ++j) { 19 //解决掉alpha 20 //获取二维数组的每一个像素信息(四个部分a/r/g/b)的首地址 21 color = *((int *)pixelscolor);//通过地址取值 22 //0~255: 23 // a = ((color & 0xFF000000) >> 24); 24 r = ((color & 0x00FF0000) >> 16); 25 g = ((color & 0x0000FF00) >> 8); 26 b = ((color & 0x000000FF)); 27 //改值!!!----保存到data数据里面 28 *data = b; 29 *(data+1) = g; 30 *(data+2) = r; 31 data = data + 3; 32 //一个像素包括argb四个值,每+4就是取下一个像素点 33 pixelscolor += 4; //保存的是原始的像素的信息 34 } 35 } 36 //处理bitmap图形信息方法2 解锁 37 AndroidBitmap_unlockPixels(env,bitmapcolor); 38 char* fileName = jstrinTostring(env,fileNameStr); 39 //调用libjpeg核心方法实现压缩 40 int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize); 41 if(resultCode ==0){ 42 jstring result = env->NewStringUTF("-1"); 43 return result; 44 } 45 return env->NewStringUTF("1"); 46 }
【核心代码】
1 int generateJPEG(BYTE* data, int w, int h, int quality, 2 const char* outfilename, jboolean optimize) { 3 4 //jpeg的结构体,保存的比如宽、高、位深、图片格式等信息,相当于java的类 5 struct jpeg_compress_struct jcs; 6 7 //当读完整个文件的时候就会回调my_error_exit这个退出方法。setjmp是一个系统级函数,是一个回调。 8 struct my_error_mgr jem; 9 jcs.err = jpeg_std_error(&jem.pub); 10 jem.pub.error_exit = my_error_exit; 11 if (setjmp(jem.setjmp_buffer)) { 12 return 0; 13 } 14 15 //初始化jsc结构体 16 jpeg_create_compress(&jcs); 17 //打开输出文件 wb:可写byte 18 FILE* f = fopen(outfilename, "wb"); 19 if (f == NULL) { 20 return 0; 21 } 22 //设置结构体的文件路径 23 jpeg_stdio_dest(&jcs, f); 24 jcs.image_width = w;//设置宽高 25 jcs.image_height = h; 26 // if (optimize) { 27 // LOGI("optimize==ture"); 28 // } else { 29 // LOGI("optimize==false"); 30 // } 31 32 //看源码注释,设置哈夫曼编码:/* TRUE=arithmetic coding, FALSE=Huffman */ 33 jcs.arith_code = false; 34 int nComponent = 3; 35 /* 颜色的组成 rgb,三个 # of color components in input image */ 36 jcs.input_components = nComponent; 37 //设置结构体的颜色空间为rgb 38 jcs.in_color_space = JCS_RGB; 39 // if (nComponent == 1) 40 // jcs.in_color_space = JCS_GRAYSCALE; 41 // else 42 // jcs.in_color_space = JCS_RGB; 43 44 //全部设置默认参数/* Default parameter setup for compression */ 45 jpeg_set_defaults(&jcs); 46 //是否采用哈弗曼表数据计算 品质相差5-10倍 47 jcs.optimize_coding = optimize; 48 //设置质量 49 jpeg_set_quality(&jcs, quality, true); 50 //开始压缩,(是否写入全部像素) 51 jpeg_start_compress(&jcs, TRUE); 52 53 JSAMPROW row_pointer[1]; 54 int row_stride; 55 //一行的rgb数量 56 row_stride = jcs.image_width * nComponent; 57 //一行一行遍历 58 while (jcs.next_scanline < jcs.image_height) { 59 //得到一行的首地址 60 row_pointer[0] = &data[jcs.next_scanline * row_stride]; 61 62 //此方法会将jcs.next_scanline加1 63 jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:写入的行数 64 } 65 jpeg_finish_compress(&jcs);//结束 66 jpeg_destroy_compress(&jcs);//销毁 回收内存 67 fclose(f);//关闭文件 68 69 return 1; 70 }
【源码下载地址】 https://download.csdn.net/download/wsxingjun/10570553
浙公网安备 33010602011771号