【安卓中的缓存策略系列】安卓缓存策略之综合应用ImageLoader实现照片墙的效果

在前面的【安卓缓存策略系列】安卓缓存之内存缓存LruCache【安卓缓存策略系列】安卓缓存策略之磁盘缓存DiskLruCache这两篇博客中已经将安卓中的缓存策略的理论知识进行过详细讲解,还没看过这两篇博客的看官建议先去看一下,本博客将依据这些理论知识打造一个ImageLoader,实现照片墙的效果,关于照片墙的知识网上相关博客也很多,首先解释一下照片墙的概念:用一个GridView控件当作“墙”,然后随着GridView的滚动将一张张照片贴在“墙”上,很显然因为图片一般比较占内存,所以得考虑使用压缩与缓存策略来防止程序OOM,而完成这些核心功能的自然就是我们的ImageLoader类,当然也可以使用类似于ImageLoader的开源框架来完成这个功能,如Universal_Image_Loader。不过本博客将根据前面讲解的理论知识自己来实现一个ImageLoader类。

首先总的来说一个合格的ImageLoader应该包含如下功能:

1..图片的同步加载

2.图片的异步加载

3.图片的压缩处理

4.图片的内存缓存处理

5.图片的磁盘缓存处理

6.图片的网络拉取处理

首先图片的异步加载肯定是必须具备的,因为为了在GridView上能流畅的显示图片肯定不能用同步加载,然后为了防止程序OOM图片的压缩处理也是必须的,而网络拉取,内存缓存,磁盘缓存这时缓存策略常用的三级缓存策略,即当要使用某一张图片时,首先尝试从内存中获取,如果内存中不存在则从磁盘中缓存,如果磁盘中也不存在,则从网络上获取,当从网络上获取到该图片后先将其添加到内存缓存中,然后添加到磁盘缓存中,供下次访问时直接使用。

当然除了上述ImageLoader本身的硬性功能需求外,我们还需要考虑它与GridView交互过程中可能出现的问题,如我们在复用View时,假设某个Item X正在从网络上获取,它对应的ImageView为X,这个时候如果用户快速的向下活动GridView,则很肯能Item Y复用了ImageView X,然后等之前的图片下载完毕后,如果直接给ImageView X设置图片,则因为这个时候ImageView X被item Y复用,但是很显然item Y要显示的图片显然不是item X刚刚下载完的图片,这个时候就会出现item
Y显示了item X的图片,即出现列表错位的现象,ImageLoader应该能够正确的处理这些问题。

上述的功能大纲就是我们的整个ImageLoader的功能框架,接下来我们就按照上述介绍的功能及前面学习的理论知识亲手打造一个ImageLoader,最后还会对针对GridView中列表的卡顿现象进行一些优化处理。

一图片的同步与异步加载:

所谓图片的同步加载即在该方法中不单独开启一个线程去加载图片,而是在调用该同步方法时将该同步方法放到一个子线程中去执行,而异步加载是在该方法中开启一个子线程去加载图片,这样调用该方法时不需将其放到子线程中去执行,即该方法可以直接运行在主线程中,显然从网络上加载图片可能比较耗时,所以一般我们使用异步加载的方式。首先我们来看一下同步加载,代码如下:

 /**
     * load bitmap from memory cache or disk cache or network.
     * @param uri http url
     * @param reqWidth the width ImageView desired
     * @param reqHeight the height ImageView desired
     * @return bitmap, maybe null.
     */
    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
            return bitmap;
        }

        try {
            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
            if (bitmap != null) {
                Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
                return bitmap;
            }
            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
            Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (bitmap == null && !mIsDiskLruCacheCreated) {
            Log.w(TAG, "encounter error, DiskLruCache is not created.");
            bitmap = downloadBitmapFromUrl(uri);
        }

        return bitmap;
    }

可以看到,在该loadBitmap()的中首先尝试通过loadBitmapFromMemCache(uri)方法从内存缓存中获取图片,如果内存缓存中存在该图片对象直接返回,如果不存在,则尝试通过loadBitmapFromDiskCache(uri, reqWidth, reqHeight)从磁盘缓存中获取,如果不存在则通过loadBitmapFromHttp(uri, reqWidth, reqHeight)来从网络上获取图片,这就是安卓缓存策略中的三步缓存策略,即内存-磁盘-网络。注意该方法不能在主线程中调用,因为图片的加载属于耗时操作,可能会导致ANR异常。

接下来看一下异步加载的代码:

  public void bindBitmap(final String uri, final ImageView imageView,
            final int reqWidth, final int reqHeight) {
        imageView.setTag(TAG_KEY_URI, uri);
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        Runnable loadBitmapTask = new Runnable() {

            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
                if (bitmap != null) {
                    LoaderResult result = new LoaderResult(imageView, uri, bitmap);
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }

可以看到在异步加载中首先尝试从内存缓存中获取,如果获取到相应的图片就直接返回结果,否则会创建一个Runnable对象,然后调用线程池的execute()方法去执行该runnable对象,在Runnable的run方法中调用了同步加载图片的loadBitmap(uri, reqWidth, reqHeight)方法,在图片加载成功后将图片及图片地址Uri和需要绑定的ImageView控件包装成一个LoaderResult对象,然后通过mMainHandler向主线程发送一个消息,因为在子线程中是不能更新UI的。之所以在异步加载图片的bindBitmap(final
String uri, final ImageView imageView, final int reqWidth, final int reqHeight)方法中添加一个ImageView参数(注意:该参数在同步加载图片的loadBitmap(String uri, int reqWidth, int reqHeight)方法中不存在),这是因为异步加载图片才是ImageLoader整个加载图片对外提供的接口,而同步加载是ImageLoader内部调用的接口,为了让ImageLoader与外部其它类最大程度的解耦,所以直接提供一个根据Uri与ImageView这两个核心参数来设置图片的功能,即将网络地址为Uri的图片设置到ImageView这个控件上去。如果不提供该参数,则只能像同步加载那样获取到一个Bitmap对象,然后通过ImageView的setImageBitmap()方法来为ImageView控件设置图片,而很显然前面一种方式较好。

另外可以看到在异步加载图片时采用了线程池,首先肯定不能采用普通的线程去完成该功能,因为照片墙的原理是当用户滑动GridView列表时会从网络上去获取图片资源,即调用该异步加载的bindBitmap方法,如果采用普通的线程去完成该功能,则在列表滑动的过程中会产生大量的线程,这显然是不利于整体效率的提高,而线程池可以复用线程,使线程的数量始终维持在一个合理的范围之内。也没采用AsyncTask类是因为从安卓3.0开始默认情况下AsyncTask是串行执行的,这显然是不符合要求的。

二图片的压缩处理:

关于图片的压缩处理的理论知识,请参看我的博客:安卓图片缓存技术这里我们将图片的压缩处理功能单独的抽象为一个类ImageResizer,其代码如下:

public class ImageResizer {
    private static final String TAG = "ImageResizer";

    public ImageResizer() {
    }

    public Bitmap decodeSampledBitmapFromResource(Resources res,
            int resId, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

    public int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        if (reqWidth == 0 || reqHeight == 0) {
            return 1;
        }

        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        Log.d(TAG, "origin, w= " + width + " h=" + height);
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and
            // keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        Log.d(TAG, "sampleSize:" + inSampleSize);
        return inSampleSize;
    }
}

图片压缩功能的代码的理论讲解请参考我的博客:安卓图片缓存技术,在此不再赘述。

三内存缓存与磁盘缓存功能及网络拉取功能:(磁盘缓存的添加是在网络拉取功能中完成的)

正如前面理论知识介绍的使用LruCach与DiskLlruCache来完成内存缓存与磁盘缓存的功能,在ImageLoader初始化的时候,会创建LruCache与DiskLruCache,代码如下:

 private Context mContext;
    private ImageResizer mImageResizer = new ImageResizer();
    private LruCache<String, Bitmap> mMemoryCache;
    private DiskLruCache mDiskLruCache;

    private ImageLoader(Context context) {
        mContext = context.getApplicationContext();
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
                        DISK_CACHE_SIZE);
                mIsDiskLruCacheCreated = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

在创建磁盘缓存时首先作了一个判断,即可用磁盘空间是否大于磁盘缓存所需的空间大小,如果小于则表示用户手机上的内存空间不足以创建该大小的磁盘缓存,一般情况下我们将内存缓存的容量定义为当前进程可用内存的1/8,磁盘缓存容量定为50MB就足够。

在创建完内存缓存与磁盘缓存后还需要提供一下方法来完成缓存的添加与获取等功能,首先看内存缓存,代码如下:

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

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

这个在前面博客的理论知识部分已经进行过详细讲解,在此不再赘述。

再来看一下磁盘缓存:首先看一下磁盘缓存的添加功能:代码如下:

 private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
            throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network from UI Thread.");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        String key = hashKeyFormUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

注意磁盘缓存的添加是在从网络上获取到图片时添加的,而内存缓存的添加是在磁盘缓存添加完成时添加的,可以看到磁盘缓存的添加需要通过 DiskLruCache.Editor这个类来完成,Editor提供了commit()与abort()方法来提交和撤销对磁盘文件系统的写操作,具体理论知识请参看我的博客:【安卓缓存策略系列】安卓缓存策略之磁盘缓存DiskLruCache

在来看一下磁盘缓存的获取,代码如下:

  private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
            int reqHeight) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }

        return bitmap;
    }

可以看到磁盘缓存的读取是通过DiskLruCache.Snapshot这个类来完成的,通过Snapshot可以得到磁盘缓存对象对应的FileInputStream,但是FileInputStream不能很好的进行图片的压缩处理,因此通过FileDescriptor来加载压缩后的图片,最后将加载后的图片添加到内存缓存中,注意内存缓存的添加是在磁盘缓存添加完成时添加的。即三级缓存策略的获取与添加过程为网络-磁盘-内存。

四滑动GridView时列表图片显示错位的解决方案:

private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.imageView;
            String uri = (String) imageView.getTag(TAG_KEY_URI);
            if (uri.equals(result.uri)) {
                imageView.setImageBitmap(result.bitmap);
            } else {
                Log.w(TAG, "set image bitmap,but url has changed, ignored!");
            }
        };
    };

前面我们在bindBitmap中的Runnable对象中通过 mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget()发送一个消息,该消息是一个包含图片Uri,bitmap图片,显示控件ImageView的LoaderResult对象,该消息交给mMainHandler的handleMessage()方法来处理,在该方法中主要逻辑是获取到传递过来的LoaderResult对象,然后通过LoaderResult对象获取到bitmap图片将其显示在对应的ImageView控件上,正如我们在前面所说的,当用户快速滑动GridView列表时可能导致列表错位现象,因此在设置bitmap图片时首先需要做个判断,即在给ImageView设置图片之前先判断接受到的uri与该图片的Uri是否相同,如果相同则将其设置为该ImagView控件的图片,否则不处理。

另外我们可以看到mMainHandler的创建是直接通过主线程的Looper来构造的Handler对象,这样我们在子线程中就可以直接构造ImageLoader对象。而不需要在子线程中创建Looper对象。

五针对GridView中列表卡顿现象的优化处理:

首先,不要再getView中执行耗时操作,即在getView中必须通过异步的方式加载图片。

其次,控制异步任务的执行频率,对于列表而言,仅仅在getView中采用异步操作是不够的,以照片墙来说,在getView方法中会通过ImageLoader的binBitmap方法来异步加载图片,但是如果用户刻意的频繁上下滑动,这会在瞬时产生上百个异步任务,这些异步任务会造成线程池的拥堵和大量的UI更新操作,这是没有意义的,但因为瞬时产生大量的UI更新操作,这些UI操作是运行在主线程中的,会造成一定程度上的卡顿,那如何解决呢?可以考虑在列表滑动的时候停止加载图片,等列表停止滑动后再加载图片,这样仍可以获得良好的用户体验,具体实现时,可以给ListView或GridView设置setOnScrollListener,,在 OnScrollListener的onScrollStateChanged方法中判断列表是否处于滑动状态,如果是则停止加载图片,代码如下:

public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
            mIsGridViewIdle = true;
            mImageAdapter.notifyDataSetChanged();
        } else {
            mIsGridViewIdle = false;
        }
    }

在getView方法中,仅当列表静止时才能加载图片,代码如下:

 if (mIsGridViewIdle && mCanGetBitmapFromNetWork) {
                imageView.setTag(uri);
                mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth);

最后给出整个ImageLoader的实现代码:

public class ImageLoader {

    private static final String TAG = "ImageLoader";

    public static final int MESSAGE_POST_RESULT = 1;

    private static final int CPU_COUNT = Runtime.getRuntime()
            .availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final long KEEP_ALIVE = 10L;

    private static final int TAG_KEY_URI = R.id.imageloader_uri;
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
    private static final int IO_BUFFER_SIZE = 8 * 1024;
    private static final int DISK_CACHE_INDEX = 0;
    private boolean mIsDiskLruCacheCreated = false;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
        }
    };

    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
            KEEP_ALIVE, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(), sThreadFactory);

    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.imageView;
            String uri = (String) imageView.getTag(TAG_KEY_URI);
            if (uri.equals(result.uri)) {
                imageView.setImageBitmap(result.bitmap);
            } else {
                Log.w(TAG, "set image bitmap,but url has changed, ignored!");
            }
        };
    };

    private Context mContext;
    private ImageResizer mImageResizer = new ImageResizer();
    private LruCache<String, Bitmap> mMemoryCache;
    private DiskLruCache mDiskLruCache;

    private ImageLoader(Context context) {
        mContext = context.getApplicationContext();
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
                        DISK_CACHE_SIZE);
                mIsDiskLruCacheCreated = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * build a new instance of ImageLoader
     * @param context
     * @return a new instance of ImageLoader
     */
    public static ImageLoader build(Context context) {
        return new ImageLoader(context);
    }

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

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

    /**
     * load bitmap from memory cache or disk cache or network async, then bind imageView and bitmap.
     * NOTE THAT: should run in UI Thread
     * @param uri http url
     * @param imageView bitmap's bind object
     */
    public void bindBitmap(final String uri, final ImageView imageView) {
        bindBitmap(uri, imageView, 0, 0);
    }

    public void bindBitmap(final String uri, final ImageView imageView,
            final int reqWidth, final int reqHeight) {
        imageView.setTag(TAG_KEY_URI, uri);
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        Runnable loadBitmapTask = new Runnable() {

            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
                if (bitmap != null) {
                    LoaderResult result = new LoaderResult(imageView, uri, bitmap);
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }

    /**
     * load bitmap from memory cache or disk cache or network.
     * @param uri http url
     * @param reqWidth the width ImageView desired
     * @param reqHeight the height ImageView desired
     * @return bitmap, maybe null.
     */
    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
            return bitmap;
        }

        try {
            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
            if (bitmap != null) {
                Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
                return bitmap;
            }
            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
            Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (bitmap == null && !mIsDiskLruCacheCreated) {
            Log.w(TAG, "encounter error, DiskLruCache is not created.");
            bitmap = downloadBitmapFromUrl(uri);
        }

        return bitmap;
    }

    private Bitmap loadBitmapFromMemCache(String url) {
        final String key = hashKeyFormUrl(url);
        Bitmap bitmap = getBitmapFromMemCache(key);
        return bitmap;
    }

    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
            throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network from UI Thread.");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        String key = hashKeyFormUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
            int reqHeight) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }

        return bitmap;
    }

    public boolean downloadUrlToStream(String urlString,
            OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),
                    IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (IOException e) {
            Log.e(TAG, "downloadBitmap failed." + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            MyUtils.close(out);
            MyUtils.close(in);
        }
        return false;
    }

    private Bitmap downloadBitmapFromUrl(String urlString) {
        Bitmap bitmap = null;
        HttpURLConnection urlConnection = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),
                    IO_BUFFER_SIZE);
            bitmap = BitmapFactory.decodeStream(in);
        } catch (final IOException e) {
            Log.e(TAG, "Error in downloadBitmap: " + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            MyUtils.close(in);
        }
        return bitmap;
    }

    private String hashKeyFormUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    public File getDiskCacheDir(Context context, String uniqueName) {
        boolean externalStorageAvailable = Environment
                .getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        final String cachePath;
        if (externalStorageAvailable) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }

        return new File(cachePath + File.separator + uniqueName);
    }

    @TargetApi(VERSION_CODES.GINGERBREAD)
    private long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        final StatFs stats = new StatFs(path.getPath());
        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
    }

    private static class LoaderResult {
        public ImageView imageView;
        public String uri;
        public Bitmap bitmap;

        public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
            this.imageView = imageView;
            this.uri = uri;
            this.bitmap = bitmap;
        }
    }
}

注:本博文为本人阅读安卓开发艺术探索这本书的读书记录,绝大部分内容来自该书,中间融合了本人自己的思考过程,如果看官觉得不错记得顶个赞哦

时间: 2024-10-02 21:03:36

【安卓中的缓存策略系列】安卓缓存策略之综合应用ImageLoader实现照片墙的效果的相关文章

安卓中的消息循环机制Handler及Looper详解

我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handler机制来完成该功能,即当子线程中耗时操作完成后,在子线程中通过Handler向主线程发送消息,在主线程中的Handler的handleMessage方法中处理接受到的消息.这就是安卓中的消息机制,安卓中的消息机制主要是指Handler的运行机制,但是Handler的运行需要底层的MessageQu

【安卓中的缓存策略系列】安卓缓存策略之磁盘缓存DiskLruCache

安卓中的缓存包括两种情况即内存缓存与磁盘缓存,其中内存缓存主要是使用LruCache这个类,其中内存缓存我在[安卓中的缓存策略系列]安卓缓存策略之内存缓存LruCache中已经进行过详细讲解,如看官还没看过此博客,建议看官先去看一下. 我们知道LruCache可以让我们快速的从内存中获取用户最近使用过的Bitmap,但是我们无法保证最近访问过的Bitmap都能够保存在缓存中,像类似GridView等需要大量数据填充的控件很容易就会用完整个内存缓存.另外,我们的应用可能会被类似打电话等行为而暂停导

大型网站架构系列:缓存在分布式系统中的应用(三)

本文是<缓存在分布式系统中的应用>第三篇文章. 上次主要给大家分享了,缓存在分布式系统中的应用,主要从不同的场景,介绍了CDN,反向代理,分布式缓存,本地缓存的常规架构和基本原理. 因为时间关于,原计划分享<缓存常见问题>的内容,没有讲.本次主要针对缓存的常见个问题,做一个介绍.主要有以下议题: 一.分享大纲 分享大纲 数据一致性 缓存高可用 缓存雪崩 缓存穿透 参考资料 分享总结 二.数据一致性 缓存是在数据持久化之前的一个节点,主要是将热点数据放到离用户最近或访问速度更快的介质

安卓中的内存泄漏

因为安卓是基于java语言的,所以我们先来看一看java中的内存泄漏,然后在此基础上来谈谈安卓中的内存泄漏. 一java中的内存泄漏: java中的内存泄漏主要是指在堆中分配的内存,明明已经不需要的时候,还仍然保留着访问它的引用,导致GC回收不能及时回收(关于GC回收不做过多赘述),导致这种情况出现的最主要原因是长生命周期的对象持有短生命周期对象的引用,导致短生命周期的对象明明已经不需要却无法被GC回收,从而导致内存泄漏.主要包括以下几种情况: 1在一个类中创建了一个非静态内部类的静态实例,如下

安卓中实现界面数据懒加载

大家在使用手机新闻客户端的时候就会有一个发现,大多数的新闻客户端都会把新闻分类,诸如头条.娱乐.体育.科技等等,如何实现这种界面的呢?这个实现起来其实很简单,就是在一个Fragment中实现多个ViewPage的切换,再在ViewPage的上面放一个TabLayout,关联起来就可以实现联动效果.如果大家感觉不太明了的话,以后我可以专门写一篇关于Fragment中放入多个ViewPage的博客,今天,我主要介绍的是怎样实现界面即Fragment的懒加载.那么,大家就会奇怪了既然是加载界面直接加载

安卓中常用权限

安卓中常用权限 添加WiFi以及访问网络的权限:  <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" ></uses-permission>  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" ></uses-permission>  &l

[原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

【用PS3手柄在安卓设备上玩游戏系列】FC(任天堂NES/FC主机)模拟器

NESoid 是安卓系统下公认最好的FC模拟器.据我所知,现在安卓系统下面的绝大部分的FC模拟器,都是基于 NESoid 的内核来开发的. 官方网站:http://www.nesoid.com NESoid 是原生支持实体手柄的,下面以<超级魂斗罗>为例说明我的设置步骤: Step1:运行 SixaxisController(以下简称 SC),连接手柄和设备,我的设备是小米2: Step2:扩展菜单按钮 > 设置 > 手柄设置,勾选"启用手柄",然后选择&quo

Android客户端中Bitmap的下载过程和缓存机制

加载流程: if(内存命中){ 从内存中读取 }else{ create AsyncTasks,task中的多个Runnable是通过堆栈先进后出的方式来调度,而非队列式的先进先出,目的是最先加载用户最近划到或打开的图片. } AsyncTask: //do in background——该后台进程在用户scroll列表的时候会暂停,从而减小了列表划动时cpu的overhead,此方法也被ImageLoader和facebook的官方app所使用. if(磁盘缓存命中){ 从缓存中读取 }els