从零开始实现一个网络图片加载框架
来源:互联网 发布:c语言定时器 编辑:程序博客网 时间:2024/05/19 02:03
尊重他人的劳动成果,转载请标明出处:http://blog.csdn.net/gengqiquan/article/details/53064858, 本文出自:【gengqiquan的博客】
项目使用 Picasso的时候发现一些问题,列表大量快速滑动的时候容易内存过高而崩溃。快速滑动有卡顿。仔细看了下源码。发现Picasso的解析是没有做线程池限制处理的。大量解析容易导致内存来不及释放而崩溃。于是闲来无事自己写了个。当然考虑的没有Picasso等框架全面,只是简单能用对性能消耗不大而已,可拓展性不是特别高。
参考了网上一些人的思想,历史悠久,就不一一贴出来了。总之感谢广大技术网友的贡献。
50张网络图片加载的效果图
框架暂时做到了以下这些功能,以后会慢慢完善。
1、切换请求框架,默认HttpUrlConnection(还是未优化过的,示例项目提供volley和okhttp3的下载器的实现)
2、二级缓存,一级内存,二级本地
3、可以决定缓存裁剪后的图片还是原图
4、支持添加加载占位图片,加载失败图片,
5、支持图片变味圆形图片(项目需要,其实应该支持设置圆角的,后期会加)
6、支持返回给主线程根据控件大小裁剪后的图片
7、支持解析和加载请求顺序倒序
8、内存缓存中能找到的立即加载
9、将解析和请求放入队列,利用线程池进行管理,控制辅助资源的消耗
10、支持加载本地图片
接下来我们来一步步的实现这个框架,暂时就叫它HelloLoader吧
首先定义一个配置类。可以决定每个图片需要按怎样的方式加载和缓存
public class LoaderConfigure { public int loading = 0;//加载占位图资源id public int error = 0;//加载失败展位图资源id int width = -1;//图片目标宽度 int height = -1;//图片目标高度 public boolean roundBitmap = false;//是否圆形图片 public boolean memoryCache = true;//是否需要内存缓存 public boolean diskCache = true;//是否需要本地缓存 public boolean adjust = true;//是否需要按控件大小裁剪 public boolean cacheBaseImage = false;//是否只缓存原图 public LoaderConfigure cacheBaseImage(boolean cache) { cacheBaseImage = cache; return this; } public LoaderConfigure adjust(boolean cache) { adjust = cache; return this; } public LoaderConfigure memoryCache(boolean cache) { memoryCache = cache; return this; } public LoaderConfigure diskCache(boolean cache) { diskCache = cache; return this; } public LoaderConfigure roundBitmap(boolean round) { roundBitmap = round; return this; } public LoaderConfigure loading(int res) { loading = res; return this; } public LoaderConfigure error(int res) { error = res; return this; } public LoaderConfigure size(int w, int h) { if (w > 0 && h > 0) { width = w; height = h; } return this; }}
然后定义加载图片类的属性
public class HelloLoader { static volatile HelloLoader mInstance; Context mAppliactionContext; Cache cache; String mDiskCachePath; LoaderConfigure mDefaultConfigure;//默认的全局图片加载配置 Downloader mDownloader;//图片加载器 LinkedList<TaskInfo> mTaskQueue;//解析请求队列 ExecutorService mThreadPool;// 线程池 int threadCount = 4; Handler mPoolThreadHandler;//线程池循环句柄 Thread mPoolThread;//后台任务调度线程 Type mType = Type.LIFO;//请求加载顺序:正序还是倒序 public enum Type { FIFO, LIFO; } //主线程设置图片句柄 @SuppressLint("HandlerLeak") private Handler UIHandler = new Handler() { @Override public void handleMessage(Message msg) { //设置图片到imageview } };
为了网络请求加载器可配置,和内存缓存可自定义,这里吧加载器和内存缓存抽象了
定义一个加载器接口
public interface Downloader { ResponseInfo downloadImgByUrl(String urlStr);}
ResponseInfo 是定义的请求返回类型,所有自定义的加载器都需要返回这个类型
public class ResponseInfo { public Bitmap bitmap; public boolean success; public ResponseInfo(boolean success) { this.success = success; }}
定义一个内存缓存接口
public interface Cache { /** Retrieve an image for the specified {@code key} or {@code null}. */ Bitmap get(String key); /** Store an image in the cache for the specified {@code key}. */ Bitmap put(String key, Bitmap bitmap);}
由于每个图片的加载是独立的,有着自己的配置属性,对应的imageview和URL。所以我们需要将这些信息封装起来
定义一个图片加载构建类
public class LoaderBuilder { ImageView mImageView; LoaderConfigure mLoaderConfigure; Context mContext; HelloLoader mLoader; public LoaderBuilder(ImageView mImageView, HelloLoader mLoader) { this.mImageView = mImageView; this.mLoader = mLoader; this.mContext = mImageView.getContext(); } public LoaderBuilder LoaderConfigure(LoaderConfigure mLoaderConfigure) { this.mLoaderConfigure = mLoaderConfigure; return this; } public void load(final String uri) { checkDefault(); mLoader.putTask(mContext, mLoaderConfigure, mImageView, uri); //从界面移除后取消加载请求 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) mImageView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { } @Override public void onViewDetachedFromWindow(View v) { mLoader.removeTask(mImageView); } }); ImageUtil.pretreatmentImage(mContext, mImageView, mLoaderConfigure); mLoader.runTask(); } private void checkDefault() { if (mLoaderConfigure == null) { mLoaderConfigure = mLoader.mDefaultConfigure; } }}
好了,现在当我们准备加载一张网络图片到imageview。我们构建这样一个图片加载构建类
public static LoaderBuilder bind(ImageView imageView) { if (imageView == null) throw new NullPointerException("imageView can not be null"); if (mInstance == null) { mInstance = new Builder(imageView.getContext()).build(); } return new LoaderBuilder(imageView, mInstance); }
返回一个构建对象,然后调用他的 LoadUrl()方法,在这个方法里我们先检查加载配置,如果没有设置加载配置就用全局默认的那个。然后将加载请求放入请求队列
@NonNullpublic void putTask(final Context context, final LoaderConfigure configure, final ImageView imageView, final String uri) { //加时间戳,防止列表加载的时候有相同路径的多张图片重复加载错乱问题 final String key = Utils.md5(uri); final String tag = key+ System.currentTimeMillis(); Bitmap bm = null; bm = memoryCacheCheck(key); if (bm != null) { imageView.setTag(tag); ImageInfo imageInfo = new ImageInfo(imageView, tag, bm, configure); Message msg = new Message(); msg.what = 0; msg.obj = imageInfo; UIHandler.sendMessageAtFrontOfQueue(msg);//内存找到优先直接加载。速度快 } else { if (imageView.getTag() != null) { removeTask(imageView);//顺序不能错,先从任务队列移除这个imageView之前所绑定的任务 } imageView.setTag(tag); Runnable runnable; if (uri.startsWith("http")) { runnable = getNetImageRunnable(context, configure, imageView, uri, tag,key); } else { runnable = getLocalImageRunnable(context, configure, imageView, uri, tag,key); } TaskInfo info = new TaskInfo(imageView, runnable); synchronized (mTaskQueue) { if (mTaskQueue.contains(info)) { mTaskQueue.remove(info); mTaskQueue.add(info); } else { mTaskQueue.add(info); } } } }
加载网络图片的任务生成
@NonNull public Runnable getNetImageRunnable(final Context context, final LoaderConfigure configure, final ImageView imageView, final String url, final String tag, final String key) { return new Runnable() { @Override public void run() { // TODO: 这里其实应该加入优先级。本地如果有这个文件,应该优先级高一点,或者额外给个本地解析任务队列 Bitmap bm = getBitmapFromDiskCache(context, key, imageView); if (bm == null) { ResponseInfo responseInfo = mInstance.mDownloader.downloadImgByUrl(url); if (responseInfo.success)// 如果下载成功 { bm = dealImage(configure, imageView, key, responseInfo.bitmap); } } ImageInfo imageInfo = new ImageInfo(imageView, tag, bm, configure); Message msg = new Message(); msg.what = 0; msg.obj = imageInfo; UIHandler.sendMessage(msg); } }; }
逻辑就是先从内存找,找到就直接放到显示图片的队列的最前方,优先显示,这里用sendMessageAtFrontOfQueue这一句代码的原因是后进来的图片加载请求肯定是用户当前浏览位置或者是离用户当前浏览位置最近的,应该最先显示给用户看。
内存里没找到就加入到请求队列里,同时移除当前imageview之前的加载请求,同一个imageview不同请求一般是出现在列表页使用了viewholder机制的情况下,因为用户不一定再会回到之前的位置。
下载成功后对图片根据配置进行处理
private Bitmap dealImage(LoaderConfigure configure, ImageView imageView, String key, Bitmap bm) { if (configure.cacheBaseImage || !configure.adjust) {//缓存原图,或者不压缩的情况下 putIntoCache(configure, key, bm); } if (configure.adjust) {//是否根据控件大小压缩图片大小 bm = ImageUtil.scaleImg(bm, imageView.getWidth(), imageView.getHeight()); if (!configure.cacheBaseImage) {//缓存压缩后图片 putIntoCache(configure, key, bm); } } return bm; }
下面加载本地图片的任务生成
@NonNull public Runnable getLocalImageRunnable(final Context context, final LoaderConfigure configure, final ImageView imageView, final String path, final String tag, final String key) { return new Runnable() { @Override public void run() { Bitmap bm = getBitmapFromDiskCache(context, key, imageView); if (bm == null) { bm = getBitmapFromDisk(context, path, imageView); if (bm!= null) { bm = dealImage(configure, imageView, key, bm); } } ImageInfo imageInfo = new ImageInfo(imageView, tag, bm, configure); Message msg = new Message(); msg.what = 0; msg.obj = imageInfo; UIHandler.sendMessage(msg); } }; }
将图片加载请求放入请求队列后做一些加载前需要做的事,这里我选择设置占位图片
public static void pretreatmentImage(Context context, ImageView imageView, LoaderConfigure configure) { Bitmap bm = null; if (configure.loading > 0) bm = ReadBitmapById(context, configure.loading); if (bm != null) { imageView.setImageBitmap(configureImage(bm, imageView, configure)); } }
通知图片加载器有加载任务进来了,
protected void runTask() { mPoolThreadHandler.sendEmptyMessage(0); }
构建单例的时候启动后台轮询线程
private void initBackThread() { mPoolThread = new Thread() { @Override public void run() { Looper.prepare(); mPoolThreadHandler = new Handler() { @Override public void handleMessage(Message msg) { // 线程池去取出一个任务进行执行 Runnable runnable = getTask(); if (runnable != null) { mThreadPool.execute(runnable); } } }; Looper.loop(); } }; mPoolThread.start(); }
这里设置取任务的顺序方式
private Runnable getTask() { synchronized (mTaskQueue) { if (mTaskQueue.isEmpty()) return null; if (mType == Type.FIFO) { return mTaskQueue.removeFirst().getRunnable(); } else if (mType == Type.LIFO) { return mTaskQueue.removeLast().getRunnable(); } } return null; }
前面当我们图片下载成功后将图片信息进行封装后传递给了UIHandler
if (responseInfo.success)// 如果下载成功 { bm = dealResponse(configure, imageView, tag, responseInfo.bitmap); } } ImageInfo imageInfo = new ImageInfo(imageView, tag, bm, configure); Message msg = new Message(); msg.what = 0; msg.obj = imageInfo; UIHandler.sendMessage(msg);
我们在UIHandler把图片显示在imageview上 ,根据加载配置对图片做显示前的处理,判断图片所对应的tag和imageview是否一致。避免列表加载网络图片显示错乱,如果图片获取失败就显示失败的占位图片
private Handler UIHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: ImageInfo imageInfo = (ImageInfo) msg.obj; Bitmap bm = imageInfo.getBitmap(); LoaderConfigure configure = imageInfo.getLoaderConfigure(); ImageView imageView = imageInfo.getImageView(); if (imageView.getTag() != null && imageView.getTag().equals(imageInfo.getTag())) { if (bm == null) { if (configure.error > 0) { bm = ImageUtil.ReadBitmapById(mAppliactionContext, configure.error); } } if (bm != null) { bm = ImageUtil.configureImage(bm, imageView, configure); imageView.setImageBitmap(bm); } } break; } } };
完整的HelloLoader 类
package com.sunshine.view.library;import android.annotation.SuppressLint;import android.app.Activity;import android.content.Context;import android.graphics.Bitmap;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.support.annotation.NonNull;import android.widget.ImageView;import com.sunshine.view.library.cache.Cache;import com.sunshine.view.library.cache.LruCache;import com.sunshine.view.library.data.ImageInfo;import com.sunshine.view.library.data.ResponseInfo;import com.sunshine.view.library.data.TaskInfo;import com.sunshine.view.library.download.Downloader;import com.sunshine.view.library.download.HttpUrlConnectionDownLoader;import com.sunshine.view.library.utils.ImageSizeUtil;import com.sunshine.view.library.utils.ImageUtil;import com.sunshine.view.library.utils.Utils;import java.io.File;import java.util.LinkedList;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by Administrator on 2016/11/1. */public class HelloLoader { static volatile HelloLoader mInstance; Context mAppliactionContext; Cache cache; String mDiskCachePath; LoaderConfigure mDefaultConfigure;//默认的全局图片加载配置 Downloader mDownloader;//图片加载器 LinkedList<TaskInfo> mTaskQueue;//解析请求队列 ExecutorService mThreadPool;// 线程池 int threadCount = 4; Handler mPoolThreadHandler;//线程池循环句柄 Thread mPoolThread;//后台任务调度线程 Type mType = Type.LIFO;//请求加载顺序:正序还是倒序 public enum Type { FIFO, LIFO; } //主线程设置图片句柄 @SuppressLint("HandlerLeak") private Handler UIHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: ImageInfo imageInfo = (ImageInfo) msg.obj; Bitmap bm = imageInfo.getBitmap(); LoaderConfigure configure = imageInfo.getLoaderConfigure(); ImageView imageView = imageInfo.getImageView(); if (imageView.getTag() != null && imageView.getTag().equals(imageInfo.getTag())) { if (bm == null) { if (configure.error > 0) { bm = ImageUtil.ReadBitmapById(mAppliactionContext, configure.error); } } if (bm != null) { bm = ImageUtil.configureImage(bm, imageView, configure); imageView.setImageBitmap(bm); } } break; } } }; public HelloLoader(Context mAppliactionContext, Cache cache, String mDiskCachePath, LoaderConfigure mDefaultConfigure , Downloader downloader) { this.mAppliactionContext = mAppliactionContext; this.cache = cache; this.mDiskCachePath = mDiskCachePath; this.mDefaultConfigure = mDefaultConfigure; this.mDownloader = downloader; mTaskQueue = new LinkedList<>(); threadCount = Runtime.getRuntime().availableProcessors(); mThreadPool = Executors.newFixedThreadPool(threadCount + 1); initBackThread(); } public static LoaderBuilder bind(ImageView imageView) { if (imageView == null) throw new NullPointerException("imageView can not be null"); if (mInstance == null) { mInstance = new Builder(imageView.getContext()).build(); } return new LoaderBuilder(imageView, mInstance); } private Bitmap memoryCacheCheck(String key) { Bitmap cached = cache.get(key); return cached; } private Bitmap put2MemoryCache(String key, Bitmap bitmap) { if (key == null) { throw new NullPointerException("cache key can not be null"); } if (bitmap == null) { throw new NullPointerException("the bitmap put to cache is null"); } Bitmap cached = cache.put(key, bitmap); return cached; } /** * 初始化后台轮询线程 */ private void initBackThread() { mPoolThread = new Thread() { @Override public void run() { Looper.prepare(); mPoolThreadHandler = new Handler() { @Override public void handleMessage(Message msg) { // 线程池去取出一个任务进行执行 Runnable runnable = getTask(); if (runnable != null) { mThreadPool.execute(runnable); } } }; Looper.loop(); } }; mPoolThread.start(); } protected void runTask() { mPoolThreadHandler.sendEmptyMessage(0); } private Runnable getTask() { synchronized (mTaskQueue) { if (mTaskQueue.isEmpty()) return null; if (mType == Type.FIFO) { return mTaskQueue.removeFirst().getRunnable(); } else if (mType == Type.LIFO) { return mTaskQueue.removeLast().getRunnable(); } } return null; } protected void removeTask(ImageView imageView) { synchronized (mTaskQueue) { mTaskQueue.remove(new TaskInfo(imageView, null)); } } /** * 从内存取,,取到直接加载。因为不占据请求和解析资源,而且刚刚加载过的图片滑动过来再次加载也不合理 * 没取到则放入解析和请求队列 * * @date 2016/11/3 9:45 */ @NonNull public void putTask(final Context context, final LoaderConfigure configure, final ImageView imageView, final String uri) { //加时间戳,防止列表加载的时候有相同路径的多张图片重复加载错乱问题 final String key = Utils.md5(uri); final String tag = key+ System.currentTimeMillis(); Bitmap bm = null; bm = memoryCacheCheck(key); if (bm != null) { imageView.setTag(tag); ImageInfo imageInfo = new ImageInfo(imageView, tag, bm, configure); Message msg = new Message(); msg.what = 0; msg.obj = imageInfo; UIHandler.sendMessageAtFrontOfQueue(msg);//内存找到优先直接加载。速度快 } else { if (imageView.getTag() != null) { removeTask(imageView);//顺序不能错,先从任务队列移除这个imageView之前所绑定的任务 } imageView.setTag(tag); Runnable runnable; if (uri.startsWith("http")) { runnable = getNetImageRunnable(context, configure, imageView, uri, tag,key); } else { runnable = getLocalImageRunnable(context, configure, imageView, uri, tag,key); } TaskInfo info = new TaskInfo(imageView, runnable); synchronized (mTaskQueue) { if (mTaskQueue.contains(info)) { mTaskQueue.remove(info); mTaskQueue.add(info); } else { mTaskQueue.add(info); } } } } @NonNull public Runnable getNetImageRunnable(final Context context, final LoaderConfigure configure, final ImageView imageView, final String url, final String tag, final String key) { return new Runnable() { @Override public void run() { // TODO: 这里其实应该加入优先级。本地如果有这个文件,应该优先级高一点,或者额外给个本地解析任务队列 Bitmap bm = getBitmapFromDiskCache(context, key, imageView); if (bm == null) { ResponseInfo responseInfo = mInstance.mDownloader.downloadImgByUrl(url); if (responseInfo.success)// 如果下载成功 { bm = dealImage(configure, imageView, key, responseInfo.bitmap); } } ImageInfo imageInfo = new ImageInfo(imageView, tag, bm, configure); Message msg = new Message(); msg.what = 0; msg.obj = imageInfo; UIHandler.sendMessage(msg); } }; } @NonNull public Runnable getLocalImageRunnable(final Context context, final LoaderConfigure configure, final ImageView imageView, final String path, final String tag, final String key) { return new Runnable() { @Override public void run() { Bitmap bm = getBitmapFromDiskCache(context, key, imageView); if (bm == null) { bm = getBitmapFromDisk(context, path, imageView); if (bm!= null) { bm = dealImage(configure, imageView, key, bm); } } ImageInfo imageInfo = new ImageInfo(imageView, tag, bm, configure); Message msg = new Message(); msg.what = 0; msg.obj = imageInfo; UIHandler.sendMessage(msg); } }; } private Bitmap getBitmapFromDiskCache(Context mContext, String key, ImageView imageView) { Bitmap bm = null; File file = Utils.getDiskCacheDir(mInstance.mDiskCachePath, key); if (file.exists())// 如果在本地缓存文件中发现 { ImageSizeUtil.ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView); // 根据控件大小获取本地压缩处理后的图片 bm = ImageUtil.decodeSampledBitmapFromPath(file.getAbsolutePath(), imageSize); } return bm; } private Bitmap getBitmapFromDisk(Context mContext, String path, ImageView imageView) { Bitmap bm = null; if (!Utils.checkNULL(path))// 如果文件路径不为空 { ImageSizeUtil.ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView); // 根据控件大小获取本地压缩处理后的图片 bm = ImageUtil.decodeSampledBitmapFromPath(path, imageSize); } return bm; } private Bitmap dealImage(LoaderConfigure configure, ImageView imageView, String key, Bitmap bm) { if (configure.cacheBaseImage || !configure.adjust) {//缓存原图,或者不压缩的情况下 putIntoCache(configure, key, bm); } if (configure.adjust) {//是否根据控件大小压缩图片大小 bm = ImageUtil.scaleImg(bm, imageView.getWidth(), imageView.getHeight()); if (!configure.cacheBaseImage) {//缓存压缩后图片 putIntoCache(configure, key, bm); } } return bm; } private void putIntoCache(LoaderConfigure configure, String key, Bitmap bm) { if (configure.memoryCache) { put2MemoryCache(key, bm);//放入内存缓存 } if (configure.diskCache) { Utils.write2File(mInstance.mDiskCachePath, bm, key);//写入本地文件 } } public static class Builder { Context mContext; LoaderConfigure mDefaultConfigure; Cache mCache; String mDiskCachePath; Downloader mDownloader; public Builder(Context context) { try {//防止传入的是activity的上下文 Activity activity = (Activity) context; mContext = context.getApplicationContext(); } catch (Exception e) { e.printStackTrace(); mContext = context; } } public Builder defaultLoaderConfigure(LoaderConfigure loaderConfigure) { mDefaultConfigure = loaderConfigure; return this; } public Builder downloader(Downloader downloader) { mDownloader = downloader; return this; } public Builder cache(Cache cache) { mCache = cache; return this; } public Builder diskCachePath(String loaderConfigure) { mDiskCachePath = loaderConfigure; return this; } public HelloLoader build() { if (mDefaultConfigure == null) { mDefaultConfigure = new LoaderConfigure(); } if (mCache == null) { mCache = createDefaultCache(); } if (Utils.checkNULL(mDiskCachePath)) { mDiskCachePath = mContext.getCacheDir().getPath(); } if (mDownloader == null) { mDownloader = createDefaultDownloader(); } mInstance = new HelloLoader(mContext, mCache, mDiskCachePath, mDefaultConfigure, mDownloader); return mInstance; } private Downloader createDefaultDownloader() { return new HttpUrlConnectionDownLoader(); } private Cache createDefaultCache() { int memoryCacheSize = Utils.getMemoryCacheSize(mContext); return new LruCache(memoryCacheSize); } }}
使用
loaderConfigure = new LoaderConfigure() .memoryCache(true) .loading(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher); loader = new HelloLoader .Builder(this) .downloader(new VolleyDownLoader(Volley.newRequestQueue(this)))// .downloader(new Okhttp3DownLoader(client)) .defaultLoaderConfigure(loaderConfigure) .build();
loader.bind(holder.imageView).LoadUrl(list.get(position));
自定义Okhttp3DownLoader
public class Okhttp3DownLoader implements Downloader { OkHttpClient client; public Okhttp3DownLoader(OkHttpClient client) { this.client = client; } @Override public ResponseInfo downloadImgByUrl(String urlStr) { ResponseInfo responseInfo = new ResponseInfo(false); InputStream is = null; Bitmap bm = null; Request.Builder builder = new Request.Builder().url(urlStr); try { okhttp3.Response response = client.newCall(builder.build()).execute(); int responseCode = response.code(); if (responseCode == 200) { is = response.body().byteStream(); if (is != null) { bm = BitmapFactory.decodeStream(is, null, null); } if (bm != null) { responseInfo.success = true; responseInfo.bitmap = bm; } } } catch (IOException e) { } return responseInfo; }}
自定义VolleyDownLoader
public class VolleyDownLoader implements Downloader { RequestQueue requestQueue; public VolleyDownLoader(RequestQueue requestQueue) { this.requestQueue = requestQueue; } @Override public ResponseInfo downloadImgByUrl(String urlStr) { RequestFuture future = RequestFuture.newFuture(); Bitmap bm = null; ResponseInfo responseInfo = new ResponseInfo(false); ImageRequest request = new ImageRequest(urlStr, future, 0, 0, Bitmap.Config.RGB_565, future); requestQueue.add(request); try { bm= (Bitmap) future.get(); if (bm != null) { responseInfo.success = true; responseInfo.bitmap = bm; } } catch (Exception e) { } return responseInfo; }}
完整的示例项目Githu地址 https://github.com/gengqiquan/HelloLoader.git
有什么意见和疑惑欢迎留言
我建了一个QQ群(群号:121606151),用于大家讨论交流Android技术问题,有兴趣的可以加下,大家一起进步。
- 从零开始实现一个网络图片加载框架
- 尝试实现一个图片加载框架PicLoad
- 网络图片加载的封装-(从零开始搭建android框架系列(4))
- 网络图片加载的封装【从零开始搭建android框架系列(4)】
- 网络图片加载的封装【从零开始搭建android框架系列(4)】
- 网络图片加载的封装-(从零开始搭建android框架系列(4))
- 网络图片加载的封装-(从零开始搭建android框架系列(4))
- volley 框架 加载网络图片
- Volley框架加载网络图片
- 网络图片延迟加载实现
- 实现网络图片的加载
- Android利用Volley框架加载网络图片
- Android 网络图片加载之cude 框架
- universal-image-Loader网络图片加载框架
- Volley框架之四 加载网络图片
- 第三方框架--加载网络图片
- Picasso网络图片加载框架的使用
- 网络+图片加载框架(英文版)
- Ubuntu 12.04 for ROS by ExBot iso 发行版集合
- sencha touch 扩展篇之使用sass自定义主题样式 (上)使用官方的api修改主题样式
- ++i与i++
- 今天遇到因导入httpClient.jar包导致java.lang.NoSchMethodError异常
- sar 命令
- 从零开始实现一个网络图片加载框架
- Learning C from a iOS programmar
- 联想国际化之路上的用人智慧
- A启动系统设置应用的时间设置Activity去修改时间,修改后返回到A,A挂掉了
- 理解Java垃圾回收机制
- linux源码
- Emmet实例教程
- 解决ScrollView嵌套ListView进行切换时自动滑动至ListView顶部问题
- 数据库 sql语句中where和having的区别 、having的使用、SQL中Group By的使用