android 图片处理(压缩与缓存)

一、压缩

android程序经常用到许多图片,图片有不同的大小,形状。在大多数情况下,这些图片都会大于我们程序所需要的的大小。比如图片库展示的图片大都是手机摄像头拍出来的,图片分辨率比我们手机分辨率高得多。app都是有一定内存限制的,程序占用过高的内存就容易出现OOM,获取每个应用程序最高可用内存,因手机型号而有所不同,可用代码获取,大部分32M。

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.e("mrpeng","最大内存是:"+maxMemory);

因此,展示高分辨率图片的时候,最好先将图片压缩,压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大图片并没有带来视觉的好吃,却会占用相当多内存,性能上会带来负面影响,因此需要压缩。
BitemapFactory这个类提供了多个解析方法,(decodeByteArray,decodeFile,decodeResource)用于创建Bitmap对象,可以根据图片的来源选择合适的方法,比如SD卡中的图片可以使用decodeFile;网络图片可以使用decodeStream,资源文件中的图片可以使用decodeResource方法。这些方法会尝试为已经构建的bitmap分配内存, 这时就容易出现OOM,为此每个解析方法都提供了一个可选的BitemapFactory.Options参数,这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null但是BitemapFactory.Options的outWidth,outHeight outMimeType都会被赋值。这个技巧让我可以在加载图片之前就获取图片的长宽值和MIME类型,从而根据情况对图片进行压缩。

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeResource(getResources(), R.id.img, options);
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;
        String outMimeType = options.outMimeType;

为了避免OOM,最好解析每张图片的时候都先检查一下图片的大小,除非你非常信任图片的来源。
android计算一张图片所占内存大小,图片长所占像素字节数,android像素字节数目
1、alpha-8 占一个字节

2、ARGB-444 占2个字节

3、ARGB-8888占4个字节

4、RGB-565 占2个字节

加载图片时需要考虑是加载完整的图片还是加载一个压缩版的。

1、预估一下加载整张图片所需占用的内存。
2、为了加载这一张图片你所愿意提供多少内存。
3、用于展示这张图片的控件的实际大小。
4、当前设备的屏幕尺寸和分辨率。

加载完整的暂且不表,怎么将大分辨率图片压缩呢
1、按比例压缩


public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
        final int height=options.outHeight;
        final int width=options.outWidth;
        int inSampleSize=1;
        if (height>reqHeight||width>reqWidth){
            if (width > height) {
                inSampleSize = Math.round((float)height / (float)reqHeight);
            } else {
                inSampleSize = Math.round((float)width / (float)reqWidth);
            }
        }
        return inSampleSize;
    }

注意,需要两次设置options的inSampleSize值
首先你要将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片。然后将BitmapFactory.Options连同期望的宽度和高度一起传递到到calculateInSampleSize方法中,就可以得到合适的inSampleSize值了。之后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为false,就可以得到压缩后的图片了

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
        int reqWidth, int reqHeight) {  
    // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小  
    final BitmapFactory.Options options = new BitmapFactory.Options();  
    options.inJustDecodeBounds = true;  
    BitmapFactory.decodeResource(res, resId, options);  
    // 调用上面定义的方法计算inSampleSize值  
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
    // 使用获取到的inSampleSize值再次解析图片  
    options.inJustDecodeBounds = false;  
    return BitmapFactory.decodeResource(res, resId, options);  
} 

将缩略图填充到控件中

imageView.setImageBitmap(decodeSampleBitmapFromResource(getResources(),R.id.mgimg,100,100));

将resource或是图库图片压缩获取缩略图后还需压缩,按比例压缩

private Bitmap compressImage(Bitmap image)
    {

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

二、图片缓存技术

ui界面加载一张图片是一件很简单的事情,但是如果需要界面上加载一大堆图片,情况就复杂了,(比如ListView GridView或ViewPager这样的组件),Listview的图片优化参见我的博客,屏幕上显示的图片可以通过滑动屏幕事件不断的增加,最终导致oom。为了保证内存使用始终维持在一个合理的范围,通常会把移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对图片惊醒GC操作。这种思路解决问题非常好,可是为了能让程序快熟运行,界面上迅速加载图片,又必须考虑图片回收之后,又被用户滑入屏幕这种情况。这是重新加载以便刚刚加载的图片无疑是性能的瓶颈。
这个时候,使用内存缓存技术可以很好的解决这个问题。它可以让组件快速地重新加载和处理图片。
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问方法。其中最核心的类LruCacheI(此类在android-support-v4的包中提供)。这个类非常适合用来缓存图片。主要算法原理是把最近使用的对象用强引用存储在LinkedHashMap中,并且把最少使用的对象在缓存值达到预定值之前从内存中移除。
在过去,我们经常会使用非常流行的内存缓存技术的实现,即软引用或弱引用,现在已经不再推荐使用这种方式。因为从Android 2.3(API Level9)开始,垃圾回收期会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (Android Level 11)中,图片的数据会存储在本地的内存中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出或崩溃。
为了能够选择一个合适的缓存大小给LruCache,需要考虑以下因素

*设备为每个内存分配的内存大小
*屏幕上最多能显示多少张图片,有多少图片需要进行预加载,
*屏幕大小和分辨率是多少,一个超高分辨率的设备比起一个较低分辨率设备在持有相同数量图片的时候,需要更大的内存空间
*图片的尺寸和大小,每张图片占据多少内存空进啊。
*图片被访问的频率有多高,会不会有一些图片访问频率比其他图片要高,如果是,也许应该让一些图片长驻内存中或者使用多个LruCache对象来区分不同组的图片

并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起OOM。

private LruCache<String, Bitmap> mMemoryCache;  
  @Override  
protected void onCreate(Bundle savedInstanceState) {  
    // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。  
    // LruCache通过构造函数传入缓存值,以KB为单位。  
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
    // 使用最大可用内存值的1/8作为缓存的大小。  
    int cacheSize = maxMemory / 8;  
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
        @Override  
        protected int sizeOf(String key, Bitmap bitmap) {  
            // 重写此方法来衡量每张图片的大小,默认返回图片数量。  
            return bitmap.getByteCount() / 1024;  
        }  
    };  
}  

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
    if (getBitmapFromMemCache(key) == null) {  
        mMemoryCache.put(key, bitmap);  
    }  
}  

public Bitmap getBitmapFromMemCache(String key) {  
    return mMemoryCache.get(key);  
}  

很多第三方框架 诸如xutils imagloader等加载网络图片的时候,内部都有个三级缓存机制。原理:
优先从内存获取图片,效率高,访问速度快,如果没有拿到,就从本地获取,省流量,如果还是没有,就只能从网络获取。

工具类:

public class ImageCacheUtils {

    private  MemoryCacheUtils memoryCacheUtils;
    private final LocalCacheUtils localCacheUtils;
    private final NetCacheUtils netCacheUtils;

    public ImageCacheUtils(){
        memoryCacheUtils = new MemoryCacheUtils();
        localCacheUtils = new LocalCacheUtils(memoryCacheUtils);
        netCacheUtils = new NetCacheUtils(memoryCacheUtils, localCacheUtils);
    }

    public void display(ImageView imageView,String url,ListView list){
        Bitmap bitmap =null;
        //内存中get,如果不为null就展示
        bitmap=memoryCacheUtils.getBitmap(url);
        if (bitmap!=null){
            imageView.setImageBitmap(bitmap);
            return;
        }
        //内存中没有获取到,本地中获取
        bitmap= localCacheUtils.getBitmap(url);
        if (bitmap!=null){
            imageView.setImageBitmap(bitmap);
            return;
        }
        //本地中没有获取到就直接访问网络
        netCacheUtils.display(url,imageView,list)

    }

}

一级缓存,内存

public class MemoryCacheUtils {

    private final LruCache<String, Bitmap> lruCache;

    public MemoryCacheUtils(){
        int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);
        lruCache = new LruCache<String, Bitmap>(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                 重写此方法来衡量每张图片的大小,默认返回图片数量。
                return value.getRowBytes()*value.getHeight();
            }
        };
    }

    //内存缓存中get图片
    public Bitmap getBitmap(String url){
        return lruCache.get(url);
    }

    //内存缓存中put图片
    public void putBitmap(String url,Bitmap bitmap){
        lruCache.put(url,bitmap);
    }


}

二级缓存,本地

public class LocalCacheUtils {

    private String cache_dir;
    MemoryCacheUtils memoryCacheUtils;
    public LocalCacheUtils(MemoryCacheUtils memoryCacheUtils) {
        cache_dir="/sdcard/mrpeng";
        this.memoryCacheUtils=memoryCacheUtils;
    }

    public Bitmap getBitmap(String url) {
        try {
            String fileName = MD5Encoder.encode(url);
            File file = new File(cache_dir, fileName);
            if (file.exists()){
                Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
                memoryCacheUtils.putBitmap(url,bitmap);
                return bitmap;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public void saveBitmap(String url,Bitmap bitmap){

        try {
            String fileName = MD5Encoder.encode(url);
            File file = new File(cache_dir, fileName);
            File parentFile = file.getParentFile();
            if(!parentFile.exists()){
                parentFile.mkdirs();
            }
            FileOutputStream fos = new FileOutputStream(file);
            //对象持久化到文件中
            bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三级,网络加载

public class NetCacheUtils {

    private MemoryCacheUtils memoryCacheUtils;
    private LocalCacheUtils localCacheUtils;
    private final ExecutorService pool;
    private MyHandler myHandler;
    private ListView list;


    public NetCacheUtils(MemoryCacheUtils memoryCacheUtils, LocalCacheUtils localCacheUtils) {
        pool = Executors.newFixedThreadPool(5);
        this.memoryCacheUtils=memoryCacheUtils;
        this.localCacheUtils=localCacheUtils;
    }

    public void display(String url, ImageView imageView, ListView list) {
        pool.execute(new DownloadRunnable(url,imageView));
        myHandler = new MyHandler();
        this.list=list;



    }

    private class DownloadRunnable implements Runnable {

        private String mUrl;
        private ImageView imageView;
        private int position;

        public DownloadRunnable(String url, ImageView imageView) {
            mUrl=url;
            imageView=imageView;
            position=(Integer)imageView.getTag();
        }

        @Override
        public void run() {
            try {
                Thread.sleep(3000);
                HttpURLConnection con = (HttpURLConnection) new URL(mUrl).openConnection();
                con.connect();
                int responseCode = con.getResponseCode();
                if (responseCode==200){
                    InputStream is = con.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(is);
                    Message.obtain(myHandler,1,new Result(position,bitmap)).sendToTarget();
                    memoryCacheUtils.putBitmap(mUrl, bitmap);
                    localCacheUtils.saveBitmap(mUrl,bitmap);

                }


            } catch (Exception e) {
                e.printStackTrace();
            }


        }
    }

    private class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Result res = (Result) msg.obj;
            ImageView imageView = (ImageView) list.findViewWithTag(res.position);
            if (imageView!=null){
                imageView.setImageBitmap(res.bitmap);
            }
            super.handleMessage(msg);
        }
    }

    class Result {
        public Bitmap bitmap;
        public int position;
        public Result(int position, Bitmap bitmap) {
            this.bitmap = bitmap;
            this.position = position;
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值