Android ImageLoader(Android-Universal-Image-Loader)【1】概述及使用简介
一,前言:为什么要引入Android-Universal-Image-Loader?
众所周知,简单的几个ImageView加载几个图像资源、或者这几个图像资源是从本地加载时无需考虑过多直接加载即可,但当成千上百个ImageView加载成千上百个图像、尤其是当这些图片还是从网络中异步获取,那么需要考虑的问题细节很多很繁琐且容易出错,现在随便举例其中几条:
(1)最基本的问题,网络不可靠,可能在不可靠网络加载过程中,图片加载发生难以预估的失败。
(2)已经从网络或本地中加载成功的图片,应该避免重复加载,重复加载造成网络流量浪费,以及设备计算资源的重复浪费,因此需要考虑图片缓存策略。缓存分为两级缓存:第一级:内存缓存,第二级:“硬盘”缓存(通常是手机的外置存储如SD卡和内置存储)。实现这样的层级缓存策略需要自己维护和组织。内存缓存可以考虑使用Android的LruCache,详情参考我的另外两篇文章:
a、《使用新式LruCache取代SoftReference缓存图片,Android异步加载图片》,文章链接地址:http://blog.csdn.net/zhangphil/article/details/43667415
b、或者自己按照LruCache设计思路实现和管理内存管理,《基于Java LinkedList,实现Android大数据缓存策略》,文章链接地址:http://blog.csdn.net/zhangphil/article/details/44116885
硬盘缓存则要自己建立缓存索引和缓存文件结构(如何建立缓存目录?内存在何时机把硬盘缓存的图片加入等等问题)。
(3)设想这一种情况,在一个Android竖直方向上ListView中有成千上万条图片item,每条item中的图片均需从网络获取。用户手指在屏幕上快速滑动,滑动过程中,极有可能可见视野内的图片还没有加载完成后,用户已经快速的往下滑看下面的图片去了。而上面已经消失的图片加载线程如果置之不理任由其运作,那么,当用户在不断的下拉和上拉过程中,将会造成线程不断的重建和运行,内存开销极大。而对于用户来来,最紧迫的当前可见视野的图片加载显示可能因为线程过多而被无限期拖延到最后显示。这种情况一般得应对策略师自己维护和管理一个线程池(关于Java线程池,详情请参考我的另外一篇文章:《Java线程池:ExecutorService,Executors》,文章链接地址:http://blog.csdn.net/zhangphil/article/details/43898637 ),自己管理和维护多线程下载任务队列,显然,需要考虑的线程队列问题很多,很繁琐。
等等还有很多未列举出来的细节问题。
为了避免重复造轮子,这种情况下最好考虑使用一些业界比较成熟稳定的开源框架。
Android ImageLoader(Android-Universal-Image-Loader),是github上的一个第三方开源图像加载库。该项目在github上的链接地址:
https://github.com/nostra13/Android-Universal-Image-Loader
Android-Universal-Image-Loader主要应用领域是ImageView加载图片。该开源框架对上述问题给予了充分的解决。并提供了其他额外的附加功能(如加载的图片尺寸,加载动画等等)。
二、Android-Universal-Image-Loader使用简介。
首先到Android-Universal-Image-Loader官方网址下载项目包,使用可以分为两种方法
(1)把Android-Universal-Image-Loader的全部实现源代码(*.java)放入到自己的项目目录src下,当作是自己的源代码使用。
(2)导入Android-Universal-Image-Loader的jar库文件,比如universal-image-loader-1.9.4.jar。
两种方式都可以,看个人偏好。
我用的是第一种方法,这样可以方便查阅甚至直接二次定制修改Android-Universal-Image-Loader的源代码为自己所用。
代码结构层次如图:
然后就可以直接使用,现给出一个示例。
测试用的MainActivity.java:
package zhangphil.imageloader; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.assist.QueueProcessingType; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import android.app.ListActivity; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; public class MainActivity extends ListActivity { private ImageLoader mImageLoader = null; // 加载的图片资源URL private final String ZHANGPHIL_CSDN_LOGO_URL = "http://avatar.csdn.net/9/7/A/1_zhangphil.jpg"; // 加载的数目,假定数据总量很大 private final int ITEM_COUNT = 10000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ListView lv = this.getListView(); ArrayAdapter adapter = new MyArrayAdapter(this, -1); lv.setAdapter(adapter); mImageLoader = ImageLoader.getInstance(); mImageLoader.init(getImageLoaderConfiguration()); } private ImageLoaderConfiguration getImageLoaderConfiguration() { ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder( this) .threadPoolSize(3) // 线程数量 .threadPriority(Thread.NORM_PRIORITY) // 线程优先级 .tasksProcessingOrder(QueueProcessingType.FIFO) .denyCacheImageMultipleSizesInMemory() .memoryCacheSize(1024 * 1024 * 10) // 内存缓存的容量10MB .diskCacheFileCount(100)// 缓存的文件数量 .diskCacheSize(1024 * 1014 * 100)// 硬盘缓存的大小100MB .writeDebugLogs()// 输出日志 .build(); return config; } private DisplayImageOptions getDisplayImageOptions() { DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.loading) // 加载过程中显示的图片 .showImageForEmptyUri(R.drawable.ic_launcher) // 空URI显示的图片 .showImageOnFail(R.drawable.error) // 加载失败时候显示内容 .cacheInMemory(true) // 缓存到内存 .cacheOnDisk(true) // 缓存到硬盘 .considerExifParams(true) .displayer(new FadeInBitmapDisplayer(1000))// 淡入加载图片显示 .build(); return options; } private class MyArrayAdapter extends ArrayAdapter { private LayoutInflater inflater; private int resId = R.layout.item; private DisplayImageOptions mDisplayImageOptions; public MyArrayAdapter(Context context, int resource) { super(context, resource); inflater = LayoutInflater.from(getContext()); mDisplayImageOptions = getDisplayImageOptions(); } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) convertView = inflater.inflate(resId, null); ImageView imageView = (ImageView) convertView .findViewById(R.id.image); mImageLoader.displayImage(ZHANGPHIL_CSDN_LOGO_URL, imageView, mDisplayImageOptions); return convertView; } @Override public int getCount() { return ITEM_COUNT; } } // private File getMyCacheDir() { // File sdRoot = Environment.getExternalStorageDirectory(); // String myImageLoaderCacheFileDir = "ImageLodaerCache"; // File cacheFileDir = new File(sdRoot, myImageLoaderCacheFileDir); // return cacheFileDir; // } }
Item.xml文件:
<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/image" > </ImageView>
素材error.png和Loading.gif可以根据个人的需要选取不同的图片资源。
ImageLoader在使用之前需要做一些初始化工作,配置ImageLoaderConfiguration和DisplayImageOptions 。然后就可以直接使用ImageLoader的displayImage()方法从网络或本地存储中异步加载图片资源。而关于图片资源的缓存和异步下载线程池队列则交由ImageLoader为我们妥善在后台管理好。