图片的三级缓存实现(LruCache内存中的高效引用、AsyncTask的封装)

Android系统为我们提供了一个封装好的类,该类能够在去完成后台的操作的同时去更新UI,大家都知道,一般执行后台耗时的操作都会放在一个子线程中执行,更新UI的操作则需在主线程或UI线程中执行(UI线程实际上是在主线程中实现的),所以我们不得不通过Android的消息处理机制在合适的时机去通知主线程执行UI的操作,所以就出现了这个封装好了的AsyncTask类来帮助我们方便的完成上述的操作。

AsyncTask类中封装好了线程和消息处理器Handler,官方文档:AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

大概意思是:AsyncTask类能够让我们在使用UI线程的时候更加方便和容易,这个类能够让我们不直接操作线程和消息处理器Handler的前提下去完成后台的操作,并将执行的结果返回到UI线程中去完成界面的更新,所以很明显,他内部已经为咱们封装好了,上代码:

package com.example.threelevelcache;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import android.widget.ImageView;

import com.example.threelevelcache.utils.Md5Utils;

public class BitmapUtils {

    private final int LOAD_PICTURE_SUCCESS = 0;

    int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);

    // 存放缓存图片文件的目录
    private File dir;

    private Context context ;

    public BitmapUtils(Context context) {

        this.context = context ;

        dir = new File("mnt/sdcard/bitmapFile");

        if (!dir.exists()) {
            dir.mkdirs();
        }


    }

    /**
     * android 2.3之后,supportv4包中提供了一个缓存的静态类LruCache
     */
    private LruCache lruCache = new LruCache(
            maxSize) {

        protected int sizeOf(String key, Bitmap value) {

            return value.getRowBytes() * value.getHeight() / 1024;
        };
    };

    /**
     * 执行异步任务的、继承了AsyncTask的类
     * 
     * @author Administrator
     *
     */
    private class MyAsyncTask extends
            AsyncTask {

        @Override
        protected void onPreExecute() {

            super.onPreExecute();

        }

        @Override
        protected BitmapImageBounding doInBackground(ImageViewUrl... params) {

            String url = params[0].url;
            ImageView iv = params[0].iv;

            Bitmap bitmap = null;

            try {

                HttpClient client = new DefaultHttpClient();

                client.getParams().setParameter(
                        HttpConnectionParams.CONNECTION_TIMEOUT, 3000);

                HttpGet httpGet = new HttpGet(url);

                HttpResponse response = client.execute(httpGet);

                if (response.getStatusLine().getStatusCode() == 200) {

                    InputStream is = response.getEntity().getContent();

                    bitmap = BitmapFactory.decodeStream(is);

                    // 1.将图片保存到内存的集合中
                    lruCache.put(url, bitmap);

                    // 2.将图片保存到sd卡的文件中

                    File bitmapFile = new File(dir, getFileName(url));

                    FileOutputStream fos = new FileOutputStream(bitmapFile);

                    // 将bitmap图片压缩到sd卡文件中
                    bitmap.compress(CompressFormat.JPEG, 100, fos);

                }

            } catch (Exception e) {

                e.printStackTrace();
            }

            return new BitmapImageBounding(iv, bitmap);

        }

        @Override
        protected void onPostExecute(BitmapImageBounding result) {

            super.onPostExecute(result);

            result.iv.setImageBitmap(result.bitmap);
        }

    }

    /**
     * 创建一个类用于关联ImageView和在该ImageView中显示的图片的url
     * 
     * @author Administrator
     *
     */
    private class ImageViewUrl {

        private ImageView iv;
        private String url;

        public ImageViewUrl(ImageView iv, String url) {
            super();
            this.iv = iv;
            this.url = url;
        }

    }

    /**
     * 将图片显示到ImageView中的方法
     * 
     * @param iv
     *            界面中的某个ImageView对象
     * @param url
     *            服务器某张图片的路径
     */
    public void display(ImageView iv, String url) {

        Bitmap bitmap = null;

        bitmap = getPictureFromCache(url);

        if (bitmap != null) {

            iv.setImageBitmap(bitmap);
            return;
        }

        bitmap = getPictureFromSdcard(url);

        if (bitmap != null) {

            iv.setImageBitmap(bitmap);
            return;
        }

//      getPictureFromInternet(iv, url);

    }

    /**
     * 1.从缓存加载图片
     * 
     * @param url
     * @return
     */
    public Bitmap getPictureFromCache(String url) {

        System.out.println("当前正在从内存缓存中获取图片");

        Bitmap bitmap = null;

        // 对引用对象进行是否为空的判断
        if (lruCache != null) {

            bitmap = lruCache.get(url);

        }
        return bitmap;

    }

    /**
     * 2.从sd卡加载图片
     * 
     * @param url
     * @return
     */
    public Bitmap getPictureFromSdcard(String url) {

        System.out.println("当前正在从sd卡中获取图片");

        Bitmap bitmap = null;

        // 定位到对应的url在sd卡上已经保存成功的图片
        File bitmapFile = new File(dir, getFileName(url));

//      bitmap = BitmapFactory.decodeFile(bitmapFile.getAbsolutePath());// 从一个文件中将bitmap图片加载出来

        bitmap = decodeFile(bitmapFile.getAbsolutePath());

        // 如果从sd卡中获取到的bitmap对象不为空,则将该对象保存到内存的虚引用对象中
        if (bitmap != null) {

            lruCache.put(url, bitmap);// 从sd卡中成功加载到图片后保存到内存中
        }

        return bitmap;

    }


    /**
     * 解决从本地SDcard加载图片时可能导致OOM的问题
     * 解决方案: 先加载图片的宽高,并获取到手机屏幕的宽高,对图片的宽高进行缩放处理
     * 
     */
    private Bitmap decodeFile(String absolutePath) {

        Options opts = new Options(); 

//      设置inJustDecodeBounds为true,只去加载图片的配置信息,获取图片的大小,长宽,而不去加载图片的像素
        opts.inJustDecodeBounds = true  ;

        opts.inInputShareable = true ;

        opts.inPurgeable = true ;

        BitmapFactory.decodeFile(absolutePath, opts);


        int sampleSize = calculateSampleSize(opts);

        opts.inSampleSize = sampleSize ;

        opts.inJustDecodeBounds = false ;

        Bitmap bitmap = BitmapFactory.decodeFile(absolutePath, opts);

        return bitmap ;

    }


    /**
     * 如果图片的大小大于屏幕的大小,则获取图片缩放的比例
     * @param opts
     * @return
     */
    private int calculateSampleSize(Options opts) {

//      先读取图片的配置信息,拿到图片的宽高的值

        int sampleSize = 1 ;

        int bitmapWidth = opts.outWidth ;
        int bitmapHeight = opts.outHeight ;

        //获取屏幕的宽高,单位为像素
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();

        int screenWidth = displayMetrics.widthPixels;

        int screenHeight = displayMetrics.heightPixels;


        if(bitmapWidth > screenWidth || bitmapHeight > screenHeight){

            int scaleWidth = bitmapWidth / screenWidth ;
            int scaleHeight = bitmapHeight / screenHeight ;


            sampleSize =  scaleWidth > scaleHeight ? scaleWidth : scaleWidth ;

        }

        return sampleSize ;

    }


    /**
     * 3.从网络加载图片
     * 
     * @param iv
     * @param url
     */
    public void getPictureFromInternet(ImageView iv, String url) {

        System.out.println("当前正在从网络中获取图片");

//      要每次new出来一个继承了AsyncTask类的对象去执行execute方法,
//      不能通过在类中创建一个全局变量,并每次通过该全局变量去执行execute方法,会报出如下的异常:
//      Cannot execute task: the task has already been executed (a task can be executed only once)

         new MyAsyncTask().execute(new ImageViewUrl(iv, url));

//      mAsyncTask.execute(new ImageViewUrl(iv, url));  ---------》不能每次通过一个全局变量执行execute方法


    }

    /**
     * 创建一个类,用于绑定ImageView和在该ImageView上要显示的Bitmap对象
     * 
     * @author Administrator
     *
     */
    private class BitmapImageBounding {

        ImageView iv;

        Bitmap bitmap;

        public BitmapImageBounding(ImageView iv, Bitmap bitmap) {
            super();
            this.iv = iv;
            this.bitmap = bitmap;
        }

    }

    /**
     * 将url加密,并将加密后的字符串作为sd卡上保存该图片的文件夹的名称
     * 
     * @param url
     * @return
     */
    public String getFileName(String url) {

        return Md5Utils.encode(url);
    }

}

你可能感兴趣的:(Android基础)