Universal-Image-Loader完全解析(上)
基本介紹及使用
大家平時做專案的時候,或多或少都會接觸到非同步載入圖片,或者大量載入圖片的問題,而載入圖片時候經常會遇到各種問題,如oom,圖片載入混亂等。對於剛入門的新手來說,這些問題目前解決起來還比較因難,因此放多開源圖片載入的框架就應運而生,其中Universal-Image-Loader就是裡面的佼佼者。今天我們主要是針對這個開源框架進行解析,此框架的原始碼存在在Github上面,具體地址:https://github.com/nostra13/Android-Universal-Image-Loader,我們可以先看看這個框架有哪些特點:
- 多執行緒圖片下載,圖片可來自網路、專案資料夾assets中以及drawable中等
- 支援隨意配置ImageLoader,例如執行緒池,記憶體快取策略,硬碟快取策略等
- 支援圖片的記憶體快取、檔案快取以及SD卡快取等
- 支援載入圖片過程中的各種事件的監聽
- 能根據ImageView的大小對Bitmap進行裁剪,減少Bitmap佔用過多的記憶體
- 支援控制圖片的載入過程,例如暫停圖片載入、重新載入圖片,一般在ListView或者 GridView等滑動時暫停載入圖片,停止滑動時再去載入圖片
- 提供在較慢的網路下對圖片進行載入
接下來我們進行這種開源框架的簡單使用吧!
新增Jar包
新建一個Android專案,並在上面的地址中下載框架專案的jar包,然後匯入到libs工程目錄下
可以新建立一個MyApplication繼承Application,並在OnCreate中建立ImageLoader的配置引數,並進行初始化:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//建立預設的ImageLoade配置引數
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
//初始化ImageLoade引數
ImageLoader.getInstance().init(configuration);
}
}
ImageLoaderConfiguration是ImageLoader的配置引數,我們可以由程式碼看出其使用了單例模式和建造者模式,這裡直接用createDefault()方法直接建立一個預設的ImageLoaderConfiguration,當然我們也可以直接設定自己的ImageLoaderConfiguration,設定如下
File cacheDir = StorageUtils.getCacheDirectory(context);
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.memoryCacheExtraOptions(480, 800) // default = device screen dimensions
.diskCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null)
.taskExecutor(...)
.taskExecutorForCachedImages(...)
.threadPoolSize(3) // default
.threadPriority(Thread.NORM_PRIORITY - 1) // 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)
.diskCacheFileCount(100)
.diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
.imageDownloader(new BaseImageDownloader(context)) // default
.imageDecoder(new BaseImageDecoder()) // default
.defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
.writeDebugLogs()
.build();
這裡是所有的選項引數,我們在專案中根據自己需求去設定即可,一般來說,使用預設的createDefault()方法建立configuration建立即可。之後呼叫ImageLoader的init()方法將ImageLoaderConfiguration引數傳遞進去。
進行Android Manifest配置
<manifest>
<uses-permission android:name="android.permission.INTERNET" />
<!-- Include next permission if you want to allow UIL to cache imageson SD card -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...
<application android:name="MyApplication">
...
</application>
</manifest>
接下來我們可以進行載入圖片了,首先我們可以定義好Activity的佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_picture" />
</FrameLayout>
裡面只包含了一個ImageView控制元件,接下來我們去載入圖片,我們可以觀察ImageLoader專案原始碼可以看出,其提供了幾個圖片載入的方法,主要有dispalyImage()、loadImage()、loadImageSync(),其中,我們可以由方法名可以看出loadImageSync()方法是同步的,因為我們知道Android4.0版本以上有個特點,網路操作不能在主執行緒操作,因此,一般來說,loadImageSync()方法我們不去使用。
LoadImage()載入圖片
我們可以使用ImageLoader的loadImage()方法來載入網路上的圖片資源
private void initData() {
String imgUrl = "http://img4.imgtn.bdimg.com/it/u=1326316882,880909110&fm=21&gp=0.jpg";
ImageLoader.getInstance().loadImage(imgUrl, new ImageLoadingListener() {
@Override
public void onLoadingStarted(String s, View view) {
//開始載入圖片
}
@Override
public void onLoadingFailed(String s, View view, FailReason failReason) {
//圖片載入失敗
}
@Override
public void onLoadingComplete(String s, View view, Bitmap bitmap) {
//圖片載入完成
}
@Override
public void onLoadingCancelled(String s, View view) {
//圖片載入取消
}
});
}
傳入圖片的Url地址和監聽圖片載入情況的ImageLoaderListener監聽器,在回撥方法的OnLoadingComplete()中將LoadImage()設定到所在的ImageView控制元件上就行了,如果你覺得傳入的監聽器中要實現的方法太多,那麼也可以選擇SimpleImageLoadingListener類,此類提供了ImageLoadingListener類中方法的實現,我們可根據需要選擇實現的方法即可。
ImageLoader.getInstance().loadImage(imgUrl,new SimpleImageLoadingListener(){
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
}
});
如果我們要指定圖片的大小,則就應該在開始載入圖片前指定一個ImageSize物件,並指定其寬高,之後再傳入給loadImage()即可。
ImageSize mImageSize = new ImageSize(100,100);
ImageLoader.getInstance().loadImage(imgUrl,mImageSize,new SimpleImageLoadingListener(){
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
}
});
上面只是簡單的載入網路圖片,但在實際的開發中,因為涉及到是否需要使用記憶體快取、是否使用檔案快取等等。這時我們就會用到DisplayImageOptions()來配置這些相關圖片的選項。具體如下
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.ic_loading) // resource or drawable
.showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
.showImageOnFail(R.drawable.ic_fail) // resource or drawable
.resetViewBeforeLoading(false) // default
.delayBeforeLoading(1000)
.cacheInMemory(false) // default
.cacheOnDisk(false) // default
.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
.handler(new Handler()) // default
.build();
因此我們可以進行進一步對圖片進行配置
private void initData() {
String imgUrl = "http://img4.imgtn.bdimg.com/it/u=1326316882,880909110&fm=21&gp=0.jpg";
final ImageSize mImageSize = new ImageSize(100, 100);
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
ImageLoader.getInstance().loadImage(imgUrl, mImageSize,options, new SimpleImageLoadingListener(){
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
mImageView.setImageBitmap(loadedImage);
}
});
}
我們使用了DisplayImageOptions()來配置圖片的一些選項,上面的程式碼將圖片進行記憶體快取、硬碟快取,這樣我們就不用每次載入圖片就從網路上載入。但是一些選項對於loadImage()方法是無效的,如showImageOnLoading()、showImageForEmptyUri()等。
DisplayImage()載入圖片
我們也可以選用DisplayImage()方法來載入網路圖片
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.ic_loading)
.showImageOnFail(R.drawable.ic_fail)
.cacheInMemory(true)
.cacheOnDisk(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
ImageLoader.getInstance().displayImage(imgUrl, mImageView, options);
從上面可以看出,我們利用DisplayImageOptions()方法來載入圖片更加方便快捷,省去了ImageLoadingListener介面的監聽,也無需手動設定顯示Bitmap物件,我們直接將ImageView物件作為引數傳入到displayImage()即可,在配置的引數options中,我們設定了正在載入時顯示的圖片,以及圖片載入錯誤時顯示的圖片。
我們在載入圖片的過程中,我們有需要顯示圖片下載進度的需求,Universal-Image-Loader當然也有這樣子的功能,我們只用在displayImage()方法中傳入ImageLoadingProcessListener介面,如下所示:
ImageLoader.getInstance().displayImage(imgUrl, mImageView, options, new SimpleImageLoadingListener(), new ImageLoadingProgressListener() {
@Override
public void onProgressUpdate(String imageUrl, View view, int current, int total) {
//得到圖片的載入進度並進行更新
}
});
載入其他來源的圖片
使用此專案框架不僅我們可以載入網路圖片,還可以載入sd卡中的圖片,ContentProvider等。使用時只需要將圖片的地址Url修改一下就可以了。比如下面是載入檔案系統上的圖片程式碼
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.ic_loading)
.showImageOnFail(R.drawable.ic_fail)
.cacheInMemory(true)
.cacheOnDisk(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
String imagePath = "/mnt/sdcard/image.png";
String imagUrl = ImageDownloader.Scheme.FILE.wrap(imagePath);
ImageLoader.getInstance().displayImage(imagUrl, mImageView, options);
對於不同的圖片來源,我們只要在每個圖片來源的地方加上Scheme包裹起來(Content provider)除外,然後當作圖片的Url傳給imageLoader中,之後專案框架就會根據不同的Scheme來獲取相關的輸入流
//圖片來源於Content Provider
String contentProviderUrl = "content://media/external/audio/albumart/14";
//圖片來源於assets資原始檔夾
String assetsUrl = Scheme.ASSENTS.wrap("image.png");
//圖片來源於drawable資料夾
String drawableUrl = Scheme.DRAWABLE.wrap("R.drawable.image");
ListView、GridView載入圖片
我們一般情況下要展示大量的圖片時都是採用GridView、ListView元件,而我們在這些元件上滾動時,我們可以停止圖片的載入,而當停止滑動時,我們就可以載入當前介面上的圖片。這個框架也提供這樣的功能,使用起來也很簡單,這提供了PauseOnScrollListener這個類來控制ListView或者GridView滑動過程中停止載入圖片。
listView.setOnScrollListener(new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling));
gridView.setOnScrollListener(new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling));
其中第一個引數就是ImageLoader物件,第二個引數是控制是否在滑動過程中暫停載入圖片,如果需要暫停則傳入true,第三個引數是控制快速滑動介面時是否載入圖片。
OutOfMemoryError
這個框架有很好的快取機制,能夠有效的避免OOM現象的產生,在這個框架中,對於OutOfMemoryError只是做了簡單的捕獲catch,從而保證我們的程式能夠避免遇到OOM現象而不被crash掉,如果使用該框架經常發生OOM,則我們可以根據下面的配置進行改善。
- 儘量減少執行緒池中的個數,可以在初始化的ImageLoaderConfiguration中的threadPoolSize中配置,一般是1-5執行緒
- 減少圖片記憶體消耗,在DisplayImageOptions選項中配置bitmapConfig為Bitmap.Config.RGB_565,預設是ARGB_8888,這樣記憶體至少減少一半
- 可使用軟引用,在ImageLoaderConfiguration中配置圖片的記憶體快取為memoryCache(new WeakMemoryCache())或者不使用記憶體快取
- 設定圖片的大小,可在DisplayImageOptions選項中設定.imageScaleType(ImageScaleType.IN_SAMPLE_INT)或者imageScaleType(imageScaleType.EXACTLY)
總結
通過上面的對Universal-Image-Loader框架的學習,相信對於一些簡單的運用也已經來說比較瞭解,在使用框架的時候儘量用displayImage()方法去載入圖片,loadImage()是將圖片物件進行回撥到ImageLoadingListener介面的onLoadingComplete()方法,從而將圖片資源設定到ImageView控制元件上。如果我們需要裁剪圖片時,可以向LoadImage()方法傳遞一個ImageSize物件,而displayImage()則會根據ImageView控制元件設定的測量值來裁剪圖片,其次,displayImage()方法中對ImageView物件使用的是Weak references,方便垃圾回收器回收。
今天的分享就到這裡,接下來我會針對此框架講解圖片的快取策略,希望各位繼續關注。