[轉(zhuǎn)]教你寫Android ImageLoader框架之圖片加載與加載策略(三)

本文轉(zhuǎn)自Mr.Simple的博客,如侵刪

前言

教你寫Android ImageLoader框架之初始配置與請(qǐng)求調(diào)度中,我們已經(jīng)講述了ImageLoader的請(qǐng)求配置與調(diào)度相關(guān)的設(shè)計(jì)與實(shí)現(xiàn)。今天我們就來(lái)深入了解圖片的具體加載過(guò)程以及加載的策略(包括按順序加載和逆序加載) ,在這其中我會(huì)分享我的一些設(shè)計(jì)決策,也歡迎大家給我提建議剧防。


圖片的加載

Loader與LoaderManager的實(shí)現(xiàn)

在上一篇文章教你寫Android ImageLoader框架之初始配置與請(qǐng)求調(diào)度中京革,我們聊到了Loader與LoaderManager奇唤。 ImageLoader不斷地從隊(duì)列中獲取請(qǐng)求,然后解析到圖片uri的schema匹摇,從schema的格式就可以知道它是存儲(chǔ)在哪里的圖片咬扇。例如網(wǎng)絡(luò)圖片對(duì)象的schema是http或者h(yuǎn)ttps,sd卡存儲(chǔ)的圖片對(duì)應(yīng)的schema為file廊勃,schemae與Loader有一個(gè)對(duì)應(yīng)關(guān)系懈贺。根據(jù)schema我們從LoaderManager中獲取對(duì)應(yīng)的Loader來(lái)加載圖片。這個(gè)設(shè)計(jì)保證了SimpleImageLoader可加載圖片類型的可擴(kuò)展性坡垫,這就是為什么會(huì)增加loader這個(gè)包的原因梭灿。用戶只需要根據(jù)uri的格式來(lái)構(gòu)造圖片uri,并且實(shí)現(xiàn)自己的Loader類冰悠,然后將Loader對(duì)象注入到LoaderManager即可堡妒。RequestDispatcher中的run函數(shù)如下 :

@Override
   public void run() {
       try {
           while (!this.isInterrupted()) {
               final BitmapRequest request = mRequestQueue.take();
               if (request.isCancel) {
                   continue;
               }

               final String schema = parseSchema(request.imageUri);
               // 根據(jù)schema獲取loader
               Loader imageLoader = LoaderManager.getInstance().getLoader(schema);
               imageLoader.loadImage(request);
           }
       } catch (InterruptedException e) {
           Log.i("", "### 請(qǐng)求分發(fā)器退出");
       }
   }

Loader只定義了一個(gè)接口,只用一個(gè)加載圖片的方法溉卓。

public interface Loader {
     public void loadImage(BitmapRequest result);
}

抽象是為了可擴(kuò)展皮迟,定義這個(gè)接口,我們就可以注入自己的圖片加載實(shí)現(xiàn)類桑寨。例如從資源伏尼、assets中加載。不管從網(wǎng)絡(luò)還是本地加載圖片尉尾,我們加載圖片的過(guò)程有如下幾個(gè)步驟:

1.判斷緩存中是否含有該圖片;
2.如果有則將圖片直接投遞到UI線程爆阶,并且更新UI;
3.如果沒(méi)有緩存,則從對(duì)應(yīng)的地方獲取到圖片扰她,并且將圖片緩存起來(lái)兽掰,然后再將結(jié)果投遞給UI線程,更新UI徒役;

我們可以發(fā)現(xiàn)孽尽,不管從哪里加載圖片,這些邏輯都是通用的忧勿,因此我抽象了一個(gè)AbsLoader類杉女。它將這幾個(gè)過(guò)程抽象起來(lái),只將變化的部分交給子類處理鸳吸,就相當(dāng)于AbsLoader封裝了一個(gè)邏輯框架( 可以思考用了什么設(shè)計(jì)模式)熏挎,大致代碼如下 :

/**
 * @author mrsimple
 */
public abstract class AbsLoader implements Loader {

    /**
     * 圖片緩存
     */
    private static BitmapCache mCache = SimpleImageLoader.getInstance().getConfig().bitmapCache;

    @Override
    public final void loadImage(BitmapRequest request) {
        // 1、從緩存中獲取
        Bitmap resultBitmap = mCache.get(request);
        Log.e("", "### 是否有緩存 : " + resultBitmap + ", uri = " + request.imageUri);
        if (resultBitmap == null) {
            showLoading(request);
            // 2晌砾、沒(méi)有緩存坎拐,調(diào)用onLoaderImage加載圖片
            resultBitmap = onLoadImage(request);
            // 3、緩存圖片
            cacheBitmap(request, resultBitmap);
        } else {
            request.justCacheInMem = true;
        }
        // 4养匈、將結(jié)果投遞到UI線程
        deliveryToUIThread(request, resultBitmap);
    }

    /** 加載圖片的hook方法哼勇,留給子類處理
     * @param request
     * @return
     */
    protected abstract Bitmap onLoadImage(BitmapRequest request);
    // 代碼省略
}

代碼邏輯如上所述實(shí)現(xiàn)了一個(gè)模板函數(shù),變化的部分就是onLoadImage呕乎,子類在這里實(shí)現(xiàn)真正的加載圖片的方法积担。比如從網(wǎng)絡(luò)上加載圖片。

/**
 * @author mrsimple
 */
public class UrlLoader extends AbsLoader {

    @Override
    public Bitmap onLoadImage(BitmapRequest request) {
        final String imageUrl = request.imageUri;
        FileOutputStream fos = null;
        InputStream is = null;
        try {
            URL url = new URL(imageUrl);
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(conn.getInputStream());
            is.mark(is.available());

            final InputStream inputStream = is;
            BitmapDecoder bitmapDecoder = new BitmapDecoder() {

                @Override
                public Bitmap decodeBitmapWithOption(Options options) {
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
                    //
                    if (options.inJustDecodeBounds) {
                        try {
                            inputStream.reset();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // 關(guān)閉流
                        conn.disconnect();
                    }
                    return bitmap;
                }
            };

            return bitmapDecoder.decodeBitmap(request.getImageViewWidth(),
                    request.getImageViewHeight());
        } catch (Exception e) {

        } finally {
            IOUtil.closeQuietly(is);
            IOUtil.closeQuietly(fos);
        }

        return null;
    }

}

在初始化ImageLoader時(shí)我們會(huì)默認(rèn)將幾個(gè)Loader注入到LoaderManager中猬仁,然后在加載圖片時(shí)ImageLoader會(huì)根據(jù)圖片的schema來(lái)獲取對(duì)應(yīng)Loader來(lái)完成加載功能帝璧。

private LoaderManager() {
    register(HTTP, new UrlLoader());
    register(HTTPS, new UrlLoader());
    register(FILE, new LocalLoader());
}

加載策略

加載策略就是你的圖片加載請(qǐng)求提交以后ImageLoader按照一個(gè)什么規(guī)則來(lái)加載你的請(qǐng)求。默認(rèn)就是SerialPolicy策略(FIFO)湿刽,誰(shuí)在隊(duì)列前面就是誰(shuí)優(yōu)先被執(zhí)行的烁。但是事情往往沒(méi)有那么簡(jiǎn)單,我們?cè)贚istView滾動(dòng)時(shí)叭爱,我們希望最后添加到請(qǐng)求隊(duì)列的圖片優(yōu)先得了加載撮躁,因此此時(shí)它們就在手機(jī)屏幕上漱病,所以我們又添加了一個(gè)ReversePolicy策略买雾。咦,對(duì)于這種存在各種可能性的部分杨帽,我們最不能具體化漓穿,還是要抽象!于是我定義了LoadPolicy接口注盈,它的作用是compare兩個(gè)請(qǐng)求晃危,以此來(lái)規(guī)定排序原則。

public interface LoadPolicy {
    public int compare(BitmapRequest request1, BitmapRequest request2);
}

因?yàn)槲覀兊恼?qǐng)求隊(duì)列使用的是優(yōu)先級(jí)隊(duì)列PriorityBlockingQueue,因此我們的BitmapRequest都實(shí)現(xiàn)了 Comparable 接口僚饭,我們?cè)贐itmapRequest的函數(shù)中將compareTo委托給LoadPolicy對(duì)象的compare震叮。

@Override
 public int compareTo(BitmapRequest another) {
     return mLoadPolicy.compare(this, another);
 }

我們看看默認(rèn)的加載策略,即按順序加載鳍鸵,先添加到隊(duì)列的請(qǐng)求先被執(zhí)行苇瓣。

/**
 * 順序加載策略
 * 
 * @author mrsimple
 */
public class SerialPolicy implements LoadPolicy {

    @Override
    public int compare(BitmapRequest request1, BitmapRequest request2) {
        // 那么按照添加到隊(duì)列的序列號(hào)順序來(lái)執(zhí)行
        return request1.serialNum - request2.serialNum;
    }

}

逆序加載則為 :

/**
 * 逆序加載策略,即從最后加入隊(duì)列的請(qǐng)求進(jìn)行加載
 * 
 * @author mrsimple
 */
public class ReversePolicy implements LoadPolicy {

    @Override
    public int compare(BitmapRequest request1, BitmapRequest request2) {
        // 注意Bitmap請(qǐng)求要先執(zhí)行最晚加入隊(duì)列的請(qǐng)求,ImageLoader的策略
        return request2.serialNum - request1.serialNum;
    }
}

呵,想想這不是策略模式么偿乖!原來(lái)模式無(wú)處不在击罪,當(dāng)你習(xí)慣之后你就會(huì)發(fā)現(xiàn)模式在無(wú)形之中已經(jīng)運(yùn)用到你的代碼了。如上所示贪薪,策略都是簡(jiǎn)單的實(shí)現(xiàn)媳禁,這個(gè)策略只需要在配置ImageLoader時(shí)指定就行了,用戶也可以根據(jù)自己的需求來(lái)實(shí)現(xiàn)策略類画切,并且注入給ImageLoader竣稽。這樣就保證了靈活性、可擴(kuò)展性霍弹。


總結(jié)

通過(guò)Loader和LoaderManager保證了可加載圖片來(lái)源的擴(kuò)展性丧枪,即圖片可以存儲(chǔ)在網(wǎng)絡(luò)上、sd卡中庞萍、res文件夾中等等拧烦,實(shí)現(xiàn)一個(gè)從特定位置加載圖片的Loader,然后給這個(gè)Loader注冊(cè)一個(gè)schema,在加載圖片的時(shí)候根據(jù)圖片的路徑獲取schema,再通過(guò)schema獲取Loader,通過(guò)Loader加載圖片钝计。

而圖片的加載策略又通過(guò)LoadPolicy這個(gè)抽象來(lái)定制恋博,用戶可以自行實(shí)現(xiàn)加載策略。這樣就保證了靈活性私恬,當(dāng)然還有后期的圖片緩存也是需要同樣的靈活性债沮。和我在公共技術(shù)點(diǎn)之面向?qū)ο罅笤瓌t所說(shuō),面向?qū)ο蟮膸状笤瓌t最終化為幾個(gè)簡(jiǎn)單的關(guān)鍵字: : 抽象本鸣、單一職責(zé)疫衩、最小化。領(lǐng)悟到了這些思想荣德,我想你的代碼質(zhì)量應(yīng)該會(huì)有一個(gè)質(zhì)的提升闷煤。

ImageLoader庫(kù),圖片緩存肯定必不可少涮瞻。關(guān)于圖片的緩存設(shè)計(jì)鲤拿,還是那句老話,待我下回講解~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末署咽,一起剝皮案震驚了整個(gè)濱河市近顷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖窒升,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缀遍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡饱须,警方通過(guò)查閱死者的電腦和手機(jī)瑟由,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冤寿,“玉大人歹苦,你說(shuō)我怎么就攤上這事《搅” “怎么了殴瘦?”我有些...
    開(kāi)封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)号杠。 經(jīng)常有香客問(wèn)我蚪腋,道長(zhǎng),這世上最難降的妖魔是什么姨蟋? 我笑而不...
    開(kāi)封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任屉凯,我火速辦了婚禮,結(jié)果婚禮上眼溶,老公的妹妹穿的比我還像新娘悠砚。我一直安慰自己,他們只是感情好堂飞,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布灌旧。 她就那樣靜靜地躺著,像睡著了一般绰筛。 火紅的嫁衣襯著肌膚如雪枢泰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天铝噩,我揣著相機(jī)與錄音衡蚂,去河邊找鬼。 笑死骏庸,一個(gè)胖子當(dāng)著我的面吹牛毛甲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敞恋,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丽啡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谋右!你這毒婦竟也來(lái)了硬猫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎啸蜜,沒(méi)想到半個(gè)月后坑雅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衬横,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年裹粤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜂林。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遥诉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出噪叙,到底是詐尸還是另有隱情矮锈,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布睁蕾,位于F島的核電站苞笨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏子眶。R本人自食惡果不足惜瀑凝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望臭杰。 院中可真熱鬧粤咪,春花似錦、人聲如沸渴杆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)将塑。三九已至脉顿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間点寥,已是汗流浹背艾疟。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敢辩,地道東北人蔽莱。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像戚长,于是被迫代替她去往敵國(guó)和親盗冷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容