五、Android Bitmap文件压缩

一、压缩方式:

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 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;
    }
}

你可能感兴趣的:(五、Android Bitmap文件压缩)