Universal-Image-Loader完全解析(上)

Mz_Chris發表於2015-06-26

Universal-Image-Loader完全解析(上)

基本介紹及使用

大家平時做專案的時候,或多或少都會接觸到非同步載入圖片,或者大量載入圖片的問題,而載入圖片時候經常會遇到各種問題,如oom,圖片載入混亂等。對於剛入門的新手來說,這些問題目前解決起來還比較因難,因此放多開源圖片載入的框架就應運而生,其中Universal-Image-Loader就是裡面的佼佼者。今天我們主要是針對這個開源框架進行解析,此框架的原始碼存在在Github上面,具體地址:https://github.com/nostra13/Android-Universal-Image-Loader,我們可以先看看這個框架有哪些特點:

  1. 多執行緒圖片下載,圖片可來自網路、專案資料夾assets中以及drawable中等
  2. 支援隨意配置ImageLoader,例如執行緒池,記憶體快取策略,硬碟快取策略等
  3. 支援圖片的記憶體快取、檔案快取以及SD卡快取等
  4. 支援載入圖片過程中的各種事件的監聽
  5. 能根據ImageView的大小對Bitmap進行裁剪,減少Bitmap佔用過多的記憶體
  6. 支援控制圖片的載入過程,例如暫停圖片載入、重新載入圖片,一般在ListView或者 GridView等滑動時暫停載入圖片,停止滑動時再去載入圖片
  7. 提供在較慢的網路下對圖片進行載入

接下來我們進行這種開源框架的簡單使用吧!

新增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,方便垃圾回收器回收。

今天的分享就到這裡,接下來我會針對此框架講解圖片的快取策略,希望各位繼續關注。

相關文章