zoukankan      html  css  js  c++  java
  • 【第五篇】Volley代码修改之图片二级缓存以及相关源码阅读(重写ImageLoader.ImageCache)

    前面http://www.cnblogs.com/androidsuperman/p/8a157b18ede85caa61ca5bc04bba43d0.html

    有讲到使用LRU来处理缓存的,但是只是处理内存里面的缓存,没进行文件缓存和处理,那么如何实现Volley在本地的缓存呢
    一般硬盘缓存使用com.jakewharton.disklrucache.DiskLruCache这个Lru缓存,具体代码在
    重写ImageCache实现图片二级缓存L2LRUImageCache.java
    public class L2LRUImageCache implements ImageLoader.ImageCache{
        LruCache<String, Bitmap> lruCache;
        DiskLruCache diskLruCache;
        final int RAM_CACHE_SIZE = 10 * 1024 * 1024;
        String DISK_CACHE_DIR = "cache";
        //硬盘缓存50M
        final long DISK_MAX_SIZE = 50 * 1024 * 1024;
        String cacheFullPath;
        public L2LRUImageCache(Context context) {
            //此处是标准的Lru缓存写法
            this.lruCache = new LruCache<String, Bitmap>(RAM_CACHE_SIZE) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getByteCount();
                }
            };
            
            //如果sd卡存在,创建缓存目录
            if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
            {
                File cacheDir = context.getExternalFilesDir(DISK_CACHE_DIR);
                cacheFullPath=cacheDir.getAbsolutePath();
                if(!cacheDir.exists())
                {
                    cacheDir.mkdir();
                }
                try {
                    
                    diskLruCache = DiskLruCache.open(cacheDir, 1, 1, DISK_MAX_SIZE);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public Bitmap getBitmap(String url) {
            String key=generateKey(url);
            //从内存缓存读取
            Bitmap bmp = lruCache.get(key);
            //内存缓存中没有,读取本地文件缓存
            if (bmp == null) {
                PLog.d(this,"内存读图失败,从磁盘读"+url);
                bmp = getBitmapFromDiskLruCache(key);
                //从磁盘读出后,放入内存
                if(bmp!=null)
                {
                    lruCache.put(key,bmp);
                }
            }
            //如果文件里面也没有这个文件,就有必要采取网络下载方式进行下载
            if(bmp==null)
            {
                PLog.d(this,"从缓存读图失败,去下载"+url);
            }
            return bmp;
        }
        
        //文件缓存到内存缓存和本地缓存
        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            //文件缓存中的key为md5后的url链接
            String key=generateKey(url);
            lruCache.put(key, bitmap);
            putBitmapToDiskLruCache(key,bitmap);
        }
        
        //清理内存缓存,以及缓存目录里面的文件
        @Override
        public void clear() {
            lruCache.evictAll();
            FileUtils.deleteFile(cacheFullPath);
        }
        
        //图片放入文件缓存中区
        private void putBitmapToDiskLruCache(String key, Bitmap bitmap) {
            if(diskLruCache!=null) {
                try {
                    DiskLruCache.Editor editor = diskLruCache.edit(key);
                    if (editor != null) {
                        OutputStream outputStream = editor.newOutputStream(0);
                        bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
                        editor.commit();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //获取图片本地缓存
        private Bitmap getBitmapFromDiskLruCache(String key) {
            if(diskLruCache!=null) {
                try {
                    DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
                    if (snapshot != null) {
                        InputStream inputStream = snapshot.getInputStream(0);
                        if (inputStream != null) {
                            Bitmap bmp = BitmapFactory.decodeStream(inputStream);
                            inputStream.close();
                            return bmp;
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
        /**
         * 因为DiskLruCache对key有限制,只能是[a-z0-9_-]{1,64},所以用md5生成key
         * @param url
         * @return
         */
        private String generateKey(String url)
        {
            return MD5Utils.getMD532(url);
        }
    }

    接下来考虑Volley正常加载图片时怎么加载的,如下:

     ImageListener listener = ImageLoader.getImageListener(ivImage,
                        R.drawable.ic_launcher, R.drawable.ic_launcher);
                imageLoader.get(string, listener);
    那么调用L2LRUImageCache 进行二级缓存和缓存文件读取并加载到组件上面的逻辑也就在重写imageLoader的逻辑里面:
    原生的volley里面 imageLoader.get(string, listener);进入如下代码
     public ImageContainer get(String requestUrl, ImageListener imageListener,
                int maxWidth, int maxHeight) {
            // only fulfill requests that were initiated from the main thread.
            throwIfNotOnMainThread();
            final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
            // Try to look up the request in the cache of remote images.
            Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
            if (cachedBitmap != null) {
                // Return the cached bitmap.
                ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
                imageListener.onResponse(container, true);
                return container;
            }
            // The bitmap did not exist in the cache, fetch it!
            ImageContainer imageContainer =
                    new ImageContainer(null, requestUrl, cacheKey, imageListener);
            // Update the caller to let them know that they should use the default bitmap.
            imageListener.onResponse(imageContainer, true);
            // Check to see if a request is already in-flight.
            BatchedImageRequest request = mInFlightRequests.get(cacheKey);
            if (request != null) {
                // If it is, add this request to the list of listeners.
                request.addContainer(imageContainer);
                return imageContainer;
            }
            // The request is not already in flight. Send the new request to the network and
            // track it.
            Request<?> newRequest =
                new ImageRequest(requestUrl, new Listener<Bitmap>() {
                    @Override
                    public void onResponse(Request request,Bitmap response,boolean isFromCache) {
                        onGetImageSuccess(cacheKey, response);
                    }
                }, maxWidth, maxHeight,
                Config.RGB_565, new ErrorListener() {
                    @Override
                    public void onErrorResponse(Request request,VolleyError error) {
                        onGetImageError(cacheKey, error);
                    }
                });
            mRequestQueue.add(newRequest);
            mInFlightRequests.put(cacheKey,
                    new BatchedImageRequest(newRequest, imageContainer));
            return imageContainer;
        }

    其中throwIfNotOnMainThread为检查是否在主线程,代码如下:

    private void throwIfNotOnMainThread() {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                throw new IllegalStateException("ImageLoader must be invoked from the main thread.");
            }
        }

    可见Imageloader加载图片必须运行在主线程。

    然后getCacheKey获取key信息:如下解释是为1级缓存创建缓存key,创建方法如代码所述:

     /**
         * Creates a cache key for use with the L1 cache.
         * @param url The URL of the request.
         * @param maxWidth The max-width of the output.
         * @param maxHeight The max-height of the output.
         */
        private static String getCacheKey(String url, int maxWidth, int maxHeight) {
            return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
                    .append("#H").append(maxHeight).append(url).toString();
        }
    }
    Bitmap cachedBitmap = mCache.getBitmap(cacheKey)然后就是去1级缓存里面去都去缓存内容:
    如下ImageCache是个接口,推荐使用LRUCache来实现1级缓存:
     /**
         * Simple cache adapter interface. If provided to the ImageLoader, it
         * will be used as an L1 cache before dispatch to Volley. Implementations
         * must not block. Implementation with an LruCache is recommended.
         */
        public interface ImageCache {
            public Bitmap getBitmap(String url);
            public void putBitmap(String url, Bitmap bitmap);
            public void clear();
        }
    如果1级缓存不为null,就cachedBitmap回调给图片加载的response;
    如果1级缓存为null的话,就去加载默认图片:
    ImageContainer imageContainer =
                    new ImageContainer(null, requestUrl, cacheKey, imageListener);
    其中imageListener携带加载失败和默认图片的设置信息.
     
    后面代码就是如果这个图片url没有请求过就会去请求,通过网络的形式从服务器端拉去图片信息,并对成功失败进行处理。
     
    为了实现加载网络图片二级缓存和从从二级缓存中读取,必须重现原来的ImageLoader类,对缓存读取,加载进行重写:
    AsyncImageLoader.java
    public class AsyncImageLoader extends ImageLoader{
        /**
         * 在取的请求,可能没取到
         */
        ConcurrentHashMap<String, ReadImageRequest> readImageRequestConcurrentHashMap = new ConcurrentHashMap<>();
        // 读数据线程池,限制两个线程
        private ExecutorService readExecutorService = new ThreadPoolExecutor(0, 2, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
        //UI线程的Handler
        Handler mainHandler;
        private static AsyncImageLoader instance;
        //独立请求列队
        private static RequestQueue requestQueue;
        private AsyncImageLoader(RequestQueue queue, ImageCache imageCache) {
            super(queue, imageCache);
            mainHandler = new Handler(Looper.getMainLooper());
        }
        /**
         * 返回默认的ImageLoader,使用两级缓存,单独的请求队列
         * @return
         */
        public static AsyncImageLoader getDefaultImageLoader()
        {
            if(instance==null) {
                requestQueue=Volley.newRequestQueue(PApplication.getInstance());
                requestQueue.start();
                instance = new AsyncImageLoader(requestQueue, new L2LRUImageCache(PApplication.getInstance()));
            }
            return instance;
        }
        /**
         * 销毁,停止所有未处理请求
         */
        public void destory()
        {
            requestQueue.stop();
            instance=null;
        }
        @Override
        public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) {
            // TODO Auto-generated method stub
            throwIfNotOnMainThread();
            final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
            ImageContainer imageContainer = new ImageContainer(null, requestUrl, cacheKey, imageListener);
            ReadImageRequest readImageRequest =readImageRequestConcurrentHashMap.get(cacheKey);
            if(readImageRequest ==null){
                readImageRequest =new ReadImageRequest(imageContainer, cacheKey);
                readImageRequestConcurrentHashMap.put(cacheKey, readImageRequest);
                //去读缓存,读不到会自动转到请求网络
                readExecutorService.execute(new ReadCache(imageContainer, cacheKey,maxWidth,maxHeight));
            }else{
                //如果该请求已经存在,添加ImageContainer,不再发请求
                readImageRequest.addContainer(imageContainer);
            }
            return imageContainer;
        }
        private void throwIfNotOnMainThread() {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                throw new IllegalStateException("ImageLoader must be invoked from the main thread.");
            }
        }
        /**
         * 创建缓存的key
         *
         * @param url
         * @param maxWidth
         * @param maxHeight
         * @return
         */
        private static String getCacheKey(String url, int maxWidth, int maxHeight) {
            return new StringBuilder(url.length() + 12).append("#W").append(maxWidth).append("#H").append(maxHeight)
                    .append(url).toString();
        }
        /**
         * 读取缓存,读不到会转发给网络
         */
        class ReadCache implements Runnable {
            ImageContainer container;
            String cacheKey;
            int maxWidth, maxHeight;
            public ReadCache(ImageContainer container, String cacheKey,int maxWidth,int maxHeight) {
                this.container = container;
                this.cacheKey = cacheKey;
                this.maxWidth=maxWidth;
                this.maxHeight=maxHeight;
            }
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
                if (cachedBitmap != null) {
                    ReadImageRequest cacheRequest = readImageRequestConcurrentHashMap.get(cacheKey);
                    if (cacheRequest != null) {
                        cacheRequest.setCacheBitmap(cachedBitmap);
                        readSuccess(cacheKey);
                    }
                } else {
                    // 读不到缓存,去下载
                    mainHandler.post(new GetImageUseNetWork(container,cacheKey,maxWidth,maxHeight));
                }
            }
        }
        /**
         * 读取缓存或下载图片成功,分发结果
         * @param cacheKey
         */
        private void readSuccess(String cacheKey)
        {
            ReadImageRequest successedCacheRequest = readImageRequestConcurrentHashMap.remove(cacheKey);
            if(successedCacheRequest!=null) {
                successedCacheRequest.deliver();
            }
        }
        private void readFailure(String cacheKey,VolleyError error) {
            ReadImageRequest successedCacheRequest = readImageRequestConcurrentHashMap.remove(cacheKey);
            if(successedCacheRequest!=null) {
                successedCacheRequest.deliverError(error);
            }
        }
        class GetImageUseNetWork implements Runnable {
            ImageContainer imageContainer;
            String cacheKey;
            int maxWidth,maxHeight;
            public GetImageUseNetWork(ImageContainer imageContainer, String cacheKey,int maxWidth,int maxHeight) {
                this.imageContainer = imageContainer;
                this.cacheKey = cacheKey;
                this.maxWidth=maxWidth;
                this.maxHeight=maxHeight;
            }
            @Override
            public void run() {
                BatchedImageRequest request = mInFlightRequests.get(cacheKey);
                if (request != null) {
                    // If it is, add this request to the list of listeners.
                    request.addContainer(imageContainer);
                }
                // The request is not already in flight. Send the new request to the network and
                // track it.
                Request<?> newRequest = new ImageRequest(imageContainer.getRequestUrl(), new Response.Listener<Bitmap>() {
                    @Override
                    public void onResponse(Request request,Bitmap response,boolean isFromCache) {
                        PLog.d(this,"onResponse");
                        Bitmap bmpCompressed=ImageUtil.scaleBitmap(response, maxWidth, maxHeight);
                        ReadImageRequest cacheRequest = readImageRequestConcurrentHashMap.get(cacheKey);
                        if (cacheRequest != null) {
                            cacheRequest.setCacheBitmap(bmpCompressed);
                            //放到缓存里
                            mCache.putBitmap(cacheKey, bmpCompressed);
                            readSuccess(cacheKey);
                        }
                    }
                }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(Request request,VolleyError error) {
                        PLog.d(this,"onErrorResponse");
                        onGetImageError(cacheKey, error);
                        readFailure(cacheKey,error);
                    }
                });
                mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
                mRequestQueue.add(newRequest);
            }
        }
        /**
         * 清除缓存
         */
        public void clearCache()
        {
            mCache.clear();
        }
    }
    1,可以设置线程池的大小,这里设置为2条线程;
    2,getDefaultImageLoader里面调用上面的图片缓存的代码L2LRUImageCache;
    3,重点的读取和缓存还是在get方法里面。
     readExecutorService.execute(new ReadCache(imageContainer, cacheKey,maxWidth,maxHeight)); 

    然后看线程池里面读取缓存的逻辑:

                // TODO Auto-generated method stub
                Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
                if (cachedBitmap != null) {
                    ReadImageRequest cacheRequest = readImageRequestConcurrentHashMap.get(cacheKey);
                    if (cacheRequest != null) {
                        cacheRequest.setCacheBitmap(cachedBitmap);
                        readSuccess(cacheKey);
                    }
                } else {
                    // 读不到缓存,去下载
                    mainHandler.post(new GetImageUseNetWork(container,cacheKey,maxWidth,maxHeight));
                }

    mCache.getBitmap(cacheKey)是从一级缓存中区读取bitmap,如果一级缓存里面没有,就去下载,调用下面逻辑,拉取下来后,对图片进行裁剪,并将图片放入缓存里面去,而mCache.putBitmap(cacheKey,bmpCompressed);就是调用L2LRUImageCache 的putbitmap放来将缓存内容放入文件和内存中去。

     BatchedImageRequest request = mInFlightRequests.get(cacheKey);
                if (request != null) {
                    // If it is, add this request to the list of listeners.
                    request.addContainer(imageContainer);
                }
                // The request is not already in flight. Send the new request to the network and
                // track it.
                Request<?> newRequest = new ImageRequest(imageContainer.getRequestUrl(), new Response.Listener<Bitmap>() {
                    @Override
                    public void onResponse(Request request,Bitmap response,boolean isFromCache) {
                        PLog.d(this,"onResponse");
                        Bitmap bmpCompressed=ImageUtil.scaleBitmap(response, maxWidth, maxHeight);
                        ReadImageRequest cacheRequest = readImageRequestConcurrentHashMap.get(cacheKey);
                        if (cacheRequest != null) {
                            cacheRequest.setCacheBitmap(bmpCompressed);
                            //放到缓存里
                            mCache.putBitmap(cacheKey, bmpCompressed);
                            readSuccess(cacheKey);
                        }
                    }
                }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(Request request,VolleyError error) {
                        PLog.d(this,"onErrorResponse");
                        onGetImageError(cacheKey, error);
                        readFailure(cacheKey,error);
                    }
                });
                mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
                mRequestQueue.add(newRequest);

    代码摘自:https://github.com/pocketdigi/PLib/tree/androidstudio/src/main/java/com/pocketdigi/plib/volley,可以参考优化volley对图片的二级缓存

  • 相关阅读:
    MFC中实现LISTCRTL控件选中多行进行删除操作
    如何使属性值为“只读”(readonly)的EDIT控件在获取焦点后不显示光标?
    crm 使用stark组件
    ModelForm组件
    自定义admin管理工具(stark组件)
    Django-admin管理工具
    Django-session中间件源码简单分析
    基于角色的权限管理
    ajax参数补充
    datetime模块
  • 原文地址:https://www.cnblogs.com/androidsuperman/p/4799944.html
Copyright © 2011-2022 走看看