抽点时间写点关于Android-Universal-Image-Loader的使用,虽然很多人在开发中可能用到了各种各样的图片加载框架,比如:Gilde、Frescon、Picasso等,以及Volley,xUtils等这些框架中的图片加载都是很优秀的图片加载框架。但是,本人在开发中是非常喜欢Android-Universal-Image-Loader这款框架,不仅仅是因为这框架历史悠久,主要是它实现图片加载的实现值得我们去深入学习,对于异步加载图片,加载图片时的错乱,加载大量图片时的内存溢出的处理等等。(当然,可能是本人最先使用的图片加载框架吧)今天主要基于universal-image-loader-1.9.4来写的,该框架的GitHub的地址链接如下:
https://github.com/nostra13/Android-Universal-Image-Loader
该框架的一些主要特点如下:
1、支持可定制化,可以更具自己项目的需求进行配置:下载的线程池,图片下载器,内存缓存策略(可自定义)等
2、多线程异步加载和显示图片,图片来源广泛如:网络图片,sdcard图片,assets文件夹下的图片,drawable与mipmap文件夹下的图片,系统里面的视频缩略图(.9patch不能加载)
3、支持图片的内存缓存,sdcard缓存。(图片缓存可以设置,先进先出,使用最少的,占用内存最大的先移除的配置)
4、支持图片加载过程中的监听,可以暂停加载图片,图片在加载中的操作。
总的来说,该具备的功能与操作它基本俱全。那在开发中我们如何快速配置使用它呢。
下载下来资源后,如果你用的是Eclipse,直接把universal-image-loader-1.9.4.jar包添加到你的libs下。如果你用的AS的话,先把jar解压出来后找到里面maven/pom.properties的文件然后打开。如下图:
按照上面显示的信息在gradle里面进行配置:compile ‘com.nostra13.universalimageloader:universal-image-loader:1.9.4’
在AndroidManifest.xml里面配置ImageLodaer需要的权限
<uses-permission android:name="android.permission.INTERNET">uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">uses-permission>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS">uses-permission>
File cacheDir = StorageUtils.getCacheDirectory(getApplicationContext()); //缓存文件夹路径
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.memoryCacheExtraOptions(480, 800) // default = device screen dimensions 内存缓存文件的最大长宽
.diskCacheExtraOptions(480, 800, null) // 本地缓存的详细信息(缓存的最大长宽),最好不要设置这个
.threadPoolSize(3) // default 线程池内加载的数量
.taskExecutor(...)
.taskExecutorForCachedImages(...)
.threadPriority(Thread.NORM_PRIORITY - 2) // default 设置当前线程的优先级
.tasksProcessingOrder(QueueProcessingType.FIFO) // default
.denyCacheImageMultipleSizesInMemory()
.memoryCache(new LruMemoryCache(2 * 1024 * 1024)) //可以通过自己的内存缓存实现
.memoryCacheSize(2 * 1024 * 1024) // 内存缓存的最大值
.memoryCacheSizePercentage(13) // default
.diskCache(new UnlimitedDiscCache(cacheDir)) // default 可以自定义缓存路径
.diskCacheSize(50 * 1024 * 1024) // 50 Mb sd卡(本地)缓存的最大值
.diskCacheFileCount(100) // 可以缓存的文件数量
// default为使用HASHCODE对UIL进行加密命名, 还可以用MD5(new Md5FileNameGenerator())加密
.diskCacheFileNameGenerator(new HashCodeFileNameGenerator())
.imageDownloader(new BaseImageDownloader(context)) // default
.imageDecoder(new BaseImageDecoder()) // default
.defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
.writeDebugLogs() // 打印debug log
.build(); //开始构建
这些配置一般最好在Application中进行配置(根据自己实际情况进行配置,没有必要也不需要全部进行配置),全局调用。配置好这些后,记得调用ImageLoader.getInstance().init(config);
进行初始化。但是如果你觉的麻烦可以使用默认的配置ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
不推荐使用默认配置。
ImageLoader mImageLoader = ImageLoader.getInstance();
注意,ImageLoader 是单例模式。有兴趣可以看看源码里面是如何设计ImageLoader的。
ImageLoader.getInstance().displayImage(uri, imageView);
ImageLoader.getInstance().displayImage(uri, imageView, options);
ImageLoader.getInstance().displayImage(uri, imageView, listener);
ImageLoader.getInstance().displayImage(uri, imageView, options, listener);
ImageLoader.getInstance().displayImage(uri, imageView, options, listener, progressListener);
ImageLoader.getInstance().loadImage(uri, imageView);
ImageLoader.getInstance().loadImage(uri, imageView, options);
ImageLoader.getInstance().loadImage(uri, imageView, listener);
ImageLoader.getInstance().loadImage(uri, imageView, options, listener);
ImageLoader.getInstance().loadImage(uri, imageView, options, listener, progressListener);
等等都是用来对图片进行加载操作的。其中里面的options是什么呢?这是ImageLoader为我们提供在图片在加载的对图片进行一系列操作提供的方法。
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.ic_stub) // 设置图片下载期间显示的图片
.showImageForEmptyUri(R.drawable.ic_empty) // 设置图片Uri为空或是错误的时候显示的图片
.showImageOnFail(R.drawable.ic_error) // 设置图片加载或解码过程中发生错误显示的图片
.resetViewBeforeLoading(false) // default 设置图片在加载前是否重置、复位
.delayBeforeLoading(1000) // 下载前的延迟时间
.cacheInMemory(false) // default 设置下载的图片是否缓存在内存中
.cacheOnDisk(false) // default 设置下载的图片是否缓存在SD卡中
.preProcessor(...)
.postProcessor(...)
.extraForDownloader(...)
.considerExifParams(false) // default
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default 设置图片以如何的编码方式显示
.bitmapConfig(Bitmap.Config.ARGB_8888) // default 设置图片的解码类型
.decodingOptions(...) // 图片的解码设置
.displayer(new SimpleBitmapDisplayer()) // default 还可以设置圆角图片new RoundedBitmapDisplayer(20)
.handler(new Handler()) // default
.build();
看到这里的朋友可能会注意到.displayImage(uri, imageView)
和 .loadImage(uri, imageView)
这两个方法里面是没有Options的,那么调用这写没有把Options传递进去的话,那么从配置默认显示选项
(ImageLoaderConfiguration.defaultDisplayImageOptions(…))将被使用。
自定义配置中我们需要理解的内容:
1、ImageLoaderConfiguration的builder中的diskCache(),diskCacheSize()和diskCacheFileCount()这三个方法彼此功能重叠,设置了其中一个就不用设置另外两个了。
2、ImageLoaderConfiguration的builder中的memoryCache()方法和memoryCacheSize()方法彼此重叠,设置了其中一个就不用设置另外一个了。
3、DiskCache磁盘缓存中UnlimitedDiscCache不限制缓存大小;UnlimitedDiscCache简单的继承了BaseDiscCache并未对其做任何扩展;LimitedAgeDiscCache限制缓存时间;LimitedAgeDiscCache该类实现了在缓存中删除被加载超过规定时间的文件:满足以下条件的时候就从缓存中删除文件:系统当前时间-文件的最新修改时间 > maxFileAge;
4、MemoryCache内存缓存UsingFreqLimitedCache (最少被用到的对象会被删除) ;UsingAgeLimitedCache (最早被添加的对象会被删除) ;LargestLimitedCache (空间占用最大的对象会被删除);FIFOLimitedCache (根据先进先出的原则上删除多余对象);通过实现接口MemoryCacheAware < String,Bitmap >来实现自己的缓存。
5、.imageScaleType(ImageScaleType imageScaleType) 中imageScaleType缩放类型的属性EXACTLY :图像将完全按比例缩小的目标大小;EXACTLY_STRETCHED:图片会缩放到目标大小完全;IN_SAMPLE_INT:图像将被二次采样的整数倍;IN_SAMPLE_POWER_OF_2:图片将降低2倍,直到下一减少步骤,使图像更小的目标大小;NONE:图片不会调整。
6、.displayer(BitmapDisplayer displayer)中displayer显示方式RoundedBitmapDisplayer(int roundPixels)设置圆角图片;FakeBitmapDisplayer()这个类什么都没做;FadeInBitmapDisplayer(int durationMillis)设置图片渐显的时间;SimpleBitmapDisplayer()正常显示一张图片(默认的)。
在GirdView,ListView加载图片的情况下会出现快速滑动的操作,实际开发中为了更加友好的用户体验,在快速滑动的情况下是不用加载图片的,只有停止后才去加载图片增加流畅度。ImageLodaer内置了滑动监听的操作的代理模式。
public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling{
this(imageLoader, pauseOnScroll, pauseOnFling, null);
}
public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling,
OnScrollListener customListener) {
this.imageLoader = imageLoader;
this.pauseOnScroll = pauseOnScroll;
this.pauseOnFling = pauseOnFling;
externalListener = customListener;
}
pauseOnScroll-是否在滑动过程中停止加载图片;pauseOnFling-是否在快速猛的滑动中停止加载图片。
实际使用:mImgsListView.setOnScrollListener(new PauseOnScrollListener(mImageLoader,true,true));
public interface ImageLoadingListener {
/**
* Is called when image loading task was started (开始加载)
*
* @param imageUri Loading image URI
* @param view View for image
*/
void onLoadingStarted(String imageUri, View view);
/**
* Is called when an error was occurred during image loading (加载失败)
*
* @param imageUri Loading image URI
* @param view View for image. Can be null.
* @param failReason {@linkplain com.nostra13.universalimageloader.core.assist.FailReason The reason} why image
* loading was failed
*/
void onLoadingFailed(String imageUri, View view, FailReason failReason);
/**
* Is called when image is loaded successfully (and displayed in View if one was specified) (加载成功)
*
* @param imageUri Loaded image URI
* @param view View for image. Can be null.
* @param loadedImage Bitmap of loaded and decoded image
*/
void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);
/**
* Is called when image loading task was cancelled because View for image was reused in newer task (加载取消)
*
* @param imageUri Loading image URI
* @param view View for image. Can be null.
*/
void onLoadingCancelled(String imageUri, View view);
}
public interface ImageLoadingProgressListener {
/**
* Is called when image loading progress changed.
*
* @param imageUri Image URI
* @param view View for image. Can be null.
* @param current Downloaded size in bytes
* @param total Total size in bytes
*/
void onProgressUpdate(String imageUri, View view, int current, int total);
}
AndroidManifest.xml代码
<uses-permission android:name="android.permission.INTERNET">uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">uses-permission>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS">uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="com.example.rrichard.myapplication.MainApplication"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
application>
Gradle中配置
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'
}
Application代码
package com.example.rrichard.myapplication;
import android.app.Application;
import android.os.Environment;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import java.io.File;
/**
* Created by Rrichard on 2015/10/17.
*/
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initConfig();
}
/**
* 配置ImageLoader基本属性,最好放在Application中(只能配置一次,如多次配置,则会默认第一次的配置参数)
*/
private void initConfig() {
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.threadPriority(Thread.NORM_PRIORITY - 2)//设置线程优先级
.threadPoolSize(4)//线程池内加载的数量,推荐范围1-5内。
.denyCacheImageMultipleSizesInMemory()//当同一个Uri获取不同大小的图片缓存到内存中时只缓存一个。不设置的话默认会缓存多个不同大小的图片
.memoryCacheExtraOptions(480, 800)//内存缓存文件的最大长度
.memoryCache(new LruMemoryCache(10 * 1024 * 1024))//内存缓存方式,这里可以换成自己的内存缓存实现。(推荐LruMemoryCache,道理自己懂的)
.memoryCacheSize(10 * 1024 * 1024)//内存缓存的最大值
.diskCache(new UnlimitedDiskCache(createSavePath()))//可以自定义缓存路径
.diskCacheFileNameGenerator(new Md5FileNameGenerator())//对保存的URL进行加密保存
.defaultDisplayImageOptions(DisplayImageOptions.createSimple())
.imageDownloader(new BaseImageDownloader(getApplicationContext(), 5 * 1000, 30 * 1000))//设置连接时间5s,超时时间30s
.writeDebugLogs()
.build();
ImageLoader.getInstance().init(config);
}
/**
* 创建存储缓存的文件夹路径
*
* @return
*/
private File createSavePath() {
String path;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
path = Environment.getExternalStorageDirectory().getPath() + "/TestCash/";
} else {
path = "/TestCash/";
}
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
return file;
}
}
Adapter
package com.example.rrichard.myapplication;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.util.List;
/**
* Created by Rrichard on 2015/10/17.
*/
public class ImgsAdapter extends BaseAdapter {
private Context mContext;
private List imgsList;
private LayoutInflater mInflater;
private DisplayImageOptions mImageOptions;
public ImgsAdapter(Context ctx, List list, DisplayImageOptions options) {
this.mContext = ctx;
this.imgsList = list;
this.mImageOptions = options;
mInflater = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
return imgsList.size();
}
@Override
public Object getItem(int position) {
return imgsList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder mViewHolder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.imgs_layout, parent, false);
mViewHolder = new ViewHolder();
mViewHolder.img_iv = (ImageView) convertView.findViewById(R.id.show_img_iv);
convertView.setTag(mViewHolder);
} else {
mViewHolder = (ViewHolder) convertView.getTag();
}
ImageLoader.getInstance().displayImage(imgsList.get(position), mViewHolder.img_iv, mImageOptions);
return convertView;
}
private static class ViewHolder {
ImageView img_iv;
}
}
MainActivity
package com.example.rrichard.myapplication;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.ListView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;
import com.nostra13.universalimageloader.core.listener.PauseOnScrollListener;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private ListView mImgsListView;
private ImgsAdapter mImgsAdapter;
private List imgsStrList = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mImgsListView = (ListView) findViewById(R.id.imgs_listview);
imgsStrList.addAll(setImageData());
mImgsAdapter = new ImgsAdapter(this,imgsStrList,setImageOptionsConfig());
mImgsListView.setAdapter(mImgsAdapter);
ImageLoader mImageLoader = ImageLoader.getInstance();
mImgsListView.setOnScrollListener(new PauseOnScrollListener(mImageLoader, true, true));
}
/**
* 配置图片加载时候的配置,在实际开发中可以对这些参数进行一次封装。
*/
private DisplayImageOptions setImageOptionsConfig(){
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.mipmap.ic_launcher)//设置图片在下载期间显示的图片
.showImageForEmptyUri(R.mipmap.ic_launcher)//设置图片Uri为null或是错误的时候显示的图片
.showImageOnFail(R.mipmap.ic_launcher)//设置图片加载/解码过程中错误时显示的图片
.cacheInMemory(true)//设置下载的图片是否缓存在内存中
.cacheOnDisk(true)//设置下载的图片是否缓存在SD卡中
.considerExifParams(true)//是否考虑JPEG图像的旋转,翻转
.imageScaleType(ImageScaleType.IN_SAMPLE_INT)//设置图片以如何的编码方式显示
.bitmapConfig(Bitmap.Config.ARGB_8888)//设置图片的解码类型
.resetViewBeforeLoading(true)//设置图片在下载前是否重置和复位
.displayer(new SimpleBitmapDisplayer())//不设置的时候是默认的
//.displayer(new RoundedBitmapDisplayer(20))//是否为圆角,弧度是多少
//displayer()还可以设置渐入动画
.build();
return options;
}
private List setImageData(){
List mImags = new ArrayList();
mImags.add("http://imgsrc.baidu.com/forum/pic/item/09be3f094b36acaf0ad6eb717cd98d1000e99cde.jpg");
mImags.add("http://img.popoho.com/UploadPic/2012-1/2012110163244154.jpg");
mImags.add("http://img.popoho.com/UploadPic/2012-1/2012110163244261.jpg");
mImags.add("http://img.popoho.com/UploadPic/2012-1/2012110163244424.jpg");
mImags.add("http://21chance.com/upload/22/findings/1444922543-5984.jpg");
return mImags;
}
@Override
protected void onDestroy() {
super.onDestroy();
ImageLoader.getInstance().clearMemoryCache();
ImageLoader.getInstance().clearDiskCache();
}
}
在使用ImageLoader时如果出现OOM的情况,我们可以从下面的思路去优化
1、减少配置的线程池的大小(threadPoolSize 推荐1-5);
2、使用.bitmapConfig(Bitmap.config.RGB_565)代替ARGB_8888;
3、使用.imageScaleType(ImageScaleType.IN_SAMPLE_INT)或者.imageScaleType(ImageScaleType.EXACTLY);
4、避免使用RoundedBitmapDisplayer.他会创建新的ARGB_8888格式的Bitmap对象;
5、如果以上都解决不了你的问题,可以尝试禁用内存中的缓存cacheInMemory(false),或者使用软引用.memoryCache(new WeakMemoryCache())。
注意在程序退出时我们可以调用
ImageLoader.getInstance().clearMemoryCache(); // 清除内存缓存
ImageLoader.getInstance().clearDiskCache(); // 清除本地缓存
"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)
(^o^)/~!大致说到这吧,可能有的地方不足欢迎底下留言提出,本人吃中饭去….