一、压缩方式:
1.质量压缩:也是清晰度压缩,它牺牲掉一些画面的细节,这些丢失的细节或许可以被肉眼观察到,这种压缩也叫有损压缩;
2.尺寸压缩:就是图片的宽高减小;图片的宽高减小了,图片文件大小也减小了;
3.格式选择:JPEG/WEBP(4.0以上);jpeg比png压缩率高,webp压缩率比jpeg高;
jpeg没有alpha通道;
android bitmap压缩方法,当格式是png时,会忽略质量参数(无效);
二、Bitmap内存占用(指的加载到手机,占用手机运行内存,与文件大小没有任何关系)
1.内存大小,不是占用磁盘大小:也就是只要宽和高一样的图片,且格式一样,占用内存大小一致;占用的内存大小(和图片分辨率有关)和文件的大小(占用磁盘代销)没有关系;
内存计算大小与文件大小没有任何关系;内存大小只和分辨率有关;
ARGB_8888:ARGB各占8位,8位一个字节,共4个字节,占用内存:width * height * 4;
RGB_565:R占5位,G占6位,B占5位,共占16位也就是2个字节,占用内存大小:widthheight2;
2.在程序中获得一个Bitmap的大小,使用系统提供的bitmap.getByteCount()方法,此方法返回存储此位图像素最小字节数;
3.BitmapFactory.Option:这个是控制解码图片参数
inDensity:表示这个Bitmap的像素密度,根据drawable目录获取的值;
inTargetDensity:表示要被画出来时的目标(屏幕)像素密度;
inTargetDensity = getResources().getDisplayMetrics().densityDpi;
Bimap会根据inDensity和inTargetDensity两个参数解码图片;
三、质量、大小压缩代码演示
对于图片处理,都是使用google研发、开源的C++二维图形库Skia;
Android使用的是阉割版本的Skia,对jpeg的处理基于libjpeg,对png的处理基于libpng;
早起由于手机cpu吃紧,将libjpeg中的最优哈夫曼编码关闭了,直到7.0才打开;
哈夫曼编码是一种字符编码方式,常用于数据文件压缩;压缩率通常在20%--90%之间;
哈夫曼编码的主要思想:采取可变长编码方式,对文件中出现次数多的字符采取比较短的编码,对于出现次数少的字符采取正常的编码方式,这样可以有效的减小总的编码方式;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
/**
* 质量压缩
*/
compress(bitmap, Bitmap.CompressFormat.JPEG,50,Environment.getExternalStorageDirectory()+"/test_q.jpeg");
/**
* 尺寸压缩
* 最后一个参数 filter是否开启滤波,可以使图片更加鲜艳
*/
//获取一个指定大小的缩放的bitmap
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 100, 100, true);
compress(scaledBitmap, Bitmap.CompressFormat.JPEG,100,Environment.getExternalStorageDirectory()+"/test_sacle.jpeg");
/**
* 质量压缩Bitmap的方法
* @param bitmap
* @param compressFormat
* @param quality
* @param outPath
*/
private void compress(Bitmap bitmap, Bitmap.CompressFormat compressFormat, int quality, String outPath) {
FileOutputStream fileOutputStream = null ;
try {
fileOutputStream = new FileOutputStream(outPath);
bitmap.compress(compressFormat, quality, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (null != fileOutputStream) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
四、压缩加载Bitmap占用内存大小代码
package com.zcbl.airport_assist.permissiondemo;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
/**
* Created by serenitynanian on 2018/10/16.
* 处理加载Bitmap占用内存大小类
*/
public class ImageResize {
public static Bitmap resizeBitmap(Context context, int resId, int maxW, int maxH,boolean isAlpha,Bitmap reuseableBitmap) {
Resources resources = context.getResources();
BitmapFactory.Options options = new BitmapFactory.Options();
//设置inJustDecodeBounds为true后,BitmapFactory.decodeResource(resources, resId, options)返回
// 的是一个null的bitmap,它不会解码出bitmap数据,只会解码出这张图片的信息;
// 设置完毕后,可以从options中取出以out...开头的属性参数;
options.inJustDecodeBounds = true ;
BitmapFactory.decodeResource(resources, resId, options);
int width = options.outWidth;
int height = options.outHeight;
//设置缩放系数 宽高全部缩放inSampleSize倍
options.inSampleSize = calculateInSampleSize(width,height,maxW,maxH);
if (!isAlpha) {
//去除alpha通道
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
//设置false,解码出图像数据;,默认是flase;
options.inJustDecodeBounds = false ;
//设置inMutable为true,代表此bitmap占用的空间可以被复用,默认为fasle
options.inMutable = true ;
//使用复用图片的内存
options.inBitmap = reuseableBitmap ;
return BitmapFactory.decodeResource(resources,resId,options);
}
/**
* 计算缩放系数
* @param width
* @param height
* @param maxW
* @param maxH
* @return
*/
private static int calculateInSampleSize(int width, int height, int maxW, int maxH) {
int inSampleSize = 1 ;
if (width > maxW && height > maxH) {
inSampleSize = 2 ;
while (width / inSampleSize > maxW && height / inSampleSize > maxH) {
inSampleSize*=2;
}
}
Log.e("ImageResize", "calculateInSampleSize: "+inSampleSize );
return inSampleSize;
}
}
五、Bitmap内存缓存和复用内存代码
package com.zcbl.airport_assist.permissiondemo;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.util.LruCache;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Created by serenitynanian on 2018/10/16.
*/
public class ImageCache {
private static ImageCache instances ;
private LruCache memoryCache;
/**
* 创建一个Bitmap复用池,来保存可以被复用的Bitmap
*
*/
Set> reuseablePool;
private ReferenceQueue referenceQueue;
private Thread clearReferenceQueueThread ;
private boolean shutDown ;
private ImageCache(){}
public static ImageCache getInstances() {
if (null == instances) {
synchronized (ImageCache.class) {
if (null == instances) {
instances = new ImageCache();
}
}
}
return instances ;
}
public void init(Context context){
//创建一个线程安全的复用池
reuseablePool = Collections.synchronizedSet(new HashSet>());
Context applicationContext = context.getApplicationContext();
ActivityManager am = (ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE);
//得到系统给此应用分配的最大内存,单位是兆
int memoryClass = am.getMemoryClass();
//LruCache参数指的是:LruCache缓存的内存最大字节数
memoryCache = new LruCache(memoryClass / 8 * 1024 * 1024){
/**
* 如果sizeOf返回1,那么上面给LruCache传递的参数是100,表明能缓存100个这样的value
* @param key
* @param value
* @return value占用的内存
*/
@Override
protected int sizeOf(String key, Bitmap value) {
//两者的区别:
//getByteCount:返回这个bitmap占用的实际大小
//getAllocationByteCount:如果这个bitmap是复用的,且复用的bitmap比实际的大,则返回复用的bitmap的大小
// int byteCount = value.getByteCount();
int byteCount = 0 ;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
byteCount = value.getAllocationByteCount();
}else{
byteCount = value.getByteCount();
}
return byteCount;
}
/**
* 当内存达到以后,在往LruCache添加时,旧的bitmap从LruCache移除时,会回调此方法
* @param evicted
* @param key
* @param oldValue
* @param newValue
*/
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue.isMutable()) {
// 在android3.0以下,bitmap的内存在Native中;
// 在>=3.0以上占用内存在java;
// 在8.0以上在native;
reuseablePool.add(new WeakReference(oldValue,getReferenceQueue()));
}
oldValue.recycle();//回收bitmap
}
};
}
/**
* @return 返回一个引用队列
*/
private ReferenceQueue getReferenceQueue(){
if (null == referenceQueue) {
//创建一个引用队列,当弱引用保存的对象需要被回收时,对象会放入引用队列中
referenceQueue = new ReferenceQueue<>();
clearReferenceQueueThread = new Thread(new Runnable() {
@Override
public void run() {
while (!shutDown){
try {
//为什么不使用referenceQueue.poll()?
// 因为remove是阻塞的,有的话就执行,没有的话就等待
Reference extends Bitmap> remove = referenceQueue.remove();
Bitmap bitmap = remove.get();
if (bitmap != null && !bitmap.isRecycled()) {
//这个主要为了释放Native层的内存;java层的内存不用管,gc会自动回收
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
clearReferenceQueueThread.start();
}
return referenceQueue;
}
public void putBitmap2Memory(String key, Bitmap bitmap) {
memoryCache.put(key, bitmap);
}
public Bitmap getBitmapFromMemory(String key) {
return memoryCache.get(key);
}
public void clearMemoryCache(){
memoryCache.evictAll();//相当于hasmap的clean()
}
/**
* 可以被复用的Bitmap必须设置inMutable为true;
* Android4.4(API 19)之前只有格式是jpg、png,同等宽高(要求苛刻),inSampleSize为1的bitmap才能被复用;
* Android4.4被复用的bitmap的inPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig;
* Android4.4之后被复用的bitmap的内存必须大于等于需要申请内存的bitmap的内存;
* @param w
* @param h
* @param inSampleSize
* @return
*/
public Bitmap getReuseableBitmap(int w, int h, int inSampleSize) {
//小于3.0以下的
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return null;
}
Bitmap reusebaleBitmap = null ;
Iterator> iterator = reuseablePool.iterator();
//迭代查找符合复用的bitmap
while (iterator.hasNext()) {
WeakReference next = iterator.next();
//得到复用池中的一个bitmap
Bitmap bitmap = next.get();
if (null != bitmap) {
//检查此bitmap能否被复用
if (checkBitmap(bitmap, w, h, inSampleSize)) {
reusebaleBitmap = bitmap;
//能够被复用,拿出复用池,即从复用池中移除
iterator.remove();
break ;
}
}else{
iterator.remove();
}
}
return reusebaleBitmap ;
}
/**
* 检查bitmap是否符合复用条件
* @param bitmap
* @param w
* @param h
* @param inSampleSize
* @return
*/
private boolean checkBitmap(Bitmap bitmap,int w, int h, int inSampleSize){
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return bitmap.getWidth()==w&&bitmap.getHeight()==h&&inSampleSize==1;
}
if (inSampleSize > 1) {
w/=inSampleSize;
h/=inSampleSize;
}
int byteCout = w*h*getPixel(bitmap.getConfig());
boolean equalMemory = bitmap.getAllocationByteCount()>=byteCout;
return equalMemory ;
}
private int getPixel(Bitmap.Config config){
if (config == Bitmap.Config.ARGB_8888) {
return 4 ;
}
return 2;
}
}