Android项目框架之图片加载框架的选择
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!
从Android爆发以后,自定义的控件如EditTextWithDelete、ActionBar、PullToRresh逐步进入开发者的视野,想起11年的时候,基本项目中使用的UI还全都是Android提供的基础控件,少一点的动画+布局,下载和网络请求都是用HttpClient,图片加载当然也是下载后再使用,当时的程序资源可没有现在这么丰富,看看Github上开源的项目,现在的程序员应该感到幸福。
项目开发从程序上来讲,万古不变两大事情,一是网络通信框架,二是图片加载框架,有关网络框架上一篇已经介绍了async-http和okhttp,而其他如volly同时拥有网络请求和图片加载两个框架,很多人图省事就一次性使用了,当然facebook自己的开源框架也是写的非常不错,接下来再一一介绍;先贴一张11年我们自己写的imageloader
public class ImageDownloader { private static ImageDownloader instance = null; private static File cacheDir; public static Map<String, SoftReference<Bitmap>> bitMapCache = new HashMap<String, SoftReference<Bitmap>>(); public static ImageDownloader getInstance() { if (instance == null) { instance = new ImageDownloader(); } return instance; } private ImageDownloader() { // Find the dir to save cached images if (android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) cacheDir = new File( android.os.Environment.getExternalStorageDirectory(), "WholeMag"); else cacheDir = WholeMagApplication.getInstance().getCacheDir(); if (!cacheDir.exists()) cacheDir.mkdirs(); } public void download(String actName, String url, ImageView imageView) { BitmapDownloaderTask task = new BitmapDownloaderTask(imageView, actName); task.execute(url); // return task.doInBackground(url); } class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { // private String url; // private boolean flag; private final WeakReference<ImageView> imageViewReference; // 使用WeakReference解决内存问题 private String actName; public BitmapDownloaderTask(ImageView imageView, String actName) { imageViewReference = new WeakReference<ImageView>(imageView); this.actName = actName; } @Override protected Bitmap doInBackground(String... params) { // 实际的下载线程,内部其实是concurrent线程,所以不会阻塞 Bitmap rebmp = getLocalBitmap(params[0], actName); if (rebmp == null) rebmp = downloadBitmap(params[0], actName); if (rebmp == null) { doInBackground(params[0]); } return rebmp; } @Override protected void onPostExecute(Bitmap bitmap) { // 下载完后执行的 if (isCancelled()) { bitmap = null; } if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setDrawingCacheEnabled(true); Bitmap temp = imageView.getDrawingCache(); imageView.setDrawingCacheEnabled(false); if (temp != null) { temp.recycle(); } double widthX = (float) WholeMagDatas.getDeviceWidth() / bitmap.getWidth(); // 图片宽度拉伸比例 int bitmapHight = bitmap.getHeight();// 图片高度 imageView.setImageBitmap(bitmap); // 下载完设置imageview为刚才下载的bitmap对象 if(actName.equals(AppData.NEWS_DETAIL_ACT)){ FrameLayout.LayoutParams ll = new FrameLayout.LayoutParams( android.view.ViewGroup.LayoutParams.FILL_PARENT, (int) (bitmapHight * widthX), Gravity.CENTER); imageView.setLayoutParams(ll); } AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);// 创建一个AlphaAnimation对象 alphaAnimation.setDuration(500);// 设置动画执行的时间(单位:毫秒) imageView.startAnimation(alphaAnimation); } } } } static Bitmap getLocalBitmap(String url, String actName) { if (bitMapCache.containsKey(url)) { return bitMapCache.get(url).get(); } // String tmp = url; // String first = url.substring(url.lastIndexOf("/") + 1); // tmp = tmp.substring(0, tmp.lastIndexOf("/")); // String second = tmp.substring(tmp.lastIndexOf("/") + 1); // tmp = tmp.substring(0, tmp.lastIndexOf("/")); // String third = tmp.substring(tmp.lastIndexOf("/") + 1); // String filename = third + second + first; // File f = new File(cacheDir, filename); File f = Tools.getFile(actName, url); InputStream inputStream = null; try { // decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inPreferredConfig = Bitmap.Config.RGB_565; o.inDither = false; o.inPurgeable = true; // o.inTempStorage = new byte[12 * 1024]; inputStream = new FileInputStream(f); // Bitmap bitmap = BitmapFactory.decodeFile(f.getAbsolutePath()); Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, o); bitMapCache.put(url, new SoftReference<Bitmap>(bitmap)); return bitmap; } catch (Exception e) { } finally { if (null != inputStream) { try { inputStream.close(); } catch (Exception ex) { } } } return null; } static Bitmap downloadBitmap(String url, String actName) { final AndroidHttpClient client = AndroidHttpClient.newInstance("linux"); final HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { // Log.e("cwjDebug", "Error " + statusCode // + " while retrieving bitmap from " + url); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { // String tmp = url; // String first = url.substring(url.lastIndexOf("/") + 1); // tmp = tmp.substring(0, tmp.lastIndexOf("/")); // String second = tmp.substring(tmp.lastIndexOf("/") + 1); // tmp = tmp.substring(0, tmp.lastIndexOf("/")); // String third = tmp.substring(tmp.lastIndexOf("/") + 1); // String filename = third + second + first; // File f = new File(cacheDir, filename); File f = Tools.getFile(actName, url); OutputStream os = new FileOutputStream(f); InputStream inputStream = null; try { inputStream = entity.getContent(); BitmapFactory.Options o = new BitmapFactory.Options(); o.inPreferredConfig = Bitmap.Config.RGB_565; o.inDither = false; o.inPurgeable = true; final Bitmap bitmap = BitmapFactory.decodeStream( inputStream, null, o); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); bitMapCache.put(url, new SoftReference<Bitmap>(bitmap)); return bitmap; } finally { if (inputStream != null) { inputStream.close(); } if (null != os) { os.close(); } entity.consumeContent(); } } } catch (Exception e) { getRequest.abort(); } finally { if (client != null) { client.close(); } } return null; } }当年为了防止图片爆掉出现OOM,使用了软引用,觉得还不错;图片加载的基本原理就是,把url+imageview抛过来,然后开启异步线程加载,根据获取的byte流decode成bitmap,最后在UI线程将图片加载到imageview上;也没有说做本地缓存,仅做了应用缓存;没有对图片进行压缩或者设置格式,使占用内存更小,展示也更合理;LruCache基本原理跟上面使用软引用的过程差不多,只不过多限制了图片占用内存的大小,计算图片使用的频率,对应用层、SD卡层均做了封装。
上面介绍完,相信你也对图片加载有个大概的轮廓,我们拿开源的imageloader为例,来讲讲图片加载框架的一些细节
public final class ImageLoaderConfiguration { final Resources resources;//主要给图片设计宽高时,获得屏幕宽高使用
final int maxImageWidthForMemoryCache;//内存中最大的图片宽度
final int maxImageHeightForMemoryCache;//内存中最大的图片高度
final int maxImageWidthForDiskCache;//SD卡中最大的图片宽度
final int maxImageHeightForDiskCache;//SD卡中最大的图片高度
final BitmapProcessor processorForDiskCache;//从SD卡获得Bitmap的加载器
final Executor taskExecutor;//加载图片时的执行器
final Executor taskExecutorForCachedImages;//加载缓存时的执行器
final boolean customExecutor;//是否使用默认执行器
final boolean customExecutorForCachedImages;//是否使用默认缓存执行器
final int threadPoolSize;//线程数,可以用来控制展示当前界面的item图片
final int threadPriority;//线程的执行优先级
final QueueProcessingType tasksProcessingType;//是LILO还是LIFO,默认是前者,但一般喜欢后者
final MemoryCache memoryCache;//内存缓存对象,如不写可用默认
final DiskCache diskCache;//SD卡缓存对象,如不写可用默认
final ImageDownloader downloader;//图片加载器,根据网络(http/s)、file、content、drawable、asset来加载
final ImageDecoder decoder;//图片解析器,根据获取的图片参数拿到Bitmap
final DisplayImageOptions defaultDisplayImageOptions;//设置图片加载状态和结果,见下面源码
final ImageDownloader networkDeniedDownloader;//不用网络下载图片的下载器,可理解为加载SD卡图片的加载器
final ImageDownloader slowNetworkDownloader;//仅网络下载图片的下载器,支持断点续传
拿这些变量来讲,基本就可以说明事情
public final class DisplayImageOptions { private final int imageResOnLoading;//图片是否加载中 private final int imageResForEmptyUri;//图片是否来自于空url private final int imageResOnFail;//图片是否加载失败 private final Drawable imageOnLoading;//加载中的图片 private final Drawable imageForEmptyUri;//空数据的图片 private final Drawable imageOnFail;//加载失败的图片 private final boolean resetViewBeforeLoading;//加载完是否重置(意味着放弃之前的加载) private final boolean cacheInMemory;//是否缓存在内存中 private final boolean cacheOnDisk;//是否缓存在SD卡中 private final ImageScaleType imageScaleType;//要多大的图片,统一设置 private final Options decodingOptions;//Bitmap的options对象 private final int delayBeforeLoading;//是否延迟加载,可用于非当前页面图片 private final boolean considerExifParams;//是否支持jpeg图片的rotate和flip等方法 private final Object extraForDownloader;//额外数据 private final BitmapProcessor preProcessor;//加载不在内存中的图片 private final BitmapProcessor postProcessor;//加载在图片中的图片 private final BitmapDisplayer displayer;//展示图片 private final Handler handler;//这个就不用讲了吧,跟主线程交互必不可少的工具 private final boolean isSyncLoading;//是否同步加载
补充一下,上面框架还支持给图片设置像素点占位大小;看到这么多功能,对于现在的项目基本满足要求,因此就不打算换了,再看看其他几种图片加载框架的异同
fresco,facebook出品,最大的优势在于可展示加载过程,即加载进度、加载前图片、加载中图片、加载后图片、加载失败图片等,还可以设置图片的形状;并且提供加载Gif、Webp图片的方法。
picasso,加载更快,因为默认设置图片格式占内存小
Glide是升级版本的piccaso,支持跟fragment和activity生命周期绑定
volly,基于老的imageloader又做了次封装,差别不是太大,功能弱化一些
而后面这几种框架,都是在imageloader兴起之后出现的,所以也基本支持它,在我看来也仅有fresco和glide是真正写出了跟原框架不同的东西。