首先,我们看一下效果图:

android原生缩略图 安卓手机图库缩略图_UI

android原生缩略图 安卓手机图库缩略图_缓存_02

项目地址这里

图一和图二所展示的图片均为缩略图,我们对于其实现方式分别进行分析。

一、LruCache类:

官方地址

LruCache类是一个缓存的策略类,简单理解就是它会维持一个对列,有一个上限,当超过上限的时候,会优先清除掉最近最少使用的内容,以维持一个合理的缓存,我们这里主要对bitmap进行缓存处理。

使用:

初始化cache之前要确定当前可以使用的大小,官方给出的默认值为当前可用的cache的1/8,所以我们在初始化的时候要定义一下cache的大小,代码如下:

int maxMemory = (int) Runtime.getRuntime().maxMemory();//获得运行的内存大小
        int cacheSize = maxMemory / 8;
        mCache = new LruCache<String, Drawable>(cacheSize){//我们这里对图片处理
            @Override
            protected int sizeOf(String key, Drawable value) {
                BitmapDrawable bitmapDrawable = (BitmapDrawable) value;
                Bitmap bitmap = bitmapDrawable.getBitmap();
                return bitmap.getByteCount();
            }
        };

这里sizeOf返回的是键值对的条目大小,壁纸这里返回的是当前设置的drawable的bitmap大小;作为每个item的大小规格。

而且在具体使用的时候,我们的key为每张图片的path,value是每张图片的drawable对象。

LurCache的实现原理:

逻辑是维护一个缓存对象列表,对其中对象的排列方式按照访问顺序实现,一直没有访问的放在队尾,最先被淘汰;而最近访问的放在队头,最后被淘汰。它所依赖的对列是一个LinkedHashMap,排列方式按照<key, value>进行;LinkedHashMap是一个双向链表的结构。

二、AsyncTask类:

AsyncTask是一个Handler的包装类,主要通过Thread和Handler来实现多线程的通信,在子线程中执行耗时操作,然后在执行完成之后通过Handler发送Message消息给主线程(UI线程)来更新UI。而AsyncTask好用之处是它已经提供给了我们需要的各个时期的方法,我们可以直接拿来使用,非常方便,避免手动处理异步线程带来的问题。

1.AsyncTask的泛型参数:

Params:在执行AsyncTask时传入的参数,用于后台任务中的使用。

Progress:后台任务执行的时候,在界面上显示的进度。

Result:任务执行完毕之后,需要对结果进行返回,这里是执行返回值的类型。

2.几个方法:

onPreExecute():这个方法在后台任务执行之前调用,用于对界面进行一些初始化操作。

doInBackground(Params...):这个是启动一个子线程,在其中执行耗时操作,任务一旦完成,通过return语句来讲任务的执行结果返回;如果执行的Result为void,那么就不会返回任务的执行结果。他不会直接对UI进行调用,会调用publishProgress(Progress...)来完成。

onProgressUpdate(Progress...):当调用了publishProgress(Progress...)方法后,publishProgress方法会调用该方法,从而对UI进行操作。

onPostExecute(Result):当后台任务执行完毕并且通过Result参数返回时,调用该方法,然后利用返回的数据进行一些UI操作。

3.具体使用:

我们这里定义一个AsyncLoadImageTask类来对Bitmap进行异步加载,代码如下:

public class AsyncLoadImageTask extends AsyncTask<Integer, Void, Drawable>{//因为要显示bitmap缩略图,所以返回的是一个drawable对象

        private String url = null;
        private final WeakReference<ImageView> mImageViewWeakReference;
        private ImageView mImageView;

        public AsyncLoadImageTask(ImageView imageView){//传入的是ImageView
            this.mImageView = imageView;
            mImageViewWeakReference = new WeakReference<ImageView>(imageView);
        }

        @Override
        protected Drawable doInBackground(Integer... integers) {//这里主要是对缩略图的压缩操作
            Drawable drawable = null;
            if (isCancelled()){
                return null;
            }
            if (integers[0] >= mList.size()){
                this.cancel(true);
            }else {
                Constants.debug("integers[0] : " + integers[0]);
                this.url = mList.get(integers[0]).getPath();
                drawable = getBitmapFromUrl(this.url);
                Constants.mDList.add(integers[0], drawable);
                if (drawable != null){
                    mCache.put(this.url, drawable);
                }
            }
            return drawable;
        }

        @Override
        protected void onPostExecute(Drawable drawable) {//这里执行缩略图的返回值,更新UI
            if (isCancelled()){
                drawable = null;
            }
            if (mImageViewWeakReference != null){
                if (drawable != null && mImageView != null){
                    mImageView.setImageDrawable(drawable);//这里更新UI的具体代码
                }
            }
            super.onPostExecute(drawable);
        }
    }

我们这里用到了WeakReference这个java类,他的主要作用是建立一种弱连接,以防止内存泄漏的出现。我们这里将当前的imageView建立一个弱连接,如果他本身为空的时候被GC掉,那么依赖他的方法体或者对象会自动判断是否为null,从而避免了内存泄漏情况的出现。【可以参考这个博客的内容】

三、Bitmap的压缩策略

在加载的时候,我们先获得FileInputStream对象,然后用FileDescriptor获得FD,getFD返回的是File输入流,在这基础上我们使用BitmapFactory.Options的decodeFileDescription获得bitmap对象。

代码如下:

private static Bitmap decodeSampleFromFilePath(String filePath, int reqWidth, int reqHeight){
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(filePath);//fileInputStream对象
        }catch (Exception e){
            e.printStackTrace();
        }
        FileDescriptor fileDescriptor = null;
        if (fileInputStream == null){
            return null;
        }
        try {
            fileDescriptor = fileInputStream.getFD();//FD
        }catch (Exception e){
            e.printStackTrace();
        }
        final BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap bitmap = null;
        try {
            options.inJustDecodeBounds = true;//设置为true,返回的是图片本身的宽高信息,而且设置为true的时候是不会加载到内存中,不占用内存
            BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
            options.inSampleSize = calculateRatioSize(options, reqWidth, reqHeight);
            options.inJustDecodeBounds = false;
            bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        }catch (Exception e){
            e.printStackTrace();
            options.inSampleSize += 1;
            options.inJustDecodeBounds = false;
            options.inPreferredConfig = Bitmap.Config.RGB_565;//支持的颜色
            bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        }
        return bitmap;
    }


计算需要的比例大小:

private static int calculateRatioSize(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){
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth){
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }

原来觉得bitmap这一块很难懂,其实静下来好好分析一下,也不是特别难~