Android 开发中我们会经常会加载网络图片的需求,目前成熟的图片加载框架有 Fresco、Glide、Picasso, 比较老的还有UniversaclImageLoader(15年开发的时候还在用这个开源库,可惜现在已停止维护了)。由于最近想分析下Glide 的源码,之前有没有分析过图片加载框架,所以就自己参考网上简单的图片加载框架自己写了一个ImageLoader,分析下图片加载的基本原理和流程,为以后分析更复杂框架打下基础。
ImageLoader 基本框架
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXzcGVPhHNXl1bO1mYsR2MiZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jM1EzMxQDN2EDMzATM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
整个框架还是有点复杂,我们从底层往上层开始分析 。
第一部分 缓存
为了提高用户体验,提升图片的加载速度, 我们一般会将下载的图片缓存起来,从缓存的位置来分,可以分为内存缓存和本地缓存(一般是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 "";
}
}