简易的Android网络图片加载器

651 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情

在项目开发中,我们加载图片一般使用的是第三方库,比如Glide,在闲暇之余突发奇想,自己动手实现一个简单的Android网络图片加载器。

首先定义API,API的定义应该简单易用,比如

imageLoader.displayImage(imageView,imagePath);

其次应该支持缓存。缓存一般是指三级缓存,先定义一个入口类ImageLoader

public ImageLoader(Activity activity) {
    this.activity = activity;
    memoryCache = new MemoryCache();
    diskCache = new DiskCache(activity);
    netCache = new NetCache(activity,memoryCache,diskCache);
}

在初始化的时候就初始化内存缓存,磁盘缓存,网络缓存三个变量,然后定义加载方法:

public void displayImage(final ImageView imageView, String url,int placeholder){
    imageView.setTag(url);
    imageView.setImageResource(placeholder);
    Bitmap bitmap;
    bitmap = memoryCache.getBitmap(url);
    if(bitmap != null){
        imageView.setImageBitmap(bitmap);
        Log.i(TAG, "从内存中获取图片");
        return;
    }
    bitmap = diskCache.getBitmap(url);
    if(bitmap != null){
        imageView.setImageBitmap(bitmap);
        memoryCache.setBitmap(url,bitmap);
        Log.i(TAG, "从磁盘中获取图片");
        return;
    }
    netCache.getBitmap(imageView,url);
}

首先将图片地址设置给ImageView的tag,防止因为ImageView复用导致图片错乱的问题。然后设置一个占位图防止图片加载过慢ImageVIew显示白板

三级缓存中从内存中加载缓存信息是最快的,所以第一步从内存缓存中查找,如果找到了就直接设置给ImageView,否则继续从磁盘缓存中查找,找到了就显示,最后实在找不到就从网络下载图片

内存缓存

public class MemoryCache {
    private LruCache<String,Bitmap> lruCache;
    public MemoryCache() {
        long maxMemory  = Runtime.getRuntime().maxMemory() / 8;
        lruCache = new LruCache<String,Bitmap>((int) maxMemory){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }
    public Bitmap getBitmap(String url) {
        return lruCache.get(url);
    }
    public void setBitmap(String url,Bitmap bitmap) {
        lruCache.put(url,bitmap);
    }
}

内存缓存比较简单,只需要将加载过的图片放入内存,然后下次加载直接获取,由于内存大小有限制,所以这里使用了LruCache算法保证缓存不会无限制增长。

磁盘缓存

对于已经缓存在磁盘上的文件,就不需要在从网络下载了,直接从磁盘读取。

public Bitmap getBitmap(String url) {
    FileInputStream is;
    String cacheUrl = Md5Utils.md5(url);
    File parentFile = new File(Values.PATH_CACHE);
    File file = new File(parentFile,cacheUrl);
    if(file.exists()){
        try {
            is = new FileInputStream(file);
            Bitmap bitmap =  decodeSampledBitmapFromFile(file.getAbsolutePath());
            is.close();
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return null;
}

考虑到多图加载的时候,如果图片太大容易OOM,所以需要对加载的图片稍作处理

public  Bitmap decodeSampledBitmapFromFile(String pathName){
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(pathName,options);
    options.inSampleSize = calculateInSampleSize(options)*2;
    options.inJustDecodeBounds = false;
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    return BitmapFactory.decodeFile(pathName,options);
}

这里降低了图片的采样,对图片的质量进行了压缩

对于本地没有缓存的图片,需要从网络下载,当获取到图片流之后,保存在本地

public void saveBitmap(InputStream inputStream, String url) throws IOException {
    String cacheUrl = Md5Utils.md5(url);
    File parentFile = new File(Values.PATH_CACHE);
    if(!parentFile.exists()){
        parentFile.mkdirs();
    }
    FileOutputStream fos = new FileOutputStream(new File(parentFile,cacheUrl));
    byte[] bytes = new byte[1024];
    int index = 0;
    while ((index = inputStream.read(bytes))!=-1){
        fos.write(bytes,0,index);
        fos.flush();
    }
    inputStream.close();
    fos.close();
}

为了防止图片url带一些非法字符导致创建文件失败,所以对url进行了md5处理

网络缓存

这里比较简单,直接从服务器加载图片信息就可以了,访问网络使用了OkHttp

public void getBitmap(final ImageView imageView, final String url) {
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder() .get().url(url).build();
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            imageView.setImageResource(R.mipmap.ic_error);
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            InputStream inputStream = response.body().byteStream();
            diskCache.saveBitmap(inputStream, url);
            activity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (url != null && url.equals(imageView.getTag())) {
                        Bitmap bitmap = diskCache.getBitmap(url);
                        memoryCache.setBitmap(url, bitmap);
                        if (bitmap != null) {
                            imageView.setImageBitmap(bitmap);
                        } else {
                            imageView.setImageResource(R.mipmap.ic_error);
                        }
                    } else {
                        imageView.setImageResource(R.mipmap.ic_place);
                    }
                }
            });
        }
    });
}

当获取到图片后,分别放入磁盘和内存缓存起来

使用

最后直接在需要加载图片的地方调用

new ImageLoader(activity).displayImage(imageView,path)