Android 图片加载及缓存技术解析

来源:互联网 发布:单片机usb通讯 编辑:程序博客网 时间:2024/05/21 17:49

写的一个图片缓存的demo,包括内存缓存和硬盘缓存,加载大量图片的时候感觉效果还是挺好的。


直接上代码吧:

package com.hongri.recyclerview.fragment;import android.os.Bundle;import android.support.annotation.Nullable;import android.support.v4.app.Fragment;import android.support.v7.widget.GridLayoutManager;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import com.hongri.recyclerview.R;import com.hongri.recyclerview.adapter.DetailLoadPicsTestAdapter;import com.hongri.recyclerview.common.APPConstants;import com.hongri.recyclerview.utils.DataUtil;/** * @author:zhongyao on 2016/7/22 10:52 * @description:网络加载图片性能测试 */public class DetailLoadPicsTestFragment extends Fragment{    private static DetailLoadPicsTestFragment loadPicsTestFragment;    private RecyclerView rv;    private DetailLoadPicsTestAdapter mAdapter;    private DetailLoadPicsTestFragment(){}    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        View view = inflater.inflate(R.layout.fragment_detail_loadpicstest,container,false);        initView(view);        return view;    }    private void initView(View view) {        rv = (RecyclerView) view.findViewById(R.id.rv);        rv.setLayoutManager(new GridLayoutManager(getActivity(), APPConstants.Column_Nums));        mAdapter = new DetailLoadPicsTestAdapter(getActivity(), DataUtil.getImageThumbUrls());        rv.setAdapter(mAdapter);    }    public static Fragment newInstance() {        if (loadPicsTestFragment == null){            synchronized (DetailLoadPicsTestFragment.class){                if (loadPicsTestFragment == null){                    loadPicsTestFragment = new DetailLoadPicsTestFragment();                }            }        }        return loadPicsTestFragment;    }}

package com.hongri.recyclerview.adapter;import android.content.Context;import android.graphics.Bitmap;import android.os.Handler;import android.support.v7.widget.RecyclerView;import android.support.v7.widget.RecyclerView.ViewHolder;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import com.hongri.recyclerview.R;import com.hongri.recyclerview.cache.BitmapCache;import com.hongri.recyclerview.cache.BitmapDiskCache;import com.hongri.recyclerview.cache.ImageWorker;import com.hongri.recyclerview.https.HttpRequest;import com.hongri.recyclerview.https.IHttpRequest;import com.hongri.recyclerview.utils.Logger;import java.util.ArrayList;/** * @author:zhongyao on 2016/7/22 11:09 * @description: */public class DetailLoadPicsTestAdapter extends RecyclerView.Adapter<DetailLoadPicsTestAdapter.LoadPicsViewHolder> {    private Context context;    private ArrayList<String> imageUrls;    private LayoutInflater inflater;    private Handler mHandler = new Handler();    public DetailLoadPicsTestAdapter(Context context, ArrayList<String> imageUrls) {        this.context = context;        this.imageUrls = imageUrls;        this.inflater = LayoutInflater.from(context);    }    @Override    public LoadPicsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        View view = inflater.inflate(R.layout.fragment_detail_loadpicstest_item, parent, false);        return new LoadPicsViewHolder(view);    }    @Override    public void onBindViewHolder(final LoadPicsViewHolder holder, final int position) {        /**         * 使用自己写的内存缓存+SD卡缓存(貌似效果甚至比使用DiskLruCache的效果好,这就比较蛋疼了,没看出DiskLruCache的优点)         */        ImageWorker.loadImage(holder.iv,imageUrls.get(position) ,mHandler);    }    @Override    public int getItemCount() {        return imageUrls.size();    }    public class LoadPicsViewHolder extends ViewHolder {        private ImageView iv;        public LoadPicsViewHolder(View itemView) {            super(itemView);            iv = (ImageView) itemView.findViewById(R.id.iv);        }    }}

以上就是使用RecyclerView实现的GridView,真正加载缓存图片的是:ImageWorker.loadImage(holder.iv,imageUrls.get(position),mHandler);这行代码。

下面我们着重介绍ImageWorker等类,内存缓存用的是LruCache类,sd卡缓存是自己写的,测试效果貌似不比用DiskLruCache差。(最后我会给出demo,demo中也用到了DiskLruCache做对比,有兴趣的可以自己测试下性能,对比下,欢迎交流)。

package com.hongri.recyclerview.cache;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Environment;import android.os.Handler;import android.support.v4.util.LruCache;import android.widget.ImageView;import com.hongri.recyclerview.https.DownloadImageFromNetwork;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.InputStream;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 图片缓存类ImageWorker: * 与ImageTask类相似 */public class ImageWorker {private static final int lruSizeMaxSize = 10*1024*1024;// 图片内存缓存最大空间10Mpublic final static LruCache<String,Bitmap> mLruCache = new LruCache<String,Bitmap>(lruSizeMaxSize);private static ExecutorService mExecutorService = null;// 下载图片的线程管理类private static final int threadPoolMaxSize = 6;// 同时下载图片的 最大线程数private ImageWorker() {}private static ImageWorker mImageWorker = null;private static String bitmapPath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/zhong/";// 图片在硬盘中的存储地址/** * 初始化,使用单例获得ImageWorker1的对象 */public static synchronized ImageWorker getImageWorkerInstance(){if(mImageWorker == null){mImageWorker = new ImageWorker();mExecutorService = Executors.newFixedThreadPool(threadPoolMaxSize);}return mImageWorker;}/** * (简单来说)加载图片: *  1 先从内存缓存中查找,如果没有则执行下一步。 *  2 从磁盘中查找,如果没有执行下一步。 *  3从网络下载 *//** * 1、首先判断内存缓存是否为空(不为空直接显示) * 2、内存缓存为空时,再从磁盘中读取后,将图片添加到内存缓存中(然后显示) * 3、磁盘为空时,只能重新网络加载图片(下载下来的图片,会在内存缓存、磁盘中各存储一份,然后显示) * @param mImageView * @param imgUrl * @param mHandler */public static void loadImage(ImageView mImageView, String imgUrl, Handler mHandler) {getImageWorkerInstance();Bitmap bitmapCache = getBitmapFromCache(imgUrl);if(bitmapCache!=null){mImageView.setImageBitmap(bitmapCache);}else {Bitmap bitmapDisk = getBitmapFromDisk(imgUrl);if(bitmapDisk!=null){addBitmapToLruCache(imgUrl,bitmapDisk);mImageView.setImageBitmap(bitmapDisk);}else{loadImageFromNetwork(mImageView,imgUrl,mHandler,mLruCache,bitmapPath);}}}private static void loadImageFromNetwork(ImageView mImageView, String imgUrl, Handler mHandler, LruCache<String, Bitmap> mLruCache, String bitmapPath) {mExecutorService.submit(new DownloadImageFromNetwork(mImageView,imgUrl,mHandler,mLruCache,bitmapPath));}private static void addBitmapToLruCache(String imgUrl, Bitmap bitmapDisk) {mLruCache.put(imgUrl,bitmapDisk);}private static Bitmap getBitmapFromDisk(String imgUrl) {InputStream inputStream = null;String[] fileNames = imgUrl.split("/");String fileName = fileNames[fileNames.length-1];try {inputStream = new FileInputStream(bitmapPath+fileName);} catch (FileNotFoundException e) {e.printStackTrace();}Bitmap bitmap = BitmapFactory.decodeStream(inputStream);return bitmap;}/** * 第一步:尝试从缓存中获取图片 * @param imgUrl * @return */private static Bitmap getBitmapFromCache(String imgUrl) {Bitmap bitmap = mLruCache.get(imgUrl);return bitmap;}}

说的很清晰了,还是那一套:先内存找,没有则sd,sd没有,则网络加载。

这里用到了ExecutorService线程管理类,可以设置启动最大的线程数,线程对象可重复利用,避免过多的重复创建线程对象,浪费内存空间。

package com.hongri.recyclerview.https;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Handler;import android.support.v4.util.LruCache;import android.widget.ImageView;import java.io.BufferedOutputStream;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;/** * @author:zhongyao on 2016/7/11 10:39 * @description:从网络下载图片,并将下载好的图片存储到内存缓存、SD卡缓存中 */public class DownloadImageFromNetwork implements Runnable {    private ImageView mImageView;    private String imgUrl;    private Handler mHandler;    private LruCache<String, Bitmap> mLruCache;    private String bitmapPath;    public DownloadImageFromNetwork(ImageView mImageView, String imgUrl,                                    Handler mHandler, LruCache<String, Bitmap> mLruCache, String bitmapPath) {        this.mImageView = mImageView;        this.imgUrl = imgUrl;        this.mHandler = mHandler;        this.mLruCache = mLruCache;        this.bitmapPath = bitmapPath;    }    @Override    public void run() {        try {            InputStream inputStream;            URL url = new URL(imgUrl);            HttpURLConnection conn = (HttpURLConnection) url.openConnection();            conn.setRequestMethod("GET");            conn.setReadTimeout(10 * 1000);            conn.setConnectTimeout(10 * 1000);            conn.setDoInput(true);            conn.connect();// Starts the query            int responseCode = conn.getResponseCode();            if (responseCode == 200) {                inputStream = conn.getInputStream();                if (inputStream != null) {                    final Bitmap bitmapNetwork = BitmapFactory.decodeStream(inputStream);                    mHandler.post(new Runnable() {                        @Override                        public void run() {                            mImageView.setImageBitmap(bitmapNetwork);                        }                    });                    /**                     * 此时输入流inputStream不存在了(输入流使用一次后就没有了),                     * 所以以下两行传送的代码是Bitmap对象                     */                    addBitmapToCache(imgUrl, bitmapNetwork);                    addBitmapToDisk(imgUrl, bitmapNetwork);                }            }        } catch (Exception e) {            e.printStackTrace();        }    }    private void addBitmapToDisk(String imgUrl, Bitmap bitmapNetwork) {        OutputStream outputStream = null;        BufferedOutputStream bos = null;        InputStream inputStream = null;        File file = new File(bitmapPath);        if (!file.exists()) {            boolean result = file.mkdirs();        }        try {            String[] fileNames = imgUrl.split("/");            String fileName = fileNames[fileNames.length - 1];            outputStream = new FileOutputStream(bitmapPath + fileName);            bos = new BufferedOutputStream(outputStream);            /**             * 以下三行的代码表示:             * 将Bitmap对象转换为inputStream             */            ByteArrayOutputStream baos = new ByteArrayOutputStream();            bitmapNetwork.compress(Bitmap.CompressFormat.JPEG, 100, baos);            inputStream = new ByteArrayInputStream(baos.toByteArray());            byte[] data = new byte[1024];            while (inputStream.read(data) != -1) {                bos.write(data);            }        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                bos.close();                outputStream.close();                inputStream.close();            } catch (Exception e) {                e.printStackTrace();            }        }    }    private void addBitmapToCache(String imgUrl, Bitmap bitmapNetwork) {        mLruCache.put(imgUrl, bitmapNetwork);    }}

该类需要注意的是,下载完图片并展示的时候,需要将其加载到内存和sd卡缓存中。以后再次加载的时候直接get就可以了。

效果图:


github上的demo:点击查看源码

2 0
原创粉丝点击