Android性能優(yōu)化:Bitmap圖片資源優(yōu)化

**版權(quán)聲明:本文為Carson_Ho原創(chuàng)文章腮介,轉(zhuǎn)載請注明出處稚叹!

目錄

一惜论、優(yōu)化原因

即為什么要優(yōu)化圖片Bitmap資源捧弃,具體如下圖
二赠叼、優(yōu)化的方向

本文將從以下方面優(yōu)化圖片Bitmap資源的使用&內(nèi)存管理
三擦囊、具體的優(yōu)化方案

下面,我將詳細(xì)講解每個優(yōu)化方向的具體優(yōu)化方案
3.1 使用完畢后 釋放圖片資源
  • 優(yōu)化原因
    使用完畢后若不釋放圖片資源嘴办,容易造成內(nèi)存泄露瞬场,從而導(dǎo)致內(nèi)存溢出

  • 優(yōu)化方案
    在 Android2.3.3(API 10)前,調(diào)用 Bitmap.recycle()方法
    在 Android2.3.3(API 10)后涧郊,采用軟引用(SoftReference)

  • 具體描述

    在 Android2.3.3(API 10)前贯被、后,Bitmap對象 & 其像素數(shù)據(jù) 的存儲位置不同妆艘,從而導(dǎo)致釋放圖片資源的方式不同彤灶,具體如下圖
    注:若調(diào)用了Bitmap.recycle()后,再繪制Bitmap批旺,則會出現(xiàn)Canvas: trying to use a recycled bitmap錯誤
  • 1.在Android3.0之前幌陕,Bitmap對象與數(shù)據(jù)是分開存儲的,Bitmap對象存儲在Java heap中 汽煮,而像素數(shù)據(jù)存儲在Native Memory中搏熄,Java虛擬機(jī)的垃圾回收機(jī)制不會主動回收Native Memory中的對象, 需要在Bitmap不需要使用的時候暇赤,主動調(diào)用recycle()方法來釋放心例,而在Android3.0之后, Bitmap的像素數(shù)據(jù)和Bitmap對象都存放在Java Heap中鞋囊,所以不需要手動調(diào)用recycle()來釋放止后,垃圾收集器會處理。

  • 2.應(yīng)該在什么時候去調(diào)用recycle()方法呢失暴?可以用引用計數(shù)算法坯门,用一個變量來記錄Bitmap顯示情況, 如果Bitmap繪制在View上面displayRefCount加一, 否則就減一, 只有在displayResCount為0且Bitmap不為空且Bitmap沒有調(diào)用過recycle()的時候逗扒,才調(diào)用recycle()古戴,下面用BitmapDrawable類來包裝下Bitmap對象。

public class RecycleBitmapDrawable extends BitmapDrawable {
private int displayResCount = 0;
private boolean mHasBeenDisplayes;

public RecycleBitmapDrawable(Resources res, Bitmap bitmap) {
    super(res,bitmap);
}

public void setIsDisplayed(boolean isDisplay) {
    synchronized (this) {
        if(isDisplay) {
            mHasBeenDisplayes = true;
            displayResCount ++;
        } else {
            displayResCount --;
        }
    }
    
    checkState();
}

/**
 * 檢查圖片的一些狀態(tài),判斷是否需要調(diào)用recycle
 */
private synchronized void checkState() {
    if(displayResCount <= 0 && mHasBeenDisplayes 
            && hasValidBitmap()) {
        getBitmap().recycle();
    }
}

/**
 * 判斷Bitmap是否為空且是否調(diào)用過recycle()
 * @return
 */
private synchronized boolean hasValidBitmap() {
    Bitmap bitmap = getBitmap();
    return bitmap != null && !bitmap.isRecycled();
}
3.2根據(jù)分辨率適配&縮放圖片
  • 優(yōu)化原因
    若BItmap與當(dāng)前設(shè)備的分辨率不匹配矩肩,則會拉伸Bitmap现恼,而Bitmap分辨率增加后,所占用的內(nèi)存也會相應(yīng)增加黍檩。因為Bitmap的內(nèi)存占用叉袍,根據(jù)x,y的大小來增加的。

  • 優(yōu)化方案
    public static int caculateSampleSize(BitmapFactory.Options options,
          int reqWidth, int reqHeight) {
      // height和width圖片長寬的像素
      final int height = options.outHeight;
      final int width = options.outWidth;
      int inSampleSize = 1;
      
      if (height > reqHeight || width > reqWidth) {
    
          final int halfHeight = height / 2;
          final int halfWidth = width / 2;
    
          while ((halfHeight / inSampleSize) > reqHeight
                  && (halfWidth / inSampleSize) > reqWidth) {
              inSampleSize *= 2;
          }
      }
    
      return inSampleSize;
    }
    
     /**
       * 通過設(shè)置inJustDecodeBounds屬性為true可以在解碼的時候避免內(nèi)存的分配,它會返回一個null的Bitmap,但是可以獲取
       * 到outWidth,outHeight與outMimeType刽酱。確定好壓縮比例后喳逛,再將inJustDecodeBounds設(shè)置為false
       */
    public static Bitmap decodeSampleBitmapFromResource(Resources res,int resId,
          int reqWidth, int reqHeight) {
      final BitmapFactory.Options options = new BitmapFactory.Options();
      options.inJustDecodeBounds = true;
      BitmapFactory.decodeResource(res, resId,options);
      
      //計算inSampleSize
      options.inSampleSize = caculateSampleSize(options, reqWidth, reqHeight);
      
      //根據(jù)inSampleSize壓縮圖片
      options.inJustDecodeBounds = false;
      return BitmapFactory.decodeResource(res, resId, options);
    }
    
3.3 按需 選擇合適的解碼方式
  • 優(yōu)化原因

    不同的圖片解碼方式 對應(yīng)的 內(nèi)存占用大小 相差很大,具體如下
  • 優(yōu)化方案
    根據(jù)需求 選擇合適的解碼方式
    使用參數(shù):BitmapFactory.inPreferredConfig 設(shè)置棵里,默認(rèn)使用解碼方式:ARGB_8888润文。
3.4 設(shè)置 圖片緩存
  • 優(yōu)化原因
    重復(fù)加載圖片資源耗費(fèi)太多資源(CPU姐呐、內(nèi)存 & 流量)
  • 優(yōu)化方案
    核心緩存原理:三級緩存:
  • 1.內(nèi)存緩存:緩存在內(nèi)存中,基于LRU(least recently used)算法,機(jī)器重啟消失.
  • 2.本地緩存:緩存在本地中典蝌,一般鍵值對形式曙砂。(url,filepath)
  • 3.網(wǎng)絡(luò)緩存:從網(wǎng)絡(luò)加載資源,然后緩存在內(nèi)存骏掀、本地中鸠澈。

內(nèi)存緩存

public class MemoryCacheUtils {
private LruCache<String,Bitmap> mMemoryCache;
public MemoryCacheUtils() {
    long maxMemory = Runtime.getRuntime().maxMemory()/8;//得到手機(jī)最大允許內(nèi)存的1/8,即超過指定內(nèi)存,則開始回收
    //需要傳入允許的內(nèi)存最大值,虛擬機(jī)默認(rèn)內(nèi)存16M,真機(jī)不一定相同
    mMemoryCache=new LruCache<String,Bitmap>((int) maxMemory){
       //用于計算每個條目的大小
        @Override
        protected int sizeOf(String key, Bitmap value) {
           int byteCount = value.getByteCount();
           return byteCount;
        }
    };
}

/**
 * 從內(nèi)存中讀圖片
 */
public Bitmap getBitmapFromMemory(String url) {
    //Bitmap bitmap = mMemoryCache.get(url); //1.強(qiáng)引用方法
     /*2.弱引用方法
    SoftReference<Bitmap> bitmapSoftReference = mMemoryCache.get(url);
    if (bitmapSoftReference != null) {
        Bitmap bitmap = bitmapSoftReference.get();
        return bitmap;
    }
    */
    if(url == null||"".equals(url)) {
        return null;
    }
    Bitmap bitmap = mMemoryCache.get(url);
    return bitmap;
}

/**
 * 往內(nèi)存中寫圖片
 * @param url
 * @param bitmap
 */
public void setBitmapToMemory(String url, Bitmap bitmap) {
    //mMemoryCache.put(url, bitmap);//1.強(qiáng)引用方法
    /*2.弱引用方法
    mMemoryCache.put(url, new SoftReference<>(bitmap));
    */
    mMemoryCache.put(url,bitmap);
}

本地緩存

public class LocalCacheUtils {
private static final String CACHE_PATH= Environment.getExternalStorageDirectory().getAbsolutePath()+"/my/images";

/**
 * 從本地讀取圖片
 * @param url
 */
public Bitmap getBitmapFromLocal(String url){
    String fileName = null;//把圖片的url當(dāng)做文件名,并進(jìn)行MD5加密
    try {
        fileName = MD5Encoder.encode(url);    //這里加不加密無所謂
        File file=new File(CACHE_PATH,fileName);
        Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
        return bitmap;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 從網(wǎng)絡(luò)獲取圖片后,保存至本地緩存
 * @param url
 * @param bitmap
 */
public void setBitmapToLocal(String url,Bitmap bitmap){
    try {
     
        String fileName = MD5Encoder.encode(url);//把圖片的url當(dāng)做文件名,并進(jìn)行MD5加密
        File file=new File(CACHE_PATH,fileName);

        //通過得到文件的父文件,判斷父文件是否存在
        File parentFile = file.getParentFile();
        if (!parentFile.exists()){
            parentFile.mkdirs();
        }
        //把圖片保存至本地
        bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(file));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

網(wǎng)絡(luò)緩存

public class NetCacheUtils {
private LocalCacheUtils mLocalCacheUtils;
private MemoryCacheUtils mMemoryCacheUtils;

public NetCacheUtils(LocalCacheUtils localCacheUtils, MemoryCacheUtils memoryCacheUtils) {
    mLocalCacheUtils = localCacheUtils;
    mMemoryCacheUtils = memoryCacheUtils;
}

public NetCacheUtils(){
    
}

/**
 * 從網(wǎng)絡(luò)下載圖片
 * @param ivPic 顯示圖片的imageview
 * @param url   下載圖片的網(wǎng)絡(luò)地址
 */
public void getBitmapFromNet(ImageView ivPic,String url) {
    new BitmapTask().execute(ivPic, url);//啟動AsyncTask
}


public void getBitmapFromNet(View ivPic, String url) {
    new BitmapTask().execute(ivPic, url);//啟動AsyncTask

}
public Bitmap getBitmapFromNet(final String url) {
    //啟動AsyncTask
    return null;
}

/**
 * AsyncTask就是對handler和線程池的封裝
 * 第一個泛型:參數(shù)類型
 * 第二個泛型:更新進(jìn)度的泛型
 * 第三個泛型:onPostExecute的返回結(jié)果
 */
@SuppressLint("NewApi")
class BitmapTask extends AsyncTask<Object, Void, Bitmap> {

    private View ivPic;
    private String url;

    /**
     * 后臺耗時操作,存在于子線程中
     * @param params
     * @return
     */
    @Override
    protected Bitmap doInBackground(Object[] params) {
        ivPic = (View) params[0];
        url = (String) params[1];

        return downLoadBitmap(url);
    }

    /**
     * 更新進(jìn)度,在主線程中
     * @param values
     */
    @Override
    protected void onProgressUpdate(Void[] values) {
        super.onProgressUpdate(values);
    }

    /**
     * 耗時方法結(jié)束后執(zhí)行該方法,主線程中
     * @param result
     */
    @Override
    protected void onPostExecute(Bitmap result) {
        if (result != null) {
            //ivPic.setImageBitmap(result);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {  
                //Android系統(tǒng)大于等于API16,使用setBackground  
                ivPic.setBackground(new BitmapDrawable(result));
            } else {  
                //Android系統(tǒng)小于API16截驮,使用setBackground  
                ivPic.setBackgroundDrawable(new BitmapDrawable(result));
            }  
            
            
            System.out.println("從網(wǎng)絡(luò)緩存圖片啦.....");

            //從網(wǎng)絡(luò)獲取圖片后,保存至本地緩存
            mLocalCacheUtils.setBitmapToLocal(url, result);
            //保存至內(nèi)存中
            mMemoryCacheUtils.setBitmapToMemory(url, result);

        }
    }
}
/**
 * 網(wǎng)絡(luò)下載圖片
 * @param url
 * @return
 */
public Bitmap downLoadBitmap(String url) {
    HttpURLConnection conn = null;
    try {
        conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);
        conn.setRequestMethod("GET");

        int responseCode = conn.getResponseCode();
        if (responseCode == 200) {
            //圖片壓縮
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize=2;//寬高壓縮為原來的1/2
            options.inPreferredConfig=Bitmap.Config.ARGB_4444;
            
            //Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream(),null,options);
            Bitmap bitmap=BitmapFactory.decodeStream(conn.getInputStream());
            return bitmap;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }catch (Exception e) {
    } finally {
        if(conn!=null){
            conn.disconnect();
        }
    }

    return null;
}

緩存管理類

public class MyBitmapUtils {
private NetCacheUtils mNetCacheUtils;
private LocalCacheUtils mLocalCacheUtils;
private MemoryCacheUtils mMemoryCacheUtils;

public MyBitmapUtils() {
    mMemoryCacheUtils = new MemoryCacheUtils();
    mLocalCacheUtils = new LocalCacheUtils();
    mNetCacheUtils = new NetCacheUtils(mLocalCacheUtils, mMemoryCacheUtils);
}

public Bitmap getBitmap(String url) {
    Bitmap bitmap = null;
    bitmap = mMemoryCacheUtils.getBitmapFromMemory(url);
    if (bitmap != null) {
        return bitmap;
    }

    bitmap = mLocalCacheUtils.getBitmapFromLocal(url);
    if (bitmap != null) {
        mMemoryCacheUtils.setBitmapToMemory(url, bitmap);
        return bitmap;
    }

    return bitmap;
}

public void disPlay(ImageView ivPic, String url) {
    Bitmap bitmap;

    // 內(nèi)存緩存
    bitmap = mMemoryCacheUtils.getBitmapFromMemory(url);
    if (bitmap != null) {
        ivPic.setImageBitmap(bitmap);
        Log.d("iamgecache", "從內(nèi)存獲取圖片啦.....--->" + url);
        return;
    }

    // 本地緩存
    bitmap = mLocalCacheUtils.getBitmapFromLocal(url);
    if (bitmap != null) {
        ivPic.setImageBitmap(bitmap);
        Log.d("iamgecache", "從本地獲取圖片啦.....-->" + url);
        // 從本地獲取圖片后,保存至內(nèi)存中
        mMemoryCacheUtils.setBitmapToMemory(url, bitmap);
        return;
    }
    // 網(wǎng)絡(luò)緩存
    mNetCacheUtils.getBitmapFromNet(ivPic, url);
    Log.d("iamgecache", "從網(wǎng)絡(luò)獲取圖片啦.....-->" + url);
}

@SuppressLint("NewApi")
public void disPlay(View ivPic, String url) {
    Bitmap bitmap;
    // 內(nèi)存緩存
    bitmap = mMemoryCacheUtils.getBitmapFromMemory(url);
    if (bitmap != null) {
        // ivPic.setImageBitmap(bitmap);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            // Android系統(tǒng)大于等于API16笑陈,使用setBackground
            ivPic.setBackground(new BitmapDrawable(bitmap));
        } else {
            // Android系統(tǒng)小于API16,使用setBackground
            ivPic.setBackgroundDrawable(new BitmapDrawable(bitmap));

        }
        // ivPic.setBackground(new BitmapDrawable(bitmap));

        Log.d("iamgecache", "從內(nèi)存獲取圖片啦.....--->" + url);
        return;
    }

    // 本地緩存
    bitmap = mLocalCacheUtils.getBitmapFromLocal(url);
    if (bitmap != null) {
        // ivPic.setImageBitmap(bitmap);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            // Android系統(tǒng)大于等于API16侧纯,使用setBackground
            ivPic.setBackground(new BitmapDrawable(bitmap));
        } else {
            // Android系統(tǒng)小于API16新锈,使用setBackground
            ivPic.setBackgroundDrawable(new BitmapDrawable(bitmap));
        }
        // ivPic.setBackground(new BitmapDrawable(bitmap));

        Log.d("iamgecache", "從本地獲取圖片啦.....-->" + url);
        // 從本地獲取圖片后,保存至內(nèi)存中
        mMemoryCacheUtils.setBitmapToMemory(url, bitmap);
        return;
    }
    // 網(wǎng)絡(luò)緩存
    mNetCacheUtils.getBitmapFromNet(ivPic, url);
    // ivPic.setBackground(new BitmapDrawable(bitmap));
    Log.d("iamgecache", "從網(wǎng)絡(luò)獲取圖片啦.....-->" + url);
}
4.總結(jié)

本文全面總結(jié)了圖片資源Bitmap的使用優(yōu)化,具體如下圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眶熬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子块请,更是在濱河造成了極大的恐慌娜氏,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墩新,死亡現(xiàn)場離奇詭異贸弥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)海渊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門绵疲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人臣疑,你說我怎么就攤上這事盔憨。” “怎么了讯沈?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵郁岩,是天一觀的道長。 經(jīng)常有香客問我缺狠,道長问慎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任挤茄,我火速辦了婚禮如叼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘穷劈。我一直安慰自己笼恰,他們只是感情好片酝,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挖腰,像睡著了一般雕沿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猴仑,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天审轮,我揣著相機(jī)與錄音,去河邊找鬼辽俗。 笑死疾渣,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崖飘。 我是一名探鬼主播榴捡,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼朱浴!你這毒婦竟也來了吊圾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤翰蠢,失蹤者是張志新(化名)和其女友劉穎项乒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梁沧,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡檀何,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了廷支。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片频鉴。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖恋拍,靈堂內(nèi)的尸體忽然破棺而出垛孔,到底是詐尸還是另有隱情,我是刑警寧澤芝囤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布似炎,位于F島的核電站,受9級特大地震影響悯姊,放射性物質(zhì)發(fā)生泄漏羡藐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一悯许、第九天 我趴在偏房一處隱蔽的房頂上張望仆嗦。 院中可真熱鬧,春花似錦先壕、人聲如沸瘩扼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽集绰。三九已至规辱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間栽燕,已是汗流浹背罕袋。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碍岔,地道東北人浴讯。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像蔼啦,于是被迫代替她去往敵國和親榆纽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

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