Android中BitMap加載和緩存

1.Bitmap的高效加載

1.1 通常如何加載Bitmap

Bitmap在Android指的是一張圖艇炎,可以是.png/.jpg等其他格式

BitmapFactory提供四類方法:

decodeFile窘茁、decodeResource尺棋、decodeStream谣殊、decodeByteArray

對應(yīng)從文件系統(tǒng)建椰、資源搪柑、輸入流世分、字節(jié)數(shù)組中加載出一個(gè)Bitmap對象

decodeFile编振、decodeResource又間接調(diào)用了decodeStream方法

1.2 如何高效加載Bitmap

核心思想是采用BitmapFactory.Options來加載所需尺寸的圖片

比如通過ImageView來顯示圖片,通常ImageView沒有圖片原始尺寸這么大,這時(shí)就通過BitmapFactory.Options按一定的采樣率來加載縮小后的圖片,降低內(nèi)存占用

1.3 BitmapFactory.Options

BitmapFactory.Options縮放圖片,主要是用到了 inSampleSize 參數(shù),即采樣率
inSampleSize為1時(shí),采樣為原始大小
inSampleSize 為2時(shí),圖片的寬高為原圖的1/2,像素?cái)?shù)為1/4,占用內(nèi)存為1/4
inSampleSize 為4,縮放比例就是1/16,即2的4次方
inSampleSize應(yīng)該為2的指數(shù)倍,2,4,8,16等
inSampleSize小于1,相當(dāng)于1
inSampleSize不為2 的指數(shù),向下取整為2的指數(shù)

1.4 如何獲取采樣率

●將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)為true并加載圖片
●設(shè)置為true后BitmapFactory只會(huì)解析圖片的原始寬高信息,并不會(huì)加載圖片
●根據(jù)結(jié)果設(shè)置合適的采樣率
● inJustDecodeBounds設(shè)回false然后重新加載圖片

舉例

public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();//拿到
        options.inJustDecodeBounds = true;//設(shè)置
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);//計(jì)算

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

    public int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        if (reqWidth == 0 || reqHeight == 0) {
            return 1;
        }

        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        Log.d(TAG, "origin, w= " + width + " h=" + height);
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and
            // keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        Log.d(TAG, "sampleSize:" + inSampleSize);
        return inSampleSize;
    }

2 Android中的緩存策略

緩存可以避免過多的消耗流量

當(dāng)用戶第一次從網(wǎng)上加載圖片后,會(huì)把圖片緩存在內(nèi)存中,再緩存到本地儲(chǔ)存設(shè)備.這樣當(dāng)應(yīng)用打算從網(wǎng)絡(luò)請求一張圖片的時(shí)候會(huì)先訪問內(nèi)存,再訪問儲(chǔ)存設(shè)備,都沒有后才會(huì)去下載

目前常用的緩存算法是LRU,近期最少使用算法

2.1 LruCache

最近最少使用緩存,它用強(qiáng)引用保存需要緩存的對象罚攀,內(nèi)部維護(hù)一個(gè)隊(duì)列(實(shí)際是LinkedhashMap內(nèi)部的雙向鏈表党觅,LruCache對其進(jìn)行了封裝雌澄,添加了線程安全操作),當(dāng)其中的一個(gè)值被訪問時(shí)杯瞻,它被放到隊(duì)列的尾部镐牺,當(dāng)緩存滿了,頭部的值會(huì)被丟棄魁莉,之后可以被垃圾回收睬涧。

2.2 LruCache的實(shí)現(xiàn)

需要提供緩存的總?cè)萘看笮〔⒅貙憇izeOf方法(計(jì)算緩存對象的大小)
LruCache還支持刪除操作,通過remove方法即可刪除一個(gè)指定的緩存對象

                //獲取最大可用的內(nèi)存空間
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;//緩存大小為總?cè)萘康?/8旗唁,單位KB
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {//sizeOf()用來計(jì)算緩存對象的大小
                return value.getRowBytes() * value.getHeight() / 1024;//除1024為了將單位轉(zhuǎn)成KB
            }
        };
    }
2.2.1 從Lrucache獲取一個(gè)緩存對象

mLruCache.get(key)

2.2.2 Lrucache添加一個(gè)緩存對象

mLruCache.put(key,bitmap)

2.2.3 刪除一個(gè)指定的緩存對象

mLruCache.remove(key);

2.3 DiskLruCache

● 用于實(shí)現(xiàn)存儲(chǔ)設(shè)備緩存畦浓,磁盤緩存
● 將緩存對象寫入文件系統(tǒng),從而實(shí)現(xiàn)緩存效果

2.3.1 DiskLruCache的創(chuàng)建

DiskLruCache并不能通過構(gòu)造方法來創(chuàng)建,它提供了open方法用于創(chuàng)建自身

    public static DiskLruCache open(File directory, 
                                    int appVersion, 
                                    int valueCount, 
                                    long maxSize)

第一個(gè)參數(shù):表示磁盤緩存在文件系統(tǒng)中的存儲(chǔ)路徑,可以選擇SD卡.(如果希望應(yīng)用卸載后刪除緩存文件,那么就選擇SD卡上的緩存目錄,否則應(yīng)該選擇SD卡上的其他特定目錄)
第二個(gè)參數(shù):表示應(yīng)用的版本號,一般為1,版本號改變時(shí)會(huì)清空之前所有的緩存文件
第三個(gè)參數(shù):表示單個(gè)節(jié)點(diǎn)所對應(yīng)的數(shù)據(jù)個(gè)數(shù),一般為1
第四個(gè)參數(shù):表示緩存的總大小,比如50MB

2.3.2 DiskLruCache的緩存添加

● 此操作是通過Editor來完成的
● 緩存首先要獲取圖片的url所對應(yīng)的key(url的md5值)
● 緩存只允許編輯一個(gè)緩存對象
● 通過Editor對象得到一個(gè)文件輸出流,寫入到文件系統(tǒng)上
● 最后Editor.commit()
○ 第一步

    private String hashKeyFormUrl(String url){
        String cacheKey;
        try {
            MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;//獲取URL對應(yīng)的key
    }
 
    private String bytesToHexString(byte[] digest) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < digest.length; i++) {
            String hex = Integer.toHexString(0xFF&digest[i]);
            if(hex.length() == 1){
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();

○ 網(wǎng)絡(luò)下載圖片检疫,通過文件輸出流寫入到文件系統(tǒng)上讶请。

 private boolean downloadUrlToStream(String urlString,OutputStream outputStream){
        int IO_BUFFER_SIZE = 0;
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
            int b ;
            while ((b = in.read()) != -1){
                out.write(b);
            }
            return true;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(urlConnection != null){
                urlConnection.disconnect();
            }
        }
        return false;
}

○ 將圖片的url轉(zhuǎn)為key之后,就可以獲取Editor對象了屎媳,對于這個(gè)key來說夺溢,如果當(dāng)前不存在其他Editor對象,那么edit()就會(huì)返回一個(gè)新的Editor對象烛谊,通過它就可以得到一個(gè)文件輸入流风响,通過Editor的commit完成提交。
int DISK_CACHE_INDEX = 0;//由于前面的open設(shè)置了一個(gè)節(jié)點(diǎn)只能有一個(gè)數(shù)據(jù)丹禀,因此DISK_CACHE_SIZE = 0

                String key = hashKeyFormUrl(url);
                try {
                    DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                    if(editor != null){
                        OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
                     //執(zhí)行下載
                     if(downloadUrlToStream(url,outputStream)){
                            //提交寫入操作
                            editor.commit();
                        }else {
                           //下載異常状勤,執(zhí)行回退操作
                            editor.abort();
                        }
                         //更新操作
                         mDiskCache.flush();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
2.3.3 DiskLruCache的緩存的查找

● 查找也需要將url轉(zhuǎn)換為key
● 然后通過DiskLruCache的get方法得到一個(gè)Snapshot對象
● 再通過Snapshot對象得到緩存的文件輸入流
● 記得通過BitmapFactory.Options加載一個(gè)縮放后的圖片

     Bitmap bitmap = null;
     String keys = hashKeyFormUrl(url);
     try {
       DiskLruCache.Snapshot snapshot = mDiskLruCache.get(keys);
       if(snapshot != null){
 FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
        FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap=mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);
                        if(bitmap != null){
                            addBitmapToMemoryCache(keys,bitmap);
                        }

2.4 ImageLoader

2.4.1 ImageLoader的實(shí)現(xiàn)

一個(gè)優(yōu)秀的ImageLoader應(yīng)具備如下功能:

● 圖片的同步加載(從內(nèi)存緩存、磁盤緩存双泪、網(wǎng)絡(luò)中獲取的)
● 圖片的異步加載(ImageLoader內(nèi)部需要自己在線程中加載圖片并將圖片設(shè)置給所需的ImageView)
● 圖片壓縮
● 內(nèi)存緩存
● 磁盤緩存
● 網(wǎng)絡(luò)拉取

ImageLoader還需要處理在ListView或者GridView中,快速下拉時(shí)圖片在item錯(cuò)位的情況

內(nèi)存緩存和磁盤緩存是ImageLoader的核心持搜,通過這兩級緩存極大的提高了程序的效率并降低了流量消耗,只有這兩級緩存都不可用時(shí)才需要從網(wǎng)絡(luò)中拉去圖片攒读。

2.4.2 優(yōu)化卡頓現(xiàn)象

● 關(guān)鍵是不要在主線程中做太多耗時(shí)的操作
● 不要在getView中執(zhí)行耗時(shí)操作(如加載圖片)
● 控制異步任務(wù)的執(zhí)行頻率,比如頻繁的上下滑動(dòng)會(huì)產(chǎn)生N個(gè)異步任務(wù).這時(shí)可以·考慮在列表滑動(dòng)的時(shí)候停止加載圖片,停下來再加載
● 開啟硬件加速:android:hardwareAccelerated=“true”

來自:https://www.yuque.com/mrprf/sgsv7s/hq8gm5#6e7ca743

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朵诫,一起剝皮案震驚了整個(gè)濱河市辛友,隨后出現(xiàn)的幾起案子薄扁,更是在濱河造成了極大的恐慌,老刑警劉巖废累,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邓梅,死亡現(xiàn)場離奇詭異,居然都是意外死亡邑滨,警方通過查閱死者的電腦和手機(jī)日缨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掖看,“玉大人匣距,你說我怎么就攤上這事面哥。” “怎么了毅待?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵尚卫,是天一觀的道長。 經(jīng)常有香客問我尸红,道長吱涉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任外里,我火速辦了婚禮怎爵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盅蝗。我一直安慰自己鳖链,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布墩莫。 她就那樣靜靜地躺著撒轮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贼穆。 梳的紋絲不亂的頭發(fā)上题山,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音故痊,去河邊找鬼顶瞳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛愕秫,可吹牛的內(nèi)容都是我干的慨菱。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼戴甩,長吁一口氣:“原來是場噩夢啊……” “哼符喝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起甜孤,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤协饲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后缴川,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茉稠,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年把夸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了而线。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖膀篮,靈堂內(nèi)的尸體忽然破棺而出嘹狞,到底是詐尸還是另有隱情,我是刑警寧澤誓竿,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布刁绒,位于F島的核電站,受9級特大地震影響烤黍,放射性物質(zhì)發(fā)生泄漏知市。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一速蕊、第九天 我趴在偏房一處隱蔽的房頂上張望嫂丙。 院中可真熱鬧,春花似錦规哲、人聲如沸跟啤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隅肥。三九已至,卻和暖如春袄简,著一層夾襖步出監(jiān)牢的瞬間腥放,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工绿语, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秃症,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓吕粹,卻偏偏與公主長得像种柑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子匹耕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

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