Android 图片缓存、加载器

来源:互联网 发布:电脑看书软件app 编辑:程序博客网 时间:2024/05/16 05:04

前言

关于缓存的文章网络上有很多,本文旨在提供一份优秀缓存机制的同时,使大家理解其运行原理,供各位交流、进一步提升。


一、代码结构及缓存基本原理


二、图片加载器ImageWorker

   /**     * 根据url加载一个图片到ImageView上,未加载成功的时候显示加载图片<loadingImageId>     *      * @param url 图片网络地址.     * @param imageView 图片View     * @param loadingImageId 加载图片资源ID     * @Description:     * @Author Justlcw     * @Date 2014-3-6     */    public void loadImage(String url, ImageView imageView, int loadingImageId)    {        //如果图片网络地址为空,直接返回.        if(TextUtils.isEmpty(url))        {            return;        }        Bitmap bitmap = null;        if (mImageCache != null)        {            //先从内存缓存中取.            bitmap = mImageCache.getBitmapFromMemCache(url);        }        if (bitmap != null)        {            //如果取到了,直接显示.            imageView.setImageBitmap(bitmap);        }        else if (cancelPotentialWork(url, imageView))        {            //如果没有取到,取消之前相同的加载线程,取消成功,就启动一个新的加载线程.            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);            final AsyncDrawable asyncDrawable =                     new AsyncDrawable(mContext.getResources(), getLoadingImage(loadingImageId), task);            imageView.setImageDrawable(asyncDrawable);            task.execute(url);        }    }

如图,ImageWorker里面含有一个ImageCache(图片缓存),当开始加载一张网络图片的时候,首先从ImageCache(图片缓存)的内存缓存中获取,如果获取到了直接显示,否则启动一个后台线程加载图片。

1,看下BitmapWorkerTask在后台做了什么事

        @Override        protected Bitmap doInBackground(String... params)        {            url = params[0];            Bitmap bitmap = null;                        // 1.如果图片缓存不为null.            // 2.如果当前线程没有被取消.            // 3.如果被弱引用的ImageView没有被销毁.            // 4.如果加载程序无须退出.            if (mImageCache != null && !isCancelled() && getAttachedImageView() != null && !mExitTasksEarly)            {                //从硬盘缓存中去.                bitmap = mImageCache.getBitmapFromDiskCache(url);            }            // 1.如果从硬盘缓存中获取的图片为Null.            // 2.如果当前线程没有被取消.            // 3.如果被弱引用的ImageView没有被销毁.            // 4.如果加载程序无须退出.            if (bitmap == null && !isCancelled() && getAttachedImageView() != null && !mExitTasksEarly)            {                //后台获取图片<网络获取>                bitmap = processBitmap(params[0]);            }            // 1.如果加载过后的图片不为null.            // 2.如果图片缓存不为null.            if (bitmap != null && mImageCache != null)            {                //添加到缓存中.                mImageCache.addBitmapToCache(url, bitmap);            }            return bitmap;        }
先忽视if判断,执行语句表明:后台线程先是从ImageCache(图片缓存)的硬盘缓存中获取图片,如果获取不到再从网络获取;当获取成功后,又将图片放入ImageCache(图片缓存)中,这刚好验证了上面缓存的基本原理。

2,cancelPotentialWork(url, imageView)这个方法的作用

   /**     * 取消指定url的图片加载线程.     * @param url 图片网络地址     * @param imageView 图片View     * @return 如果有相同的线程在进行,返回false,否则返回 true.     * @Description:     * @Author Justlcw     * @Date 2014-3-6     */    public static boolean cancelPotentialWork(String url, ImageView imageView)    {        //从ImageView中获取加载线程.        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);        if (bitmapWorkerTask != null)        {            final String taskUrl = bitmapWorkerTask.url;            if (TextUtils.isEmpty(taskUrl) || !taskUrl.equals(url))            {                //如果加载的url为空,获得加载的url不等指定的url,则取消当前的加载线程.                bitmapWorkerTask.cancel(true);                LogUtil.d(TAG, "cancel load : " + taskUrl);            }            else            {                // 如果加载的url等于指定的url,则说明不需要重新加载,返回false.                return false;            }        }        return true;    }
回过头看下ImageWorker的内部类那张图片,当传入一个ImageView开始加载一张图片的时候,启动的加载线程(BitmapWorkerTask)会弱引用这个ImageView;同时ImageView又设置了一个AsyncDrawable(extends BitmapDrawable),而这个AsyncDrawable又弱引用了加载线程BitmapWorkerTask。

方法原理:当开始加载线程BitmapWorkerTask的时候,判断这个ImageView是否已经有一个相同的线程在运作了,如果有就不开始当前线程,如果没有就或者ImageView的加载线程加载的资源不同于当前,就取消这个原来的线程,开始新的加载线程。


三、图片缓存ImageCache

从上面的图可以看出,ImageCache包含了一个硬盘缓存ImageDiskLruCache和一个内存缓存LruCache<String, Bitmap>。


LruCache<String, Bitmap>由google的V4包提供,工作原理看源码即可理解,这里不做介绍。ImageDiskLruCache这个类继承于DiskLruCache,主要是将文件(File)的存取拓展成图片(Bitmap)的存取,所以重点看下其父类DiskLruCache:


1,DiskLruCache的构造

   /**     * 打开一个硬盘缓存.     * @Description:     * @Author Justlcw     * @Date 2014-3-5     */    public final static DiskLruCache openCache(Context context, String cacheName, long maxByteSize)    {        File cacheDir = CacheUtils.getEnabledCacheDir(context, cacheName);        if (cacheDir.isDirectory() && cacheDir.canWrite() && CacheUtils.getUsableSpace(cacheDir) > maxByteSize)        {            return new DiskLruCache(cacheDir, maxByteSize);        }        return null;    }

   /**     * 获得可使用的<缓存主目录>(先外部,后外部)     * @Description:     * @Author Justlcw     * @Date 2014-3-6     */    public static File getEnabledCacheDir(Context context, String cacheName)    {        String cachePath;        if(isExternalStorageRWable())        {            cachePath = Environment.getExternalStorageDirectory().getPath();        }        else        {            cachePath = context.getCacheDir().getPath();        }        File cacheFile = new File(cachePath + CacheConfig.DISK_CACHE_NAME + cacheName);        //如果缓存目录不存在,创建缓存目录.        if (!cacheFile.exists())        {            cacheFile.mkdirs();        }        return cacheFile;    }

如果外部存储(SD卡)可以使用,DiskLruCache则在外部存储中创建一个指定缓存名称的文件夹;如果不可用,则使用内存存储并创建指定文件夹。

同时还会对文件夹的可用空间进行判断,如果以上条件都允许,则返回一个DiskLruCache。

2,DiskLruCache的存入


   /**     * 把一个字节数组以文件存放在缓存中.     * @param key 关键字     * @param inputStream 字节流     * @return 缓存文件路径.     * @Description:     * @Author Justlcw     * @Date 2014-3-5     */    public final String put(String key, InputStream inputStream)    {        BufferedInputStream bufferedInputStream = null;        OutputStream bufferOps= null;        try        {            bufferedInputStream = new BufferedInputStream(inputStream);            String filePath = createFilePath(key);            bufferOps = new BufferedOutputStream(new FileOutputStream(filePath));            byte[] b = new byte[CacheConfig.IO_BUFFER_SIZE];            int count;            while ((count = bufferedInputStream.read(b)) > 0)            {                bufferOps.write(b, 0, count);            }            bufferOps.flush();            LogUtil.d(TAG, "put success : " + key);            onPutSuccess(key, filePath);            flushCache();            return filePath;        }        catch (IOException e)        {            LogUtil.d(TAG, "store failed to store: " + key, e);        }        finally        {            try            {                if(bufferOps != null)                {                    bufferOps.close();                }                if(bufferedInputStream != null)                {                    bufferedInputStream.close();                }                if(inputStream != null)                {                    inputStream.close();                }            }            catch (IOException e)            {                LogUtil.d(TAG, "close stream error : "+e.getMessage());            }        }        return "";    }

   /**     * 放入一个文件.     * @param key 关键字     * @param filePath 文件路径     */    protected final void onPutSuccess(String key, String filePath)    {        mLinkedHashMap.put(key, filePath);        cacheNumSize = mLinkedHashMap.size();        cacheByteSize += new File(filePath).length();    }

   /**     * 缓存大小溢出处理.     * @Description:     * @Author Justlcw     * @Date 2014-3-5     */    protected final void flushCache()    {        Entry<String, String> eldestEntry;        File eldestFile;        long eldestFileSize;        int count = 0;        //超过最大缓存文件个数   or  超过最大空间大小    移除不常用的文件,并且一次最多只能移除4个.        while (count < MAX_REMOVALS && (cacheNumSize > maxCacheNumSize || cacheByteSize > maxCacheByteSize))        {            eldestEntry = mLinkedHashMap.entrySet().iterator().next();            eldestFile = new File(eldestEntry.getValue());            eldestFileSize = eldestFile.length();            mLinkedHashMap.remove(eldestEntry.getKey());            eldestFile.delete();            cacheNumSize = mLinkedHashMap.size();            cacheByteSize -= eldestFileSize;            count++;            LogUtil.d(TAG, "flushCache - Removed :" + eldestFile.getAbsolutePath()+ ", " + eldestFileSize);        }    }

当文件存入成功的时候,做了两步操作:

1.将文件的路径放入到当前使用的缓存文件列表中,并且更新缓存文件总共的数量及大小。

2.判断当前缓存文件有没有超出上限限制 (数量限制   和    大小限制)


3,DiskLruCache的取出



   /**     * 根据key返回缓存文件.     * @param key key     * @Description:     * @Author Justlcw     * @Date 2014-3-6     */    public final File get(String key)    {        if(containsKey(key))        {            final File file = new File(createFilePath(key));            if(file.exists())            {                return file;            }        }        return null;    }    /**     * 缓存中是否有key的数据保存     * @param key 关键字     * @Description:     * @Author Justlcw     * @Date 2014-3-5     */    public final boolean containsKey(String key)    {        //是否在map中.        if (mLinkedHashMap.containsKey(key))        {            return true;        }        //如果不在map中,看是或否在文件夹中.        final String existingFile = createFilePath(key);        if (new File(existingFile).exists())        {            //如果存在,放入map后期直接提取.            onPutSuccess(key, existingFile);            return true;        }        return false;    }
根据key得到相应的文件路径,如果这个路径在当前使用的缓存中,直接返回这个文件;如果不在判断是否在原来的缓存文件夹中,如果在就是取出来放入当前使用的缓存列表中,否表示不在,则去下载。


四、图片加载器的使用

        @Override        public View getView(int position, View convertView, ViewGroup parent)        {            if(convertView == null)            {                convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_grid, parent, false);            }            ImageView imageView = (ImageView)convertView.findViewById(R.id.imageView);            mImageLoader.loadImage(imgURLArray[position], imageView, R.drawable.bg_loading);                        return convertView;        }

在activity中放一个gridview,然后设置一个adapter,adapter使用图片加载的代码如上。

图片的内存缓存大小使用的是应用分得内存大小的1/8,所有的配置都在CacheConfig类里面配置。


实际效果及缓存文件夹如下:


                 


不足之处,望指点,望体谅。 附: 源码链接

0 0
原创粉丝点击