软件的图片缓存管理

在我们实际的软件中,特别是那种需要显示很多的网络端的图片资源的时候,这种时候就特别容易产生内存溢出,所以这个时候就特别需要管理好软件的图片,那么,一般有哪些手段可以进行更好的管理呢?


方法1:等比例缩小图片,将要显示的图片,在可以接受的范围内进行压缩,以免每一张图片过大,导致显示不多的图片就占用很多的内存

方法2:对于有ListView的情况,

①、做到convertView重用,如图


这样可以避免对象的重复创建

这里有一点可以留意,就是view对象的setTag,以及getTag的方法,当多个地方会用到同一个View对象时,可以通过setTag和getTag来获取指定对象

这样,可以避免对象的重复创建



②、ViewHolder重用

我们都知道在getView()方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个item的子View的控件对象,如:ImageView、TextView等。这里的findViewById操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是使用ViewHolder,把每一个item的子View控件对象都放在Holder中,当第一次创建convertView对象时,便把这些item的子View控件对象findViewById实例化出来并保存到ViewHolder对象中。然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的,不需要再findViewById找item的子控件对象了。这样便大大提高了性能


方法3:去除xml中相关图片设置,改在程序中设置背景图

方法4:Activity destory时,将原有的可能有的setCallback回调设为空,避免由于回调而导致Activity得不到及时的释放


方法5:直接把xml配置文件加载成view 再放到一个容器里,然后直接调用 this.setContentView(View view);避免xml的重复加载


方法6:对图片采用对图片集中管理,采用软引用(SoftReference)与硬引用(LruCache)结合的方式来管理图片

LruCache类是专门用来做图片缓存处理的

而这一点正是本篇博客要写的重点


那么第一点就是需要为图片写一个图片管理中心,这个图片的缓存管理中心总的来说分为三块

1、图片的内存缓存管理

2、图片的文件缓存管理

3、图片的下载管理(在这里,下载器一般都使用封装好的,所以,这里可以酌情)


那么首先就来写第一个:

一、图片内存缓存管理

public class ImageMemoryCache {
    /**
     * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
     * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
     */
    private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量
    private static LruCache<String, Bitmap> mLruCache;  //硬引用缓存
    private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache;  //软引用缓存

    @SuppressLint("NewApi")
	public ImageMemoryCache(Context context) {
        int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        int cacheSize = 1024 * 1024 * memClass / 8;  //硬引用缓存容量,为系统可用内存的1/8
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                if (value != null){
                	//其实这里也可以用getByteCount(),就是计算出图片占用的容量,但是getByteCount()方法是从API Level 12开始
                	//一般为了兼容,所以采用value.getRowBytes() * value.getHeight()的方式,不过现在普遍手机版本都比较高,所以其实换掉也没事
                    return value.getRowBytes() * value.getHeight();
                }else{
                    return 0;
                }
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue != null){
                    // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
                    mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
                }
                super.entryRemoved(evicted, key, oldValue, newValue);
            }
        };
        //这里采用LinkedHashMap,在其构造器内的三个参数分别为:初始容量、加载因子、排序模式
        //初始容量,LinkedHashMap可以通过重写removeEldestEntry来控制内部存储的数量,但是HashMap则不行
        //加载因子,指空间的利用率,当设置的值高时比较省空间,但是会因此降低查询速度,反之也一样
        //排序方式,当为true时,按照访问顺序排序,最后访问的在第一个,当为false时,按照插入顺序排序。HashMap的是没有特定顺序的
        //在这里,我指定了按照访问顺序排序。
        mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
            private static final long serialVersionUID = 6040103833179403725L;
            @Override
            protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
            	//超出指定容量时就返回需要删除排序中的最后一项
                if (size() > SOFT_CACHE_SIZE){    
                    return true;  
                }  
                return false; 
            }
        };
    }

    /**
     * 从缓存中获取图片
     */
    public Bitmap getBitmapFromCache(String path) {
        Bitmap bitmap;
        //先从硬引用缓存中获取
        synchronized (mLruCache) {
            bitmap = mLruCache.get(path);
            if (bitmap != null) {
                //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
                mLruCache.remove(path);
                mLruCache.put(path, bitmap);
                return bitmap;
            }
        }
        //如果硬引用缓存中找不到,到软引用缓存中找
        synchronized (mSoftCache) { 
            SoftReference<Bitmap> bitmapReference = mSoftCache.get(path);
            if (bitmapReference != null) {
                bitmap = bitmapReference.get();
                if (bitmap != null) {
                    //将图片移回硬缓存
                    mLruCache.put(path, bitmap);
                    mSoftCache.remove(path);
                    return bitmap;
                } else {
                    mSoftCache.remove(path);
                }
            }
        }
        return null;
    } 

    /**
     * 添加图片到缓存
     */
    public void addBitmapToCache(String path, Bitmap bitmap) {
        if (bitmap != null) {
            synchronized (mLruCache) {
                mLruCache.put(path, bitmap);
            }
        }
    }

    /**
     * 回收一下内存,这个类是全局都会用到的,所以就不写回收了,它的生命周期应该是跟着整个软件的
     */
    public void clearCache() {
        mSoftCache.clear();
    }
}

内存缓存的方法就到这里,注解也已经比较详细了


二、图片文件缓存管理

@SuppressLint("NewApi")
public class ImageFileCache {
	//sd卡下存储文焕缓存的目录
    private static final String CACHDIR = "ImgCach";
    //缓存的图片文件的后缀
    private static final String WHOLESALE_CONV = ".cach";

    private static final int MB = 1024*1024;
    //指定的容量大小
    private static final int CACHE_SIZE = 10;
    //在sd卡上需要的内存大小
    private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;

    public ImageFileCache() {
        //清理文件缓存
        removeCache(getDirectory());
    }

    /** 从缓存中获取图片 **/
    public Bitmap getImage(final String url) {    
        final String path = getDirectory() + "/" + convertUrlToFileName(url);
        File file = new File(path);
        if (file.exists()) {
            Bitmap bmp = BitmapFactory.decodeFile(path);
            if (bmp == null) {
                file.delete();
            } else {
                updateFileTime(path);
                return bmp;
            }
        }
        return null;
    }

    /** 将图片存入文件缓存 **/
    public void saveBitmap(Bitmap bm, String url) {
        if (bm == null) {
            return;
        }
        //判断sdcard上的空间
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //SD空间不足
            return;
        }
        String filename = convertUrlToFileName(url);
        String dir = getDirectory();
        File dirFile = new File(dir);
        if (!dirFile.exists())
            dirFile.mkdirs();
        File file = new File(dir +"/" + filename);
        try {
            file.createNewFile();
            OutputStream outStream = new FileOutputStream(file);
            bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
            outStream.flush();
            outStream.close();
        } catch (FileNotFoundException e) {
            Log.w("ImageFileCache", "FileNotFoundException");
        } catch (IOException e) {
            Log.w("ImageFileCache", "IOException");
        }
    } 

    /**
     * 计算存储目录下的文件大小,
     * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
     * 那么删除40%最近没有被使用的文件
     */
    private boolean removeCache(String dirPath) {
        File dir = new File(dirPath);
        File[] files = dir.listFiles();
        if (files == null) {
            return true;
        }
        //SD卡是否存在,并且可以进行写操作
        if (!android.os.Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED)) {
            return false;
        }

        int dirSize = 0;
        //计算出所有文件的大小
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().contains(WHOLESALE_CONV)) {
                dirSize += files[i].length();
            }
        }
        //当文件大小大于指定大小,并且手机内存卡的剩余空间大于需要的空间时
        if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
        	//要删除40%的文件的个数
            int removeFactor = (int) ((0.4 * files.length) + 1);
            //删除前对文件根据文件操作的时间进行排序
            Arrays.sort(files, new FileLastModifSort());
            for (int i = 0; i < removeFactor; i++) {
            	//发现是指定的图片文件
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }
        //发现sd卡剩余空间少于所需空间时,返回删除失败的结果
        if (freeSpaceOnSd() <= CACHE_SIZE) {
            return false;
        }

        return true;
    }

    /** 修改文件的最后修改时间 **/
    public void updateFileTime(String path) {
        File file = new File(path);
        long newModifiedTime = System.currentTimeMillis();
        file.setLastModified(newModifiedTime);
    }

    /** 计算sdcard上的剩余空间 
     * 	这个方法判断的是sd卡上的空间,如果手机没有sd卡就会一直认为没有空间,这里在有些必须缓存的情况下
     * 	需要特殊处理一下,不再针对sd卡上,data/data目录下的容量也可以进行判断,但是需要先测试获取结果是否正常
     * **/
    private int freeSpaceOnSd() {
        StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
        //这里的三种返回sd卡可用空间的方法需要根据版本来,有些是需要一定版本才能使用的
//      double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
//        							mStat.f_bavail							mStat.f_bsize
//      return (int) sdFreeMB;
        long sdFreeMB = (stat.getBlockSizeLong() * stat.getAvailableBlocksLong()) / MB;
        return (int)sdFreeMB;
//      return (int) (stat.getAvailableBytes()/MB);
    } 

    /** 将url转成文件名 **/
    private String convertUrlToFileName(String url) {
        String[] strs = url.split("/");
        return strs[strs.length - 1] + WHOLESALE_CONV;
    }

    /** 获得缓存目录 **/
    private String getDirectory() {
        String dir = getSDPath() + "/" + CACHDIR;
        return dir;
    }

    /** 取SD卡路径 **/
    private String getSDPath() {
        File sdDir = null;
        boolean sdCardExist = Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED);  //判断sd卡是否存在
        if (sdCardExist) {
            sdDir = Environment.getExternalStorageDirectory();  //获取根目录
        }
        if (sdDir != null) {
            return sdDir.toString();
        } else {
            return "";
        }
    }

    /**
     * 根据文件的最后修改时间进行排序
     */
    private class FileLastModifSort implements Comparator<File> {
        public int compare(File arg0, File arg1) {
            if (arg0.lastModified() > arg1.lastModified()) {
                return 1;
            } else if (arg0.lastModified() == arg1.lastModified()) {
                return 0;
            } else {
                return -1;
            }
        }
    }

}
文件缓存的管理中心代码就到这里,这里面针对文件所占用的容量进行了控制,保证不会占用太大的容量



第三部分是图片的下载,这种情况一般在项目中都有统一的下载工具,所以在这里就不写了,那么,在实际使用中,针对以上的两个管理类,只需要再加一层控制层就可以很好地获取到图片了

 /*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
    public Bitmap getBitmap(String url) {
        // 从内存缓存中获取图片
        Bitmap result = memoryCache.getBitmapFromCache(url);
        if (result == null) {
            // 文件缓存中获取
            result = fileCache.getImage(url);
            if (result == null) {
                // 从网络获取
                result = ImageGetFromHttp.downloadBitmap(url);
                if (result != null) {
                    fileCache.saveBitmap(result, url);
                    memoryCache.addBitmapToCache(url, result);
                }
            } else {
                // 添加到内存缓存
                memoryCache.addBitmapToCache(url, result);
            }
        }
        return result;
    }

图片的管理就到这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值