天天看点

Android 框架-ImageLoader 图片加载框架

Android 开发中我们会经常会加载网络图片的需求,目前成熟的图片加载框架有 Fresco、Glide、Picasso, 比较老的还有UniversaclImageLoader(15年开发的时候还在用这个开源库,可惜现在已停止维护了)。由于最近想分析下Glide 的源码,之前有没有分析过图片加载框架,所以就自己参考网上简单的图片加载框架自己写了一个ImageLoader,分析下图片加载的基本原理和流程,为以后分析更复杂框架打下基础。

ImageLoader 基本框架

Android 框架-ImageLoader 图片加载框架

整个框架还是有点复杂,我们从底层往上层开始分析 。

第一部分 缓存

为了提高用户体验,提升图片的加载速度, 我们一般会将下载的图片缓存起来,从缓存的位置来分,可以分为内存缓存和本地缓存(一般是Data或者Cache 分区缓存)。

缓存接口

public interface BitmapCache {

    Bitmap get(Target key);

    void put(Target key, Bitmap value);

    void remove(Target key);
}
           

为了可扩展,我们定义了BitmapCache接口, 该接口中只定义了三个方法, 分别是 获取、添加、移除等,该接口中有个名字为Target 的类,该类表示一张图片的请求,类中包含的属性有Image、url、resourceId等,我们主要图图片的url 做为key。

内存缓存

内存缓存实现了缓存接口,内存缓存将解码后的Bitmap保存到Map中,下次直接从内存中加载,这样大大提升了图片重复加载的速度, 我们采用了LruCache 算法

代码如下:

public class MemoryCache implements BitmapCache {

    private LruCache<String, Bitmap> mMemoryCache;

    public MemoryCache() {

        final int totalMemory = (int) (Runtime.getRuntime().totalMemory() / );

        int cacheSize = totalMemory / ;

        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / ;
            }
        };
    }

    @Override
    public synchronized Bitmap get(Target key) {
        return mMemoryCache.get(key.imgUri);
    }

    @Override
    public synchronized void put(Target key, Bitmap bitmap) {
        mMemoryCache.put(key.imgUri, bitmap);
    }

    @Override
    public synchronized void remove(Target key) {
        mMemoryCache.remove(key.imgUri);
    }
}
           

本地缓存

如果只缓存到内存的话,下次启动应用还需要从服务器拉图片,这样即费流量用户体验又差,一次我们增加了本地缓存,这里我们采用了DiskLruCache类,同样该类也实现了缓存接口

代码如下

public class DiskCache implements BitmapCache {

    private static final String DISK_IMG_PATH = "disk_cache_path";

    private DiskLruCache mDiskLruCache;

    private static DiskCache sDiskCache;

    private DiskCache(Context context) {
        initial(context);
    }

    public static DiskCache getInstance(Context context) {
        if (sDiskCache == null) {
            synchronized (DiskCache.class) {
                if (sDiskCache == null) {
                    sDiskCache = new DiskCache(context);
                }
            }
        }
        return sDiskCache;
    }

    private void initial(Context context) {
        try {
            File cacheDir =  getDiskCacheDir(context, DISK_IMG_PATH);
            if (!cacheDir.exists()) {
                boolean success = cacheDir.mkdir();
            }
            int versionCode = context.getPackageManager().getPackageInfo(context.getPackageName(), ).versionCode;
            mDiskLruCache = DiskLruCache.open(cacheDir, versionCode, ,  *  * );
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath = null;
        try {
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
                cachePath = context.getExternalCacheDir().getPath();
            } else {
                cachePath = context.getCacheDir().getPath();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    @Override
    public Bitmap get(final Target request) {
        BitmapDecoder decoder = new BitmapDecoder() {
            @Override
            public Bitmap decodeBitmapWithOption(BitmapFactory.Options options) {
                final InputStream inputStream = getInputStream(request.imgUriMd5);
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return bitmap;
            }
        };
        return decoder.decodeBitmap(, );
    }

    @Override
    public void put(Target target, Bitmap value) {
        if (target.justCacheInMem) {
            return;
        }
        DiskLruCache.Editor editor = null;
        try {
            editor = mDiskLruCache.edit(target.imgUriMd5);
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream();
                if (writeBitmap2Disk(value, outputStream)) {
                    editor.commit();
                } else {
                    editor.abort();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void remove(Target key) {
        try {
            mDiskLruCache.remove(Md5Helper.toMD5(key.imgUriMd5));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean writeBitmap2Disk(Bitmap bitmap, OutputStream outputStream) {
        BufferedOutputStream bos = new BufferedOutputStream(outputStream,  * );
        bitmap.compress(Bitmap.CompressFormat.JPEG, , bos);
        boolean result = true;
        try {
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();
            result = false;
        } finally {
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    private InputStream getInputStream (String md5)  {
        DiskLruCache.Snapshot snapshot;
        try {
            snapshot = mDiskLruCache.get(md5);
            if (snapshot != null) {
                return snapshot.getInputStream();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
           

二级缓存

就是内存和本地同时缓存 代码如下:

public class DoubleCache implements BitmapCache {

    private DiskCache mDiskCache;

    private MemoryCache mMemoryCache = new MemoryCache();

    public DoubleCache(Context context) {
        mDiskCache = DiskCache.getInstance(context);
    }

    @Override
    public synchronized Bitmap get(Target key) {
        Bitmap bitmap = mMemoryCache.get(key);
        if (bitmap == null) {
            bitmap = mDiskCache.get(key);
            if (bitmap != null) {
                mMemoryCache.put(key, bitmap);
            }
        }
        return bitmap;
    }

    @Override
    public synchronized void put(Target key, Bitmap value) {
        mDiskCache.put(key, value);
        mMemoryCache.put(key, value);
    }

    @Override
    public synchronized void remove(Target key) {
        mDiskCache.remove(key);
        mMemoryCache.remove(key);
    }
}
           

以上是图片缓存部分接下来是图片加载部分

图片的加载

图片加载分为两大类,一类是加载网络图片,一类是加载本地图片(Sd卡、Data 分区、Asset中的等)

图片加载Factory

根据不同的uri 产生不同不同的Loder,代码如下:

public class LoaderFactory {

    private static LoaderFactory INSTANCE;

    public static final String HTTP = "http";

    public static final String FILE = "file";

    private BaseLoader mNullBaseLoader = new NullLoader();

    private static Map<String, BaseLoader> mLoaderMap = new HashMap<>();

    static {
        mLoaderMap.put(HTTP, new UrlLoader());
        mLoaderMap.put(FILE, new NativeLoader());
    }

    private LoaderFactory() {

    }

    public static LoaderFactory getInstance() {
        if (INSTANCE == null) {
            synchronized (LoaderFactory.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LoaderFactory();
                }
            }
        }
        return INSTANCE;
    }

    public BaseLoader getLoader(String schema) {
        if (mLoaderMap.containsKey(schema)) {
            return mLoaderMap.get(schema);
        }
        return mNullBaseLoader;
    }
}
           

网络图片

下载网络图片然后Decoder 成Bitmap, 代码如下

public class UrlLoader extends Loader {

    @Override
    protected Bitmap onLoadBitmap(Target request) {
        final String imageUrl = request.imgUri;
        InputStream inputStream = null;
        Bitmap bitmap = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();
            inputStream = new BufferedInputStream(connection.getInputStream());
            bitmap = BitmapFactory.decodeStream(inputStream, null, null);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtil.closeQuietly(inputStream);
            if (connection != null) {
                connection.disconnect();
            }
        }
        return bitmap;
    }
}
           

本地图片

public class NativeLoader extends AbsLoader {

    @Override
    protected Bitmap onLoadBitmap(Target request) {
        final String imagePath = Uri.parse(request.imgUri).getPath();
        File imgFile = new File(imagePath);
        if (!imgFile.exists()) {
            return null;
        }
        request.justCacheInMem = true;
        BitmapDecoder decoder = new BitmapDecoder() {
            @Override
            public Bitmap decodeBitmapWithOption(BitmapFactory.Options options) {
                return BitmapFactory.decodeFile(imagePath, options);
            }
        };
        return decoder.decodeBitmap(request.getImageWidth(), request.getImageHeight());
    }
}
           

最后就是启动线程不断的从队列中取出请求然后加载图片代码如下:

启动线程

public final class TargetQueue {

    private BlockingQueue<Target> mRequestQueue = new PriorityBlockingQueue<>();

    private AtomicInteger mAtomicInteger = new AtomicInteger();

    private static int CORE_NUM = Runtime.getRuntime().availableProcessors();

    private int mDispatchNum = CORE_NUM;

    private Executor[] mDispatchers = null;


    public TargetQueue() {

    }

    private void startDispatchers() {
        mDispatchers = new Executor[mDispatchNum];
        for (int i = ; i < mDispatchNum; i++) {
            mDispatchers[i] = new Executor(mRequestQueue);
            mDispatchers[i].start();
        }
    }


    public void addRequest(Target request) {
        if (!mRequestQueue.contains(request)) {
            request.sequenceNumber = mAtomicInteger.incrementAndGet();
            mRequestQueue.add(request);
        }
    }

    /**
     * 开始请求图片
     */
    public void start() {
        stop();
        startDispatchers();
    }

    /**
     * 停止所有的请求
     */
    public void stop() {
        if (mDispatchers != null && mDispatchers.length > ) {
            for (Executor dispatcher : mDispatchers) {
                dispatcher.interrupt();
            }
        }
    }

    public BlockingQueue<Target> getAllRequest() {
        return mRequestQueue;
    }

    public void clear() {
        mRequestQueue.clear();
    }

}
           

线程中的循环

public class Executor extends Thread {

    private BlockingQueue<Target> mTargetQueue;

    Executor(BlockingQueue<Target> targetQueue) {
        mTargetQueue = targetQueue;
    }

    @Override
    public void run() {
        while (!this.isInterrupted()) {
            try {
                final Target request  = mTargetQueue.take();
                if (request.isCancelled) {
                    continue;
                }
                String target = parseSchema(request.imgUri);
                BaseLoader imgBaseLoader = LoaderFactory.getInstance().getLoader(target);
                imgBaseLoader.loadImage(request);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private String parseSchema(String uri) {
        if (uri.contains("://")) {
            return uri.split("://")[];
        }
        return "";
    }
}