1. 程式人生 > >Android圖片載入框架最全解析(一),Glide的基本用法

Android圖片載入框架最全解析(一),Glide的基本用法

現在Android上的圖片載入框架非常成熟,從最早的老牌圖片載入框架UniversalImageLoader,到後來Google推出的Volley,再到後來的新興軍Glide和Picasso,當然還有Facebook的Fresco。每一個都非常穩定,功能也都十分強大。但是它們的使用場景基本都是重合的,也就是說我們基本只需要選擇其中一個來進行學習和使用就足夠了,每一個框架都嘗試去掌握的話則有些浪費時間。

在這幾個框架當中,我對Volley和Glide研究得比較深入,對UniversalImageLoader、Picasso和Fresco都只是有一些基本的瞭解。從易用性上來講,Glide和Picasso應該都是完勝其他框架的,這兩個框架都實在是太簡單好用了,大多數情況下載入圖片都是一行程式碼就能解決的,而UniversalImageLoader和Fresco則在這方面略遜一些。

那麼再拿Glide和Picasso對比呢,首先這兩個框架的用法非常相似,但其實它們各有特色。Picasso比Glide更加簡潔和輕量,Glide比Picasso功能更為豐富。之前已經有人對這兩個框架進行過全方面的對比,大家如果想了解更多的話可以去參考一下 這篇文章 。

總之,沒有最好的框架,只有最適合自己的框架。經過多方面對比之後,我還是決定選擇了Glide來進行研究,並且這也是Google官方推薦的圖片載入框架。

說實話,關於Glide的文章我已經籌備了好久,去年這個時候本來就打算要寫了,但是一直都沒有動筆。因為去年我的大部分時間都放在了寫《第二行程式碼》上面,只能用碎片時間來寫寫部落格,但是Glide的難度遠超出了我用碎片時間所能掌握的難度。當然,這裡我說的是對它的原始碼進行解析的難度,不是使用上的難度,Glide的用法是很簡單的。所以,我覺得去年我寫不好Glide這個題材的文章,也就一直拖到了今年。

而現在,我花費了大量的精力去研究Glide的原始碼和各種用法,相信現在已經可以將它非常好地掌握了,因此我準備將我掌握的這些知識整理成一個新的系列,幫忙大家更好地學習Glide。這個Glide系列大概會有8篇左右文章,預計花半年時間寫完,將會包括Glide的基本用法、原始碼解析、高階用法、功能擴充套件等內容,可能會是目前網際網路上最詳盡的Glide教程。

那麼本篇文章是這個系列的第一篇文章,我們先來了解一下Glide的基本用法吧。

開始

Glide是一款由Bump Technologies開發的圖片載入框架,使得我們可以在Android平臺上以極度簡單的方式載入和展示圖片。

目前,Glide最新的穩定版本是3.7.0,雖然4.0已經推出RC版了,但是暫時問題還比較多。因此,我們這個系列的部落格都會使用Glide 3.7.0版本來進行講解,這個版本的Glide相當成熟和穩定。

要想使用Glide,首先需要將這個庫引入到我們的專案當中。新建一個GlideTest專案,然後在app/build.gradle檔案當中新增如下依賴:

dependencies {
    compile 'com.github.bumptech.glide:glide:3.7.0'
}

如果你還在使用Eclipse,可以點選 這裡 下載Glide的jar包。

另外,Glide中需要用到網路功能,因此你還得在AndroidManifest.xml中宣告一下網路許可權才行:

<uses-permission android:name="android.permission.INTERNET" />
  • 1

就是這麼簡單,然後我們就可以自由地使用Glide中的任意功能了。

載入圖片

現在我們就來嘗試一下如何使用Glide來載入圖片吧。比如這是必應上一張首頁美圖的地址:

http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg

然後我們想要在程式當中去載入這張圖片。

那麼首先開啟專案的佈局檔案,在佈局當中加入一個Button和一個ImageView,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Load Image"
        android:onClick="loadImage"
        />

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

為了讓使用者點選Button的時候能夠將剛才的圖片顯示在ImageView上,我們需要修改MainActivity中的程式碼,如下所示:

public class MainActivity extends AppCompatActivity {

    ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.image_view);
    }

    public void loadImage(View view) {
        String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
        Glide.with(this).load(url).into(imageView);
    }

}

沒錯,就是這麼簡單。現在我們來執行一下程式,效果如下圖所示:

可以看到,一張網路上的圖片已經被成功下載,並且展示到ImageView上了。

而我們到底做了什麼?實際上核心的程式碼就只有這一行而已:

Glide.with(this).load(url).into(imageView);

千萬不要小看這一行程式碼,實際上僅僅就這一行程式碼,你已經可以做非常非常多的事情了,包括載入網路上的圖片、載入手機本地的圖片、載入應用資源中的圖片等等。

下面我們就來詳細解析一下這行程式碼。

首先,呼叫Glide.with()方法用於建立一個載入圖片的例項。with()方法可以接收Context、Activity或者Fragment型別的引數。也就是說我們選擇的範圍非常廣,不管是在Activity還是Fragment中呼叫with()方法,都可以直接傳this。那如果呼叫的地方既不在Activity中也不在Fragment中呢?也沒關係,我們可以獲取當前應用程式的ApplicationContext,傳入到with()方法當中。注意with()方法中傳入的例項會決定Glide載入圖片的生命週期,如果傳入的是Activity或者Fragment的例項,那麼當這個Activity或Fragment被銷燬的時候,圖片載入也會停止。如果傳入的是ApplicationContext,那麼只有當應用程式被殺掉的時候,圖片載入才會停止。

接下來看一下load()方法,這個方法用於指定待載入的圖片資源。Glide支援載入各種各樣的圖片資源,包括網路圖片、本地圖片、應用資源、二進位制流、Uri物件等等。因此load()方法也有很多個方法過載,除了我們剛才使用的載入一個字串網址之外,你還可以這樣使用load()方法:

// 載入本地圖片
File file = new File(getExternalCacheDir() + "/image.jpg");
Glide.with(this).load(file).into(imageView);

// 載入應用資源
int resource = R.drawable.image;
Glide.with(this).load(resource).into(imageView);

// 載入二進位制流
byte[] image = getImageBytes();
Glide.with(this).load(image).into(imageView);

// 載入Uri物件
Uri imageUri = getImageUri();
Glide.with(this).load(imageUri).into(imageView);

最後看一下into()方法,這個方法就很簡單了,我們希望讓圖片顯示在哪個ImageView上,把這個ImageView的例項傳進去就可以了。當然,into()方法不僅僅是隻能接收ImageView型別的引數,還支援很多更豐富的用法,不過那個屬於高階技巧,我們會在後面的文章當中學習。

那麼回顧一下Glide最基本的使用方式,其實就是關鍵的三步走:先with(),再load(),最後into()。熟記這三步,你就已經入門Glide了。

佔位圖

現在我們來學一些Glide的擴充套件內容。其實剛才所學的三步走就是Glide最核心的東西,而我們後面所要學習的所有東西都是在這個三步走的基礎上不斷進行擴充套件而已。

觀察剛才載入網路圖片的效果,你會發現,點選了Load Image按鈕之後,要稍微等一會圖片才會顯示出來。這其實很容易理解,因為從網路上下載圖片本來就是需要時間的。那麼我們有沒有辦法再優化一下使用者體驗呢?當然可以,Glide提供了各種各樣非常豐富的API支援,其中就包括了佔位圖功能。

顧名思義,佔位圖就是指在圖片的載入過程中,我們先顯示一張臨時的圖片,等圖片加載出來了再替換成要載入的圖片。

下面我們就來學習一下Glide佔位圖功能的使用方法,首先我事先準備好了一張loading.jpg圖片,用來作為佔位圖顯示。然後修改Glide載入部分的程式碼,如下所示:

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .into(imageView);

沒錯,就是這麼簡單。我們只是在剛才的三步走之間插入了一個placeholder()方法,然後將佔點陣圖片的資源id傳入到這個方法中即可。另外,這個佔位圖的用法其實也演示了Glide當中絕大多數API的用法,其實就是在load()和into()方法之間串接任意想新增的功能就可以了。

不過如果你現在重新執行一下程式碼並點選Load Image,很可能是根本看不到佔位圖效果的。因為Glide有非常強大的快取機制,我們剛才載入那張必應美圖的時候Glide自動就已經將它快取下來了,下次載入的時候將會直接從快取中讀取,不會再去網路下載了,因而載入的速度非常快,所以佔位圖可能根本來不及顯示。

因此這裡我們還需要稍微做一點修改,來讓佔位圖能有機會顯示出來,修改程式碼如下所示:

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

可以看到,這裡串接了一個diskCacheStrategy()方法,並傳入DiskCacheStrategy.NONE引數,這樣就可以禁用掉Glide的快取功能。

關於Glide快取方面的內容我們將會在後面的文章進行詳細的講解,這裡只是為了測試佔位圖功能而加的一個額外配置,暫時你只需要知道禁用快取必須這麼寫就可以了。

現在重新執行一下程式碼,效果如下圖所示:

可以看到,當點選Load Image按鈕之後會立即顯示一張佔位圖,然後等真正的圖片載入完成之後會將佔點陣圖替換掉。

當然,這只是佔位圖的一種,除了這種載入佔位圖之外,還有一種異常佔位圖。異常佔位圖就是指,如果因為某些異常情況導致圖片載入失敗,比如說手機網路訊號不好,這個時候就顯示這張異常佔位圖。

異常佔位圖的用法相信你已經可以猜到了,首先準備一張error.jpg圖片,然後修改Glide載入部分的程式碼,如下所示:

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

很簡單,這裡又串接了一個error()方法就可以指定異常佔位圖了。

現在你可以將圖片的url地址修改成一個不存在的圖片地址,或者乾脆直接將手機的網路給關了,然後重新執行程式,效果如下圖所示:

這樣我們就把Glide提供的佔位圖功能都掌握了。

指定圖片格式

我們還需要再瞭解一下Glide另外一個強大的功能,那就是Glide是支援載入GIF圖片的。這一點確實非常牛逼,因為相比之下Jake Warton曾經明確表示過,Picasso是不會支援載入GIF圖片的。

而使用Glide載入GIF圖並不需要編寫什麼額外的程式碼,Glide內部會自動判斷圖片格式。比如這是一張GIF圖片的URL地址:

http://p1.pstatp.com/large/166200019850062839d3
  • 1

我們只需要將剛才那段載入圖片程式碼中的URL地址替換成上面的地址就可以了,現在重新執行一下程式碼,效果如下圖所示:

也就是說,不管我們傳入的是一張普通圖片,還是一張GIF圖片,Glide都會自動進行判斷,並且可以正確地把它解析並展示出來。

但是如果我想指定圖片的格式該怎麼辦呢?就比如說,我希望載入的這張圖必須是一張靜態圖片,我不需要Glide自動幫我判斷它到底是靜圖還是GIF圖。

想實現這個功能仍然非常簡單,我們只需要再串接一個新的方法就可以了,如下所示:

Glide.with(this)
     .load(url)
     .asBitmap()
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

可以看到,這裡在load()方法的後面加入了一個asBitmap()方法,這個方法的意思就是說這裡只允許載入靜態圖片,不需要Glide去幫我們自動進行圖片格式的判斷了。

現在重新執行一下程式,效果如下圖所示:

由於呼叫了asBitmap()方法,現在GIF圖就無法正常播放了,而是會在介面上顯示第一幀的圖片。

那麼類似地,既然我們能強制指定載入靜態圖片,就也能強制指定載入動態圖片。比如說我們想要實現必須載入動態圖片的功能,就可以這樣寫:

Glide.with(this)
     .load(url)
     .asGif()
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

這裡呼叫了asGif()方法替代了asBitmap()方法,很好理解,相信不用我多做什麼解釋了。

那麼既然指定了只允許載入動態圖片,如果我們傳入了一張靜態圖片的URL地址又會怎麼樣呢?試一下就知道了,將圖片的URL地址改成剛才的必應美圖,然後重新執行程式碼,效果如下圖所示。

沒錯,如果指定了只能載入動態圖片,而傳入的圖片卻是一張靜圖的話,那麼結果自然就只有載入失敗嘍。

指定圖片大小

實際上,使用Glide在絕大多數情況下我們都是不需要指定圖片大小的。

在學習本節內容之前,你可能還需要先了解一個概念,就是我們平時在載入圖片的時候很容易會造成記憶體浪費。什麼叫記憶體浪費呢?比如說一張圖片的尺寸是1000*1000畫素,但是我們介面上的ImageView可能只有200*200畫素,這個時候如果你不對圖片進行任何壓縮就直接讀取到記憶體中,這就屬於記憶體浪費了,因為程式中根本就用不到這麼高畫素的圖片。

關於圖片壓縮這方面,我之前也翻譯過Android官方的一篇文章,感興趣的朋友可以去閱讀一下 Android高效載入大圖、多圖解決方案,有效避免程式OOM 。

而使用Glide,我們就完全不用擔心圖片記憶體浪費,甚至是記憶體溢位的問題。因為Glide從來都不會直接將圖片的完整尺寸全部載入到記憶體中,而是用多少載入多少。Glide會自動判斷ImageView的大小,然後只將這麼大的圖片畫素載入到記憶體當中,幫助我們節省記憶體開支。

當然,Glide也並沒有使用什麼神奇的魔法,它內部的實現原理其實就是上面那篇文章當中介紹的技術,因此掌握了最基本的實現原理,你也可以自己實現一套這樣的圖片壓縮機制。

也正是因為Glide是如此的智慧,所以剛才在開始的時候我就說了,在絕大多數情況下我們都是不需要指定圖片大小的,因為Glide會自動根據ImageView的大小來決定圖片的大小。

不過,如果你真的有這樣的需求,必須給圖片指定一個固定的大小,Glide仍然是支援這個功能的。修改Glide載入部分的程式碼,如下所示:

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .override(100, 100)
     .into(imageView);
  • 7

仍然非常簡單,這裡使用override()方法指定了一個圖片的尺寸,也就是說,Glide現在只會將圖片載入成100*100畫素的尺寸,而不會管你的ImageView的大小是多少了。