前言
最近項(xiàng)目在做性能優(yōu)化,之前項(xiàng)目的圖片加載用的是 Android-Universal-Image-Loader,相信大家對(duì)于這個(gè)老牌的圖片加載框架應(yīng)該都很熟悉。但由于該庫的作者已經(jīng)很久沒維護(hù)了, 而且 Google 又力推自己?jiǎn)T工出品的 glide,在比較了各大圖片加載框架的性能之后,決定用 glide 作為新的圖片加載框架。
為什么我要進(jìn)行封裝
結(jié)合 glide 特性的一些思考
封裝通用的 CommonImageLoader
為什么我要進(jìn)行封裝
對(duì)于開源項(xiàng)目,有些庫的 API 確實(shí)設(shè)計(jì)的相當(dāng)棒,拿 glide 舉個(gè)例子:
GlideApp.with(context).load(imageUrl).into(imageView);
很多時(shí)候只要簡(jiǎn)單的調(diào)用一行代碼實(shí)現(xiàn)圖片的高性能加載(對(duì)于 glide 這個(gè)框架不熟悉的同學(xué),可以看一下我這篇文章 glide 一個(gè)強(qiáng)大的圖片加載框架 ),使用起來是不是特別的簡(jiǎn)單?可能你會(huì)有疑問,都已經(jīng)封裝得這么好了, 有必要再封裝一層么?那你錯(cuò)了,哪怕他已經(jīng)很完美了,我都會(huì)進(jìn)行相應(yīng)的封裝。
現(xiàn)在技術(shù)發(fā)展的這么快,如果不進(jìn)行封裝,隨著業(yè)務(wù)的需求,如果發(fā)現(xiàn) glide 這個(gè)庫已經(jīng)滿足不了我們的需求,而需要換成另外的圖片加載庫的話,那估計(jì)得跪。要把所有調(diào)用 glide 的地方全部都修改一遍,累都累死你,但是如果我們封裝了一層的話,三天兩頭改一次都沒多大關(guān)系。
結(jié)合 glide 特性的一些思考
既然要對(duì) glide 進(jìn)行相應(yīng)的封裝,那我們首先就必須要對(duì)于這個(gè)圖片加載庫有著足夠充分的了解,學(xué)習(xí)一個(gè)熱門的框架,我覺得最好的方式就是直接閱讀官網(wǎng)的文檔或者
Github 上面的 wiki,寫得再好的博客,難免會(huì)有所疏漏。而且,隨著時(shí)間的流逝,這些框架大都會(huì)進(jìn)行一些新特性的添加,以及性能的優(yōu)化。只有文檔才能體現(xiàn)該框架最新的特性,如果想看 glide 的文檔的話,可以點(diǎn)擊 這里
為了更好的封裝 glide 這個(gè)圖片加載庫,我也花了相當(dāng)多的心思,除了把 glide 官網(wǎng)上的 wiki 看完之外,還看了很多有關(guān) glide 寫得很不錯(cuò)的博客,以及封裝第三方庫有關(guān)的一些文章,最后才算對(duì) glide 這個(gè)庫有了更好的把握。
我們要使用一個(gè)框架,必然是因?yàn)樗兄恍┓浅:玫奶匦裕晕覀冊(cè)诜庋b的時(shí)候就必須盡量的保留它的這些特性,不然我們的封裝就沒有意義了,glide 比較好的特性主要有這幾點(diǎn)
有著非常簡(jiǎn)潔的 API
處理圖片時(shí)能保持一個(gè)低的內(nèi)存消耗
能夠根據(jù) Activity 或 Fragment 的生命周期,對(duì)圖片就行相應(yīng)的處理和回收
我們這次的封裝的難點(diǎn)就在于第三點(diǎn),如果只是在 Activity 或 Fragment 中加載的話,那封裝很簡(jiǎn)單啊,直接
public static void displayImage(Context context, String imageUrl, ImageView imageView){
GlideApp.with(context)
.load(imageUrl)
.into(imageView);
}
但是如果你在 Adapter 或者 PopupWindow 這些無法直接獲取到 Activity 或 Fragment 的類中想進(jìn)行圖片加載的話,那就不行咯??赡苣銜?huì)說我直接將 Activity 或 Fragment 作為參數(shù)直接傳進(jìn) Adapter 或 PopupWindow 中不就行了。但這樣也未免有點(diǎn)太不優(yōu)雅了,作為一個(gè)有追求的程序員,怎么能這么懶呢。
上一節(jié)中我們談到了,封裝 glide 最大的難點(diǎn),那我們現(xiàn)在就試著解決這個(gè)問題。既然 Adapter 和 PopupWindow 無法直接拿到 Activity 或 Fragment,那我們能不能換種方式來實(shí)現(xiàn)呢?當(dāng)然是可以的。
我們先來看一下封裝后的 CommonImageLoader 中的架構(gòu)
public class CommonImageLoader {
private static LinkedList<Keeper> mKeepers;
// 創(chuàng)建新的keeper
public void addGlideRequests(@NonNull Fragment fragment) {
// 避免重復(fù)創(chuàng)建
for (Keeper keeper : mKeepers) {
if (keeper.key == fragment.hashCode()) {
return;
}
}
Keeper keeper = new Keeper(fragment);
mKeepers.add(keeper);
}
// 創(chuàng)建新的Keeper
public void addGlideRequests(@NonNull Activity activity) {
// 避免重復(fù)創(chuàng)建
for (Keeper keeper : mKeepers) {
if (keeper.key == activity.hashCode()) {
return;
}
}
Keeper keeper = new Keeper(activity);
mKeepers.add(keeper);
}
//hashCode 為 iHashCode 的對(duì)象需要使用圖像加載功能
public void iNeedLoadImageFunction(@NonNull Fragment fragment, int iHashCode) {
// 查找到相應(yīng)的Keeper,存儲(chǔ)對(duì)象的hashCode
for (Keeper keeper : mKeepers) {
if (keeper.key == fragment.hashCode()) {
keeper.values.add(iHashCode);
}
}
// 錯(cuò)誤拋出,說明fragment沒有創(chuàng)建對(duì)應(yīng)Keeper
throw new IllegalArgumentException();
}
// hashCode 為 iHashCode 的對(duì)象需要使用圖像加載功能
public void iNeedLoadImageFunction(@NonNull Activity activity, int iHashCode) {
for (Keeper keeper : mKeepers) {
if (keeper.key == activity.hashCode()) {
keeper.values.add(iHashCode);
}
}
// 錯(cuò)誤拋出,說明activity沒有創(chuàng)建對(duì)應(yīng)Keeper
throw new IllegalArgumentException();
}
private GlideRequests getGlideRequests(int hashCode) {
for (Keeper keeper : mKeepers) {
if (keeper.values.contains(hashCode)) {
return keeper.glideRequests;
}
}
return GlideApp.with(MyApplication.getInstance());
}
public void displayImage(int hashCode, String uri, ImageView imageView) {
getGlideRequests(hashCode)
.load(uri)
.error(R.mipmap.ic_launcher)
.placeholder(R.mipmap.ic_launcher)
.dontAnimate()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
}
private static class Keeper {
int key;
GlideRequests glideRequests;
Set<Integer> values;
public Keeper(@NonNull Activity activity) {
key = activity.hashCode();
values = new HashSet<>();
values.add(activity.hashCode());
glideRequests = GlideApp.with(activity);
}
public Keeper(@NonNull Fragment fragment) {
key = fragment.hashCode();
values = new HashSet<>();
values.add(fragment.hashCode());
glideRequests = GlideApp.with(fragment);
}
}
可以看到我們首先創(chuàng)建了一個(gè) Keeper 的靜態(tài)內(nèi)部類,這個(gè)類的作用就是為了解決在 Adapter 或 PupupWindow 這些無法直接獲取到 Activity 或 Fragment 的問題。
Keeper 中有兩個(gè)參數(shù)分別 Activity 和 Fragment 的構(gòu)造函數(shù),將其構(gòu)建成一個(gè) GlideRequests,GlideRequest 其實(shí)就是通過 GlideApp.with() 得到的,所以我們只要得到 GlideRequests,再調(diào)用 .load(imageUrl).into(imageView) 就能進(jìn)行圖片的加載了,同時(shí)在 Keeper 中用一個(gè) HashSet 保存了 Activity 或 Fragment 的 hashCode。
在 CommonImageLoader 中有一個(gè) private static LinkedList<Keeper> mKeepers; 用于保存 Keeper。
可以看到 CommonImageLoader 中有一個(gè) public void addGlideRequests() 的方法,這個(gè)方法有兩種重載,分別為 public void addGlideRequests(Activity activity) 和 public void addGlideRequests(Fragment fragment) 是用來將 Activity 或 Fragment 的 hashCode 以及對(duì)應(yīng)的 GlideRequests 保存在 Keeper 中。
為了統(tǒng)一方法調(diào)用,我們直接將 GlideApp.with(context) 全部改成 GlideRequests,這樣的話,我們要進(jìn)行圖片加載時(shí)只要在 Activity 或 Fragmet 的 onCreate() 中調(diào)用 addGlideRequest,然后在 CommonImageLoader 中我們便可以根據(jù) getGlideRequests() 獲取到相應(yīng)的 GlideRequests,以便于后續(xù)的處理。
至于如何處理在 Adapter 或 PopupWindow 中進(jìn)行圖片加載,可以看到在 CommonImageLoader 中有 iNeedLoadImageFuction(@NonNull Fragment fragment, int iHashCode) 以及 iNeedLoadImageFuction(@NonNull Activity activity, int iHashCode) 的重載方法,我們只要在 Activity 或 Fragment 中,將 Fragment 以及 Adapter 的 hashCode 傳進(jìn)去
Adapter adapter = new Adapter();
CommonImageLoader.getInstance().iNeedLoadImageFuction(fragment, adapter.hashCode())
然后在 Adapter 中就能根據(jù) Adapter 的 hashCode 獲取到 GlideRequests,然后進(jìn)行圖片的加載了,當(dāng)然這些操作已經(jīng)封裝在 CommonImageLoader 里面了, 我們直接調(diào)用就好了。
以上便是本文的全部?jī)?nèi)容,全部的代碼我已經(jīng)放上 Github 了,有興趣的點(diǎn)擊這里 https://github.com/developerHaoz/GlideUtils,如果對(duì)你有幫助的話,就賞個(gè) star 吧。
與之相關(guān)
關(guān)鍵詞:code小生
聯(lián)系客服