Android主流圖片框架淺析

一、UIL——UniversalImageLoader

UIL可以算是老牌最火的圖片加載庫了,使用過這個框架的項目可以說多到教你做人,我第一次把第三方開源圖片加載框架加入項目中的就是這個了,當時感覺瞬間逼格上漲,媽媽再也不用擔心出現OOM和ListView圖片錯亂了。可惜的是該作者在項目中說明已經停止了對該項目的維護。這就意味着以後任何的 bug 都不會修復,任何的新特性都不會再繼續開發,所以毫無疑問 UIL 不推薦在項目中使用了。

使用方法:

1、在Application全局變量中的進行配置ImageLoaderConfiguration,有選擇性的進行配置,具體代碼如下:

ImageLoaderConfiguration config =newImageLoaderConfiguration.Builder(context)
    .memoryCacheExtraOptions(480, 800)// default = device screen dimensions
    .discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75)
    .taskExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
    .taskExecutorForCachedImages(AsyncTask.THREAD_POOL_EXECUTOR)
    .threadPoolSize(3)// default線程池數量
    .threadPriority(Thread.NORM_PRIORITY - 1)// default
    .tasksProcessingOrder(QueueProcessingType.FIFO)// default
    .denyCacheImageMultipleSizesInMemory()
    .memoryCache(newLruMemoryCache(2 * 1024 * 1024))//內存緩存
    .memoryCacheSize(2 * 1024 * 1024)
    .discCache(newUnlimitedDiscCache(cacheDir))// 磁盤緩存
    .discCacheSize(50 * 1024 * 1024)
    .discCacheFileCount(100)
    .discCacheFileNameGenerator(newHashCodeFileNameGenerator())// default
    .imageDownloader(newBaseImageDownloader(context))// default
    .imageDecoder(newBaseImageDecoder())// default
    .defaultDisplayImageOptions(DisplayImageOptions.createSimple())// default
    .enableLogging()
    .build();

2、針對每次加載任務進行配置DisplayImageOptions

DisplayImageOptions options =newDisplayImageOptions.Builder()
    .showStubImage(R.drawable.ic_stub)
    .showImageForEmptyUri(R.drawable.ic_empty)
    .showImageOnFail(R.drawable.ic_error)
    .resetViewBeforeLoading()
    .delayBeforeLoading(1000)
    .cacheInMemory()
    .cacheOnDisc()
    .preProcessor(...)
    .postProcessor(...)
    .extraForDownloader(...)
    .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)// default
    .bitmapConfig(Bitmap.Config.ARGB_8888)// default
    .decodingOptions(...)
    .displayer(newSimpleBitmapDisplayer())// default
    .handler(newHandler())// default
    .build();

UIL支持的圖片加載格式如下:

String imageUri ="http://site.com/image.png"; // from Web
String imageUri ="file:///mnt/sdcard/image.png"; // from SD card
String imageUri ="content://media/external/audio/albumart/13"; // from content provider
String imageUri ="assets://image.png"; // from assets
String imageUri ="drawable://"+ R.drawable.image; // from drawables (only images, non-9patch)

配置好後調用imageLoader.displayImage方法就OK了,妥妥的!

下面簡單分析一下,UIL框架的加載原理:

  1. ImageLoader圖片加載器,對外的主要 API,採取了單例模式,用於圖片的加載和顯示。
  2. MemoryCache圖片內存換成。默認使用了 LRU 算法。 LRU: Least Recently Used 近期最少使用算法, 選用了基於鏈表結構的 LinkedHashMap 作爲存儲結構。假設情景:內存緩存設置的閾值只夠存儲兩個 bitmap 對象,當 put 第三個 bitmap 對象時,將近期最少使用的 bitmap 對象移除。
  3. DiskCache圖片磁盤緩存,默認使用LruDiskCache算法,在緩存滿時刪除最近最少使用的圖片;緩存目錄下名爲journal的文件記錄緩存的所有操作
  4. 圖片加載流程
    1. 判斷圖片的內存緩存是否存在,若存在直接執行步驟 8;
    2. 判斷圖片的磁盤緩存是否存在,若存在直接執行步驟 5;
    3. ImageDownloader從網絡上下載圖片;
    4. 將圖片緩存在磁盤上;
    5. ImageDecoder將圖片 decode 成 bitmap 對象;
    6. BitmapProcessor根據DisplayImageOptions配置對圖片進行預處理(Pre-process Bitmap);
    7. 將 bitmap 對象緩存到內存中;
    8. 根據DisplayImageOptions配置對圖片進行後處理(Post-process Bitmap);
    9. 執行DisplayBitmapTask將圖片顯示在相應的控件上。

二、Picasso

Picasso是Square公司開源的一個Android平臺上的圖片加載框架,簡單易用,一句話搞定項目中的圖片加載,好用到令人髮指。使用一句話:

Picasso.with(this).load("url").placeholder(R.mipmap.ic_default).into(imageView);

Picasso不僅實現了圖片異步加載的功能,還解決了Android中加載圖片時需要解決的一些常見問題:

  1. 在adapter中需要取消已經不在視野範圍的ImageView圖片資源的加載,否則會導致圖片錯位,Picasso已經解決了這個問題。
  2. 使用複雜的圖片壓縮轉換來儘可能的減少內存消耗
  3. 自帶內存和硬盤二級緩存功能

Picasso庫的引入:

  1. 在線搜索picasso庫,加入到gradle再同步即可引入picasso庫
  2. 加入源碼,加入後出現錯誤,把測試相關的內容刪除,刪除OKHttpDownloader,也可以導入okhttp包,源碼中設置圖片下載器的代碼如下:
static Downloader createDefaultDownloader(Context context) {
  if (SDK_INT >= GINGERBREAD) {
      try {
        Class.forName("com.squareup.okhttp.OkHttpClient");
        return OkHttpLoaderCreator.create(context);
      } catch (ClassNotFoundException ignored) {
      }
  }
  return new UrlConnectionDownloader(context);
}

如果沒有okhttp,把if語句註釋,直接用默認的UrlConnectionDownloader進行圖片下載

原理簡要分析:

1、Picasso.with(Context):入手

public static Picasso with(Contextcontext){
      if(singleton==null){
              synchronized(Picasso.class){
                 if(singleton==null){
                    singleton=newBuilder(context).build();
                 }
              }
      }
    return singleton;
}

單列模式,保證多線程情況下,也只有一個實例。

/** Create the {@link Picasso} instance. 創建Picasso的實例 */
public Picassobuild(){
    Context context=this.context;
    if(downloader==null){
        downloader=Utils.createDefaultDownloader(context);
    }
    if(cache==null){
        cache=new LruCache(context);
    }
    if(service==null){
        service=new PicassoExecutorService();
    }
    if(transformer==null){
        transformer=RequestTransformer.IDENTITY;
    }
    Stats stats=newStats(cache);
    Dispatcher dispatcher=new Dispatcher(context,service,HANDLER,downloader,cache,stats);
    return new Picasso(context,dispatcher,cache,listener,transformer,
        requestHandlers,stats,indicatorsEnabled,loggingEnabled);
}

默認初始化了以下的參數:

Downloader
DownLoader就是下載用的工具類,在Picasso當中,如果OKHttp可以使用的話,就會默認使用OKHttp,如果無法使用的話,就會使用UrlConnectionDownloader(默認使用HttpURLConnection實現)。

Cache
默認實現爲LruCache,就是使用LinkedHashMap實現的一個Cache類,注意的一個地方就是,在其他的地方,我們一般默認的是限制的capacity,但是這個地方我們是限制的總共使用的內存空間。因此LruCache在實現的時候,其實簡單理解就是將LinkedHashMap封裝,然後基於LinkedHashMap的方法實現Cache的方法,在Cache的set()方法的時候,會不斷計算當前還可以使用的空間大小,要是超出範圍,則刪除之前保存的數據。

ExecutorService
默認的實現爲PicassoExecutorService,該類也比較簡單,其實就是ThreadPoolExecutor,在其功能的基礎上繼續封裝,在其中有一個比較細心的功能就是,Picasso通過PicassoExecutorService設置線程數量,來調整在2G/3G/4G/WiFi不同網絡情況下的不同表現。

RequestTransformer
ReqeustTransformer是一個接口,用來預處理Reqeust,可以用來將請求進行預先處理,比如改個域名啥的。

Stats
主要是一些統計信息,比如cache hit/miss,總共下載的文件大小,下載過的圖片數量,轉換的圖片數量等等。

Dispatcher
Picasso當中,分發任務的線程,這是我們以後要重點研究的一個類,先標記一下,這個Dispatcher主要做了以下的事情:

啓動了一個DispatcherThread線程初始化了一個用來處理消息的DispatcherHandler,注意,根據Dispatcher中默認配置,該Handler所有數據的處理是在DispatcherThread之上。初始化並註冊了一個網絡狀態廣播接收器。

2、圖片加載流程:
1.初始化Picasso,實例化其唯一的對象。
2.根據傳入的Url、File、resource Id,構建ReqeustCreator對象
3.根據ReqeustCreator構建Request對象,同時根據Reqeust屬性,嘗試從Cache中訪問數據
4.Cache Hit,則通過回調,設置Target或者ImageView,完成該Reqeust
5.如果Cache Miss,那麼則構建相應的Action,並提交到DispatcherThread當中。
6.Dispatcher中的Handler接收到相應的Message,調用dispatcher.performSubmit(action)進行處理。
7.創建BitmapHunter對象,並提交到PicassoExecutorService線程池
8.再次檢查Memory Cache中已經有緩存,如果Hit,則讀取緩存中的Bitmap
9.如果Cache miss,則交給Action對應的ReqeustHandler進行處理,比如網絡請求,或者從File讀取圖片
10.返回結果之後,通知Dispatcher中的Handler處理結果。
11.DispatcherThread中將BitmapHunter的結果打包(batch),最快200ms打包一次。通知主線程HANDLER進行處理
12.主線程HANDLER接收打包的BitmapHunter,對最後的結果進行分發。

基本使用:

加載本地圖片,並做壓縮和旋轉:

//加載本地圖片
Picasso.with(this).load("file:///sdcard/Download/ddd.jpg").resize(100,100).rotate(180,0,0).into(mivPic);

加載網絡圖片,並設置不使用內存緩存中查找也不存儲內存緩存,對於本地緩存,如果用okhttp,可以設這兩者,如果是默認的downloader,只能設置NO_CACHE:

//加載網絡圖片
Picasso.with(this).
        load(mstrNetUrl).
        //networkPolicy(NetworkPolicy.NO_CACHE,NetworkPolicy.NO_STORE).
        memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE).
        into(mivPic);

加載資源圖片,並自定義轉換器:

public void getResPic(View view) {
    //加載資源圖片
    Picasso.with(this).load(R.drawable.aaa).transform(new HalfTransformation()).into(mivPic);
}

自定義轉換器對應的類

public class HalfTransformation implements Transformation{

    @Override
    public Bitmap transform(Bitmap source) {
        Matrix matrix = new Matrix();
        matrix.postScale(0.2f,0.2f);

        Bitmap newBitmap = Bitmap.createBitmap(source,0,0,source.getWidth(),source.getHeight(),matrix,true);
        source.recycle();//一定要回收原圖
        return newBitmap;
    }

    @Override
    public String key() {
        return "HalfTransformation";
    }
}

設置佔位圖片:

Picasso.with(context)
    .load(url)
    .placeholder(R.drawable.user_placeholder)
    .error(R.drawable.user_placeholder_error)
.into(imageView);

如果加載發生錯誤會重複三次請求,三次都失敗纔會顯示erro Place holder

注意:和Square的網絡庫一起能發揮最大作用,Picasso框架沒有實現磁盤緩存,配合OkHttp進行實現。因爲Picasso可以選擇將網絡請求的緩存部分交給了okhttp實現。

三、Glide

Glide 是 Google 一位員工的大作,他完全是基於 Picasso 的,沿襲了 Picasso 的簡潔風格,但是在此做了大量優化與改進。

Glide 默認的 Bitmap 格式是 RGB_565 格式,而 Picasso 默認的是 ARGB_8888 格式,這個內存開銷要小一半。

在磁盤緩存方面,Picasso 只會緩存原始尺寸的圖片,而 Glide 緩存的是多種規格,也就意味着 Glide 會根據你 ImageView 的大小來緩存相應大小的圖片尺寸,比如你 ImageView 大小是200*200,原圖是 400*400 ,而使用 Glide 就會緩存 200*200 規格的圖,而 Picasso 只會緩存 400*400 規格的。這個改進就會導致 Glide 比 Picasso 加載的速度要快,畢竟少了每次裁剪重新渲染的過程。

除此之外,還有很多其他配置選項的增加。

glide的用法跟picasso類似,不同點:

  1. 默認情況下,picasso加載整個圖片到內存然後根據imageview大小調整,glide直接根據imageview大小加載調整後的圖片,所以picasso圖片質量好,內存消耗大,加載速度相對慢,glide圖片質量相對差,內存消耗小,加載速度快
  2. picasso緩存整個圖片,只要是同一張圖片,都會直接從緩存讀取,glide根據imageview大小緩存不同大小圖片,即使是同一個圖片,顯示的imageview大小不同,也會從新緩存
  3. 最重要的一個特性是 Glide 支持加載 Gif 動態圖,而 Picasso 不支持該特性

總體來說,Glide 是在 Picasso 基礎之上進行的二次開發,各個方面做了不少改進,不過這也導致他的包比 Picasso 大不少,不過也就不到 500k,Picasso 是100多k,方法數也比 Picasso 多不少,不過畢竟級別還是蠻小的,影響不是很大。

四、Fresco

Fresco 是 Facebook 出品,他是新一代的圖片加載庫,最大的優勢在於5.0以下(最低2.3)的bitmap加載。我們知道 Android 應用程序可用的內存有限,經常會因爲圖片加載導致 OOM,雖然我們有各種手段去優化,儘量減少出現 OOM 的可能性,但是永遠沒法避免,尤其某些低端手機 OOM 更是嚴重。而 Facebook 就另闢蹊徑,既然沒法在 Java 層處理,我們就在更底層的 Native 堆做手腳。於是 Fresco 將圖片放到一個特別的內存區域叫 Ashmem 區,就是屬於 Native 堆,圖片將不再佔用 App 的內存,在圖片不顯示的時候,佔用的內存會自動被釋放,Java 層對此無能爲力,這裏是屬於 C++ 的地盤,這會使得APP更加流暢,減少因圖片內存佔用而引發的OOM。爲什麼說是5.0以下,因爲在5.0以後系統默認就是存儲在Ashmem區了。

四個庫都使用了一遍,對比到Fresco確實強大,加載大圖Fresco最屌,有的圖Glide和Picasso加載不出來,換上Fresco妥妥的,不過Fresco比較龐大,推薦在主要都是圖片的app中使用,一般的app使用Glide和Picasso就夠了!Picasso所能實現的功能,Glide都能做,無非是所需的設置不同。但是Picasso體積比起Glide小太多如果項目中網絡請求本身用的就是okhttp或者retrofit(本質還是okhttp),那麼建議用Picasso,體積會小很多(Square全家桶的幹活)。Glide的好處是大型的圖片流,比如gif、Video,如果你們是做美拍、愛拍這種視頻類應用,建議使用。Fresco在5.0以下的內存優化非常好,代價就是體積也非常的大,按體積算Fresco>Glide>Picasso,不過在使用起來也有些不便(小建議:他只能用內置的一個ImageView來實現這些功能,用起來比較麻煩,我們通常是根據Fresco自己改改,直接使用他的Bitmap層)

參考
1、那些我們用過的Android開源圖片加載框架——文/水跡(簡書作者)
2、picasso和glide
3、Android 三大圖片加載框架比較
4、深入對比Glide 和 Picasso——好文推薦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章