图片加载核心就那些东西,这里设计一个图片加载框架,涉及到本地加载和网络加载,内存缓存和硬盘缓存,等等
思路
在getView的时候开始框架的调用
- 配置一系列环境,包括加载策略,缓存策略,线程数量
- 调用图片显示,从而添加请求到执行队列
- 请求及转发请求,调用加载器,根据需要从本地或者网络得到图片
- 得到的图片再选择缓存策略,硬盘缓存或者内存缓存
- 最后将图片显示出来
实现的功能和用到的知识
根据用户需求可以灵活配置
支持高并发,图片加载的优先级
支持可以选择不同的加载策略,对加载策略进行扩展
二级缓存:加载图片时内存中已经加载了,则从内存中加载,不存在去外置卡中加载,外置还不存在则从网络下载
并对缓存策略可以扩展
支持从加载过程中显示默认加载图片
支持加载失败时,显示默认错误图片
图片显示自适应,从网络加载下来的图片经最佳比例压缩后显示
不能失真变形
支持请求转发,下载
用到的模式:
生产者 消费者模式
建造者模式
单例模式
模板方法模式
策略模式
用到的知识点
内存缓存 LruCache技术
硬盘缓存技术DiskLruCache技术
图片下载时请求转发
实现代码
首先是配置类,DisplayConfig和ImageLoaderConfig,这两个类主要用于显示及下载的初始化配置
//显示图片配置
public class DisplayConfig {
//默认显示的图片ID
public int loadingImage = -1;
public int failedImage = -1;
}
//图片下载配置
public class ImageLoaderConfig {
//缓存策略
private BitmapCache bitmapCache = new MemoryCache();
//加载策略
private LoadPolicy loadPolicy = new ReversePolicy();
//默认线程数
private int threadCount = Runtime.getRuntime().availableProcessors();
//加载过程显示图片
private DisplayConfig displayConfig= new DisplayConfig();
private ImageLoaderConfig() {}
//建造者模式,使用链式建造
public static class Builder {
private ImageLoaderConfig config;
public Builder() {
config = new ImageLoaderConfig();
}
//设置缓存策略
public Builder setCachePolicy(BitmapCache bitmapCache) {
config.bitmapCache = bitmapCache;
return this;
}
//设置加载策略
public Builder setLoadPolicy(LoadPolicy loadPolicy) {
config.loadPolicy = loadPolicy;
return this;
}
//设置线程数量
public Builder setThreadCount(int count) {
config.threadCount = count;
return this;
}
//设置加载过程中的图片
public Builder setLoadingImage(int resID) {
config.displayConfig.loadingImage = resID;
return this;
}
//设置加载失败的图片
public Builder setFaildImage(int resID){
config.displayConfig.failedImage = resID;
return this;
}
//返回配置
public ImageLoaderConfig build() {
return config;
}
}
public BitmapCache getBitmapCache() {
return bitmapCache;
}
public LoadPolicy getLoadPolicy() {
return loadPolicy;
}
public int getThreadCount() {
return threadCount;
}
public DisplayConfig getDisplayConfig() {
return displayConfig;
}
}
然后是图片的请求类,包括BitmapRequest,RequestDispatcher和RequestQueue,用于完成Bitmap请求的封装,请求的转发及请求队列的管理
//bitmap请求
public class BitmapRequest implements Comparable<BitmapRequest> {
//加载策略
private LoadPolicy loadPolicy = SimpeImageLoader.getInstance().getConfig().getLoadPolicy();
//编号
private int serialNo;
//持有ImageView的软引用
private SoftReference<ImageView> imageViewSoft;
//图片路径
private String imageUrl;
//MD5图片路径
private String imageUrlMD5;
//下载完成监听
public SimpeImageLoader.ImageListener imageListener;
//设置显示配置
private DisplayConfig displayConfig;
public BitmapRequest(ImageView imageView, String imageUrl, DisplayConfig displayConfig,
SimpeImageLoader.ImageListener imageListener) {
this.imageViewSoft = new SoftReference<>(imageView);
//设置可见Image的Tag,防止图片错位
imageView.setTag(imageUrl);
this.imageUrl = imageUrl;
this.imageUrlMD5 = MD5Utils.toMD5(imageUrl);
if (displayConfig != null) {
this.displayConfig = displayConfig;
}
this.imageListener = imageListener;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BitmapRequest that = (BitmapRequest) o;
return serialNo == that.serialNo &&
Objects.equals(loadPolicy, that.loadPolicy);
}
@Override
public int hashCode() {
return Objects.hash(loadPolicy, serialNo);
}
public int getSerialNo() {
return serialNo;
}
public void setSerialNo(int serialNo) {
this.serialNo = serialNo;
}
public ImageView getImageView() {
return imageViewSoft.get();
}
public String getImageUrl() {
return imageUrl;
}
public String getImageUrlMD5() {
return imageUrlMD5;
}
public DisplayConfig getDisplayConfig() {
return displayConfig;
}
//间接比较,确定优先级
@Override
public int compareTo(@NonNull BitmapRequest o) {
return loadPolicy.compareto(o, this);
}
}
//转发器,请求转发线程,从请求队列中获取请求
public class RequestDispatcher extends Thread{
private static final String TAG = "RequestDispatcher";
//请求队列
private BlockingQueue<BitmapRequest> requests;
public RequestDispatcher(BlockingQueue<BitmapRequest> requests) {
this.requests = requests;
}
@Override
public void run() {
while(!isInterrupted()){
try {
BitmapRequest request = requests.take();
//处理请求对象
//解析请求头
String schema = pareSchema(request.getImageUrl());
//获取加载器
Loader loader = LoaderManager.getInstance().getLoader(schema);
//加载图片
loader.loadImage(request);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判断图片路劲来源
private String pareSchema(String imageUrl) {
if(imageUrl.contains("://")){
return imageUrl.split("://")[0];
}else {
Log.d(TAG, "不支持此文件类型");
}
return null;
}
}
//请求队列
public class RequestQueue {
private static final String TAG = "RequestQueue";
//阻塞式队列,多线程共享
private BlockingQueue<BitmapRequest> requests = new PriorityBlockingQueue<>();
//转发器数量
private int threadCount;
//一组转发器
private RequestDispatcher[] dispatchers;
//请求编号
private AtomicInteger count = new AtomicInteger(0);
public RequestQueue(int threadCount) {
this.threadCount = threadCount;
}
//添加请求对象
public void addRequest(BitmapRequest request) {
if (!requests.contains(request)) {
//给请求进行编号
request.setSerialNo(count.incrementAndGet());
requests.add(request);
} else {
Log.d(TAG, "请求已存在:" + request.getSerialNo());
}
}
//开始请求
public void start() {
stop(); //开始前要先停止
starDispatchers();
}
private void starDispatchers() {
dispatchers = new RequestDispatcher[threadCount];
for (int i = 0; i < threadCount; i++) {
RequestDispatcher dispatcher = new RequestDispatcher(requests);
dispatchers[i] = dispatcher;
dispatchers[i].start();
}
}
//停止请求
public void stop() {
}
}
缓存策略类包括缓存接口BitmapCache,硬盘缓存DiskCache,内存缓存MemoryCache和双缓存DoubleCache
//缓存策略接口
public interface BitmapCache {
//缓存Bitmap
void put(BitmapRequest request, Bitmap bitmap);
//获取Bitmap
Bitmap get(BitmapRequest request);
//移除缓存
void remove(BitmapRequest request);
}
//硬盘缓存策略
public class DiskCache implements BitmapCache {
//缓存路径
private String cacheDir = "Image";
//MB
private static final int MB = 1024 * 1024;
private DiskLruCache diskLruCache;
//单利
private static DiskCache instance;
private DiskCache(Context context) {
initDiskCache(context);
}
private void initDiskCache(Context context) {
//缓存目录
File dir = getDiskCache(cacheDir, context);
if (!dir.exists()) {
dir.mkdir();
}
try {
//设置缓存容量
diskLruCache = DiskLruCache.open(dir, 1, 1, 50 * MB);
} catch (IOException e) {
e.printStackTrace();
}
}
private File getDiskCache(String cacheDir, Context context) {
//默认存储路径
return new File(Environment.getExternalStorageDirectory(), cacheDir);
}
public static DiskCache getInstance(Context context) {
if (instance == null) {
synchronized (DiskCache.class) {
if (instance == null) {
instance = new DiskCache(context);
}
}
}
return instance;
}
@Override
public void put(BitmapRequest request, Bitmap bitmap) {
DiskLruCache.Editor editor = null;
OutputStream outputStream = null;
try {
editor = diskLruCache.edit(request.getImageUrlMD5());
//一个key对应一个文件
outputStream = editor.newOutputStream(0);
if (persistBitmap2Disk(bitmap, outputStream)) {
editor.commit();
} else {
editor.abort();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private boolean persistBitmap2Disk(Bitmap bitmap, OutputStream outputStream) {
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
try {
bos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtil.closeQuietly(bos);
}
return true;
}
@Override
public Bitmap get(BitmapRequest request) {
try {
DiskLruCache.Snapshot snapshot = diskLruCache.get(request.getImageUrlMD5());
if(snapshot != null){
InputStream inputStream = snapshot.getInputStream(0);
return BitmapFactory.decodeStream(inputStream);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public void remove(BitmapRequest request) {
try {
diskLruCache.remove(request.getImageUrlMD5());
} catch (IOException e) {
e.printStackTrace();
}
}
}
//内存缓存策略
public class MemoryCache implements BitmapCache {
private LruCache<String, Bitmap> lruCache;
public MemoryCache() {
//设置最大缓存值
int maxSize = (int) (Runtime.getRuntime().freeMemory() / 1024 / 8);
lruCache = new LruCache<String, Bitmap>(maxSize) {
//告诉如何计算
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
@Override
public void put(BitmapRequest request, Bitmap bitmap) {
lruCache.put(request.getImageUrlMD5(), bitmap);
}
@Override
public Bitmap get(BitmapRequest request) {
return lruCache.get(request.getImageUrlMD5());
}
@Override
public void remove(BitmapRequest request) {
lruCache.remove(request.getImageUrlMD5());
}
}
public class DoubleCache implements BitmapCache{
//内存缓存
private MemoryCache memoryCache = new MemoryCache();
//硬盘缓存
private DiskCache diskCache;
public DoubleCache(Context context){
diskCache = DiskCache.getInstance(context);
}
@Override
public void put(BitmapRequest request, Bitmap bitmap) {
memoryCache.put(request,bitmap);
diskCache.put(request,bitmap);
}
@Override
public Bitmap get(BitmapRequest request) {
Bitmap bitmap = memoryCache.get(request);
if(bitmap == null){
bitmap = diskCache.get(request);
if(bitmap != null){
//放在内存,方便读取
memoryCache.put(request,bitmap);
}
}
return bitmap;
}
@Override
public void remove(BitmapRequest request) {
memoryCache.remove(request);
diskCache.remove(request);
}
}
硬盘缓存调用了一个开源库DiskLruCache,用到了其中的DiskLruCache.java
,IOUtils.java
和StrictLineReader.java
接下来时加载策略,包括加载策略接口LoadPolicy,ReversePolicy和SerialPolicy
//加载策略接口
public interface LoadPolicy {
//优先级比较
int compareto(BitmapRequest request1,BitmapRequest request2);
}
//逆序加载策略
public class ReversePolicy implements LoadPolicy{
@Override
public int compareto(BitmapRequest request1, BitmapRequest request2) {
return request2.getSerialNo() - request1.getSerialNo();
}
}
//顺序加载策略
public class SerialPolicy implements LoadPolicy{
@Override
public int compareto(BitmapRequest request1, BitmapRequest request2) {
return request1.getSerialNo() - request2.getSerialNo();
}
}
然后是工具类,包括图片解码类BitmapDecoder,图片宽高计算类ImageViewHelper和MD5工具类MD5Utils
//解码图片
public abstract class BitmapDecoder {
public Bitmap decodeBitmap(int reqWidth, int reqHeight) {
//初始化Options
BitmapFactory.Options options = new BitmapFactory.Options();
//读取部分信息,获得图片宽高
options.inJustDecodeBounds = true;
//根据bitmap加载图片
decodeBitmapWithOption(options);
//计算图片缩放比例
caculateSizeWithOption(options, reqWidth, reqHeight);
//返回缩放后的Bitmap
return decodeBitmapWithOption(options);
}
private void caculateSizeWithOption(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1;
if (width > reqWidth || height > reqHeight) {
int widthRatio = Math.round((float) width / (float) reqWidth);
int heightRatio = Math.round((float) height / (float) reqHeight);
inSampleSize = Math.max(widthRatio, heightRatio);
}
options.inSampleSize = inSampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = false;
//内存不足时回收Bitmap
options.inPurgeable = true;
options.inInputShareable = true;
}
public abstract Bitmap decodeBitmapWithOption(BitmapFactory.Options options);
}
public class ImageViewHelper {
//默认的图片宽高
private static int DEFAULT_WIDTH = 200;
private static int DEFAULT_HEIGHT = 200;
//获取ImageView控件的宽度
public static int getImageViewWidth(ImageView imageView){
if(imageView != null){
LayoutParams params = imageView.getLayoutParams();
int width = 0;
if(params != null && params.width != LayoutParams.WRAP_CONTENT){
width = imageView.getWidth();
}
if(width <= 0 && params != null){
width = params.width;
}
if(width <= 0){
width = getImageViewFieldValue(imageView,"mMaxWidth");
}
return width;
}
return DEFAULT_WIDTH;
}
//获取图片的高度
public static int getImageViewHeight(ImageView imageView){
if(imageView != null){
LayoutParams params = imageView.getLayoutParams();
int height = 0;
if(params != null && params.height != LayoutParams.WRAP_CONTENT){
height = imageView.getWidth();
}
if(height <= 0 && params != null){
height = params.height;
}
if(height <= 0){
height = getImageViewFieldValue(imageView,"mMaxHeight");
}
return height;
}
return DEFAULT_HEIGHT;
}
private static int getImageViewFieldValue(ImageView imageView,String fieldName) {
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = (Integer)field.get(imageView);
if(fieldValue > 0 && fieldValue < Integer.MAX_VALUE){
return fieldValue;
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
public class MD5Utils {
private static final String TAG = "MD5Utils";
private static MessageDigest digest;
static {
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
Log.d(TAG, "MD5算法不支持");
}
}
//MD5加密
public static String toMD5(String key) {
if (digest == null) {
return String.valueOf(key.hashCode());
}
//更新字节
digest.update(key.getBytes());
//获取最终的摘要
return convert2HexString(digest.digest());
}
//转为16进制字符串
private static String convert2HexString(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for (byte b : bytes) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
最后是加载类,包括加载接口Loader,加载抽象类AbstractLoader,硬盘加载器LocalLoader,网络加载器UrlLoader,空加载器NullLoader,图片加载器SimpeImageLoader,加载器管理LoaderManager
//加载器接口
public interface Loader {
//加载图片
void loadImage(BitmapRequest request);
}
//抽象加载器
public abstract class AbstractLoader implements Loader {
//持有缓存策略,得到自定义缓存策略
private BitmapCache bitmapCache = SimpeImageLoader.getInstance().getConfig().getBitmapCache();
//拿到显示配置
private DisplayConfig displayConfig = SimpeImageLoader.getInstance().getConfig().getDisplayConfig();
@Override
public void loadImage(BitmapRequest request) {
//从缓存中读取bitmap
Bitmap bitmap = bitmapCache.get(request);
if (bitmap == null) {
//显示默认加载图片
showLoadingImage(request);
//加载图片
bitmap = onLoad(request);
//缓存图片
cacheBitmap(request, bitmap);
}
deliveryToUIThread(request, bitmap);
}
//交给主线程显示
protected void deliveryToUIThread(final BitmapRequest request, final Bitmap bitmap) {
ImageView imageView = request.getImageView();
if (imageView != null) {
imageView.post(new Runnable() {
@Override
public void run() {
updateImageView(request, bitmap);
}
});
}
}
//更新ImageView
private void updateImageView(BitmapRequest request, Bitmap bitmap) {
ImageView imageView = request.getImageView();
//加载正常
if (bitmap != null && imageView.getTag().equals(request.getImageUrl())) {
imageView.setImageBitmap(bitmap);
}
//加载失败
if (bitmap == null && request.getDisplayConfig() != null &&
request.getDisplayConfig().failedImage != -1) {
imageView.setImageResource(displayConfig.failedImage);
}
//监听 回调
if (request.imageListener != null) {
request.imageListener.onComplete(imageView, bitmap, request.getImageUrl());
}
}
//缓存图片
private void cacheBitmap(BitmapRequest request, Bitmap bitmap) {
if (request != null && bitmap != null) {
synchronized (AbstractLoader.class) {
bitmapCache.put(request, bitmap);
}
}
}
//抽象的加载方法,由子类去实现
protected abstract Bitmap onLoad(BitmapRequest request);
//加载前显示的图片
protected void showLoadingImage(BitmapRequest request) {
if (hasLoadingPlaceHolder()) {
final ImageView imageView = request.getImageView();
if (imageView != null) {
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageResource(displayConfig.loadingImage);
}
});
}
}
}
protected boolean hasLoadingPlaceHolder() {
return (displayConfig != null && displayConfig.loadingImage > 0);
}
protected boolean hasFailedPlaceHolder() {
return (displayConfig != null && displayConfig.failedImage > 0);
}
}
//硬盘加载器
public class LocalLoader extends AbstractLoader{
@Override
protected Bitmap onLoad(BitmapRequest request) {
//得到本地图片路径
final String path = Uri.parse(request.getImageUrl()).getPath();
File file = new File(path);
if(!file.exists()){
return null;
}
BitmapDecoder decoder = new BitmapDecoder() {
@Override
public Bitmap decodeBitmapWithOption(BitmapFactory.Options options) {
return BitmapFactory.decodeFile(path,options);
}
};
return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()),
ImageViewHelper.getImageViewHeight(request.getImageView()));
}
}
//网络加载器
public class UrlLoader extends AbstractLoader {
@Override
protected Bitmap onLoad(final BitmapRequest request) {
//下载之后读取
downloadImgByUrl(request.getImageUrl(), getCache(request.getImageUrlMD5()));
BitmapDecoder decoder = new BitmapDecoder() {
@Override
public Bitmap decodeBitmapWithOption(BitmapFactory.Options options) {
return BitmapFactory.decodeFile(getCache(request.getImageUrlMD5()).getAbsolutePath(), options);
}
};
return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView())
, ImageViewHelper.getImageViewHeight(request.getImageView()));
}
public static boolean downloadImgByUrl(String urlStr, File file) {
FileOutputStream fos = null;
InputStream is = null;
try {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
is = conn.getInputStream();
fos = new FileOutputStream(file);
byte[] buf = new byte[512];
int len = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
return true;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private File getCache(String unipue) {
File file = new File(Environment.getExternalStorageDirectory(), "ImageLoader");
if (!file.exists()) {
file.mkdir();
}
return new File(file, unipue);
}
}
public class NullLoader extends AbstractLoader {
@Override
protected Bitmap onLoad(BitmapRequest request) {
return null;
}
}
//图片加载器,单利对象
public class SimpeImageLoader {
//配置文件
private ImageLoaderConfig config;
//请求队列
private RequestQueue queue;
//单利
private static volatile SimpeImageLoader instance;
private SimpeImageLoader() {
}
private SimpeImageLoader(ImageLoaderConfig config) {
this.config = config;
queue = new RequestQueue(config.getThreadCount());
//开启请求队列
queue.start();
}
public static SimpeImageLoader getInstance(ImageLoaderConfig config) {
if (instance == null) {
synchronized (SimpeImageLoader.class) {
if (instance == null) {
instance = new SimpeImageLoader(config);
}
}
}
return instance;
}
//第二次获取单利
public static SimpeImageLoader getInstance() {
if (instance == null) {
throw new UnsupportedOperationException("未初始化参数");
}
return instance;
}
//获取全局配置
public ImageLoaderConfig getConfig(){
return config;
}
//获取图片
public void displayImage(ImageView imageView, String url) {
displayImage(imageView, url, null, null);
}
//扩展,重载
public void displayImage(ImageView imageView, String url,
DisplayConfig displayConfig, ImageListener imageListener) {
//实例化请求
BitmapRequest bitmapRequest = new BitmapRequest(imageView,url,displayConfig,imageListener);
//添加请求到队列
queue.addRequest(bitmapRequest);
}
//扩展接口
public interface ImageListener {
void onComplete(ImageView imageView, Bitmap bitmap, String url);
}
}
//加载器管理
public class LoaderManager {
//缓存支持的Loader类型
private Map<String, Loader> loaderMap = new HashMap<>();
//单例模式
private static LoaderManager instance = new LoaderManager();
private LoaderManager() {
register("http", new UrlLoader());
register("https", new UrlLoader());
register("file", new LocalLoader());
}
public static LoaderManager getInstance() {
return instance;
}
private void register(String schema, Loader Loader) {
loaderMap.put(schema, Loader);
}
public Loader getLoader(String schema){
if(loaderMap.containsKey(schema)){
return loaderMap.get(schema);
}
return new NullLoader();
}
}
最后是测试类,这里我是用tomcat搭建服务器,使用975张图片做测试
public class MainActivity extends AppCompatActivity {
private SimpeImageLoader imageLoader;
private static final int COUNT = 975;
private static final String path = "http://192.168.1.2:8080/test/";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
GridView listview = (GridView) findViewById(R.id.listview);
listview.setAdapter(new MyAdapter(this));
//配置
ImageLoaderConfig.Builder build = new ImageLoaderConfig.Builder();
build.setThreadCount(3) //线程数量
.setLoadPolicy(new ReversePolicy()) //加载策略
.setCachePolicy(new DoubleCache(this)) //缓存策略
.setLoadingImage(R.drawable.loading)
.setFaildImage(R.drawable.not_found);
ImageLoaderConfig config = build.build();
//初始化
imageLoader = SimpeImageLoader.getInstance(config);
}
class MyAdapter extends BaseAdapter {
private LayoutInflater inflater;
public MyAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return COUNT;
}
@Override
public Object getItem(int position) {
return getUrl(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View item = inflater.inflate(R.layout.item, null);
ImageView imageView = (ImageView) item.findViewById(R.id.iv);
//请求图片
imageLoader.displayImage(imageView, getUrl(position));
return item;
}
}
public String getUrl(int position) {
if (position < 10)
return path + "00" + position + ".jpg";
else if (position < 100)
return path + "0" + position + ".jpg";
else if (position < COUNT)
return path + position + ".jpg";
else
return null;
}
}
其实,这就是缩水版的Glide