溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Bitmap優(yōu)化詳談

發(fā)布時(shí)間:2020-06-20 06:55:06 來源:網(wǎng)絡(luò) 閱讀:512 作者:楊充 欄目:移動(dòng)開發(fā)
目錄介紹
  • 01.如何計(jì)算Bitmap占用內(nèi)存
    • 1.1 如何計(jì)算占用內(nèi)存
    • 1.2 上面方法計(jì)算內(nèi)存對(duì)嗎
    • 1.3 一個(gè)像素占用多大內(nèi)存
  • 02.Bitmap常見四種顏色格式
    • 2.1 什么是bitmap
    • 2.2 Android常見是那種
    • 2.3 常見四種顏色格式介紹
    • 2.4 Bitmap到底有幾種顏色格式
  • 03.Bitmap壓縮技術(shù)
    • 3.1 質(zhì)量壓縮
    • 3.2 采樣率壓縮
    • 3.3 縮放法壓縮
  • 04.Bitmap回收問題
    • 4.1 recycle()方法
    • 4.2 緩存原理
    • 4.3 Bitmap的復(fù)用
  • 05.Bitmap常見操作
    • 5.1 Bitmap的壓縮方式
    • 5.2 Bitmap如何復(fù)用
    • 5.3 Bitmap使用API獲取內(nèi)存
    • 5.4 該博客對(duì)應(yīng)測(cè)試項(xiàng)目地址

好消息

  • 博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時(shí)也開源了生活博客,從12年起,積累共計(jì)50篇[近30萬字],轉(zhuǎn)載請(qǐng)注明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
  • 輪播圖封裝庫:https://github.com/yangchong211/YCBanner
  • 輕量級(jí)版本更新彈窗:https://github.com/yangchong211/YCUpdateApp
  • 通知欄封裝庫:https://github.com/yangchong211/YCNotification

01.如何計(jì)算Bitmap占用內(nèi)存

  • 歡迎直接查看demo的壓縮效果,https://github.com/yangchong211/YCBanner
1.1 如何計(jì)算占用內(nèi)存
  • 如果圖片要顯示下Android設(shè)備上,ImageView最終是要加載Bitmap對(duì)象的,就要考慮單個(gè)Bitmap對(duì)象的內(nèi)存占用了,如何計(jì)算一張圖片的加載到內(nèi)存的占用呢?其實(shí)就是所有像素的內(nèi)存占用總和:
  • bitmap內(nèi)存大小 = 圖片長(zhǎng)度 x 圖片寬度 x 單位像素占用的字節(jié)數(shù)
  • 起決定因素就是最后那個(gè)參數(shù)了,Bitmap'常見有2種編碼方式:ARGB_8888和RGB_565,ARGB_8888每個(gè)像素點(diǎn)4個(gè)byte,RGB_565是2個(gè)byte,一般都采用ARGB_8888這種。那么常見的1080*1920的圖片內(nèi)存占用就是:1920 x 1080 x 4 = 7.9M
1.2 上面方法計(jì)算內(nèi)存對(duì)嗎
  • 我看到好多博客都是這樣計(jì)算的,但是這樣算對(duì)嗎?有沒有哥們?cè)囼?yàn)過這種方法正確性?我覺得看博客要對(duì)博主表示懷疑,論證別人寫的是否正確。更多詳細(xì)可以看我的GitHub:https://github.com/yangchong211

    • 說出我的結(jié)論:上面1.1這種說法也對(duì),但是不全對(duì),沒有說明場(chǎng)景,同時(shí)也忽略了一個(gè)影響項(xiàng):Density。接下來看看源代碼。
    • inDensity默認(rèn)為圖片所在文件夾對(duì)應(yīng)的密度;inTargetDensity為當(dāng)前系統(tǒng)密度。
    • 加載一張本地資源圖片,那么它占用的內(nèi)存 = width height nTargetDensity/inDensity nTargetDensity/inDensity 一個(gè)像素所占的內(nèi)存。

      @Nullable
      public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
          @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
      validate(opts);
      if (opts == null) {
          opts = new Options();
      }
      
      if (opts.inDensity == 0 && value != null) {
          final int density = value.density;
          if (density == TypedValue.DENSITY_DEFAULT) {
              opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
          } else if (density != TypedValue.DENSITY_NONE) {
              opts.inDensity = density;
          }
      }
      
      if (opts.inTargetDensity == 0 && res != null) {
          opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
      }
      
      return decodeStream(is, pad, opts);
      }
  • 正確說法,這個(gè)注意呢?計(jì)算公式如下所示
    • 對(duì)資源文件:width height nTargetDensity/inDensity nTargetDensity/inDensity 一個(gè)像素所占的內(nèi)存;
    • 別的:width height 一個(gè)像素所占的內(nèi)存;
1.3 一個(gè)像素占用多大內(nèi)存
  • Bitmap.Config用來描述圖片的像素是怎么被存儲(chǔ)的?
    • ARGB_8888: 每個(gè)像素4字節(jié). 共32位,默認(rèn)設(shè)置。
    • Alpha_8: 只保存透明度,共8位,1字節(jié)。
    • ARGB_4444: 共16位,2字節(jié)。
    • RGB_565:共16位,2字節(jié),只存儲(chǔ)RGB值。

02.Bitmap常見四種顏色格式

2.1 什么是bitmap
  • 位圖文件(Bitmap),擴(kuò)展名可以是.bmp或者.dib。位圖是Windows標(biāo)準(zhǔn)格式圖形文件,它將圖像定義為由點(diǎn)(像素)組成,每個(gè)點(diǎn)可以由多種色彩表示,包括2、4、8、16、24和32位色彩。位圖文件是非壓縮格式的,需要占用較大存儲(chǔ)空間。
2.2 Android常見是那種
  • 在Gesture類中
    • Bitmap優(yōu)化詳談
  • 在Notification類中
    • Bitmap優(yōu)化詳談
  • 在fw源碼中bitmap圖片一般是以ARGB_8888(ARGB分別代表的是透明度,紅色,綠色,藍(lán)色,每個(gè)值分別用8bit來記錄,也就是一個(gè)像素會(huì)占用4byte,共32bit)來進(jìn)行存儲(chǔ)的。
2.3 常見四種顏色格式介紹
  • 四種顏色格式如下所示
    • Bitmap優(yōu)化詳談
  • 說明
    • 在實(shí)際應(yīng)用中而言,建議使用ARGB_8888以及RGB_565。 如果你不需要透明度,選擇RGB_565,可以減少一半的內(nèi)存占用。
    • ARGB_8888:ARGB分別代表的是透明度,紅色,綠色,藍(lán)色,每個(gè)值分別用8bit來記錄,也就是一個(gè)像素會(huì)占用4byte,共32bit.
    • ARGB_4444:ARGB的是每個(gè)值分別用4bit來記錄,一個(gè)像素會(huì)占用2byte,共16bit.
    • RGB_565:R=5bit,G=6bit,B=5bit,不存在透明度,每個(gè)像素會(huì)占用2byte,共16bit
    • ALPHA_8:該像素只保存透明度,會(huì)占用1byte,共8bit.
2.4 Bitmap到底有幾種顏色格式
  • 上面我說到了常見的四種,言下之意應(yīng)該不止四種,那到底有幾種呢?查看源碼可知,具體有6種類型。查看Bitmap源碼之Config配置。
    • Bitmap優(yōu)化詳談
  • 配置Config.HARDWARE為啥異常,看下面源碼提示
    • Bitmap優(yōu)化詳談

03.Bitmap壓縮技術(shù)

3.1 質(zhì)量壓縮
  • 質(zhì)量壓縮方法:在保持像素的前提下改變圖片的位深及透明度等,來達(dá)到壓縮圖片的目的,這樣適合去傳遞二進(jìn)制的圖片數(shù)據(jù),比如分享圖片,要傳入二進(jìn)制數(shù)據(jù)過去,限制500kb之內(nèi)。

    • 1、bitmap圖片的大小不會(huì)改變
    • 2、bytes.length是隨著quality變小而變小的。
      
      /**
      * 第一種:質(zhì)量壓縮法
      * @param image     目標(biāo)原圖
      * @param maxSize   最大的圖片大小
      * @return          bitmap,注意可以測(cè)試以下壓縮前后bitmap的大小值
      */
      public static Bitmap compressImage(Bitmap image , long maxSize) {
      int byteCount = image.getByteCount();
      Log.i("yc壓縮圖片","壓縮前大小"+byteCount);
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      // 把ByteArrayInputStream數(shù)據(jù)生成圖片
      Bitmap bitmap = null;
      // 質(zhì)量壓縮方法,options的值是0-100,這里100表示原來圖片的質(zhì)量,不壓縮,把壓縮后的數(shù)據(jù)存放到baos中
      image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
      int options = 90;
      // 循環(huán)判斷如果壓縮后圖片是否大于maxSize,大于繼續(xù)壓縮
      while (baos.toByteArray().length  > maxSize) {
          // 重置baos即清空baos
          baos.reset();
          // 這里壓縮options%,把壓縮后的數(shù)據(jù)存放到baos中
          image.compress(Bitmap.CompressFormat.JPEG, options, baos);
          // 每次都減少10,當(dāng)為1的時(shí)候停止,options<10的時(shí)候,遞減1
          if(options == 1){
              break;
          }else if (options <= 10) {
              options -= 1;
          } else {
              options -= 10;
          }
      }
      byte[] bytes = baos.toByteArray();
      if (bytes.length != 0) {
          // 把壓縮后的數(shù)據(jù)baos存放到bytes中
          bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
          int byteCount1 = bitmap.getByteCount();
          Log.i("yc壓縮圖片","壓縮后大小"+byteCount1);
      }
      return bitmap;
      }

    /**

    • 第一種:質(zhì)量壓縮法
    • @param src 源圖片
    • @param maxByteSize 允許最大值字節(jié)數(shù)
    • @param recycle 是否回收
    • @return 質(zhì)量壓縮壓縮過的圖片
      */
      public static Bitmap compressByQuality(final Bitmap src, final long maxByteSize, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0 || maxByteSize <= 0) {
      return null;
      }
      Log.i("yc壓縮圖片","壓縮前大小"+src.getByteCount());
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
      byte[] bytes;
      if (baos.size() <= maxByteSize) {// 最好質(zhì)量的不大于最大字節(jié),則返回最佳質(zhì)量
      bytes = baos.toByteArray();
      } else {
      baos.reset();
      src.compress(Bitmap.CompressFormat.JPEG, 0, baos);
      if (baos.size() >= maxByteSize) { // 最差質(zhì)量不小于最大字節(jié),則返回最差質(zhì)量
      bytes = baos.toByteArray();
      } else {
      // 二分法尋找最佳質(zhì)量
      int st = 0;
      int end = 100;
      int mid = 0;
      while (st < end) {
      mid = (st + end) / 2;
      baos.reset();
      src.compress(Bitmap.CompressFormat.JPEG, mid, baos);
      int len = baos.size();
      if (len == maxByteSize) {
      break;
      } else if (len > maxByteSize) {
      end = mid - 1;
      } else {
      st = mid + 1;
      }
      }
      if (end == mid - 1) {
      baos.reset();
      src.compress(Bitmap.CompressFormat.JPEG, st, baos);
      }
      bytes = baos.toByteArray();
      }
      }
      if (recycle && !src.isRecycled()){
      src.recycle();
      }
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
      Log.i("yc壓縮圖片","壓縮后大小"+bitmap.getByteCount());
      return bitmap;
      }

    /**

    • 第一種:質(zhì)量壓縮法
    • @param src 源圖片
    • @param quality 質(zhì)量
    • @param recycle 是否回收
    • @return 質(zhì)量壓縮后的圖片
      */
      public static Bitmap compressByQuality(final Bitmap src, @IntRange(from = 0, to = 100) final int quality, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
      return null;
      }
      Log.i("yc壓縮圖片","壓縮前大小"+src.getByteCount());
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      src.compress(Bitmap.CompressFormat.JPEG, quality, baos);
      byte[] bytes = baos.toByteArray();
      if (recycle && !src.isRecycled()) {
      src.recycle();
      }
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
      Log.i("yc壓縮圖片","壓縮后大小"+bitmap.getByteCount());
      return bitmap;
      }
3.2 采樣率壓縮
  • 什么是采樣率壓縮?

    • 設(shè)置inSampleSize的值(int類型)后,假如設(shè)為n,則寬和高都為原來的1/n,寬高都減少,內(nèi)存降低。上面的代碼沒用過options.inJustDecodeBounds = true;因?yàn)槲沂枪潭▉砣拥臄?shù)據(jù),為什么這個(gè)壓縮方法叫采樣率壓縮?是因?yàn)榕浜蟟nJustDecodeBounds,先獲取圖片的寬、高(這個(gè)過程就是取樣)。然后通過獲取的寬高,動(dòng)態(tài)的設(shè)置inSampleSize的值。當(dāng)inJustDecodeBounds設(shè)置為true的時(shí)候, BitmapFactory通過decodeResource或者decodeFile解碼圖片時(shí),將會(huì)返回空(null)的Bitmap對(duì)象,這樣可以避免Bitmap的內(nèi)存分配, 但是它可以返回Bitmap的寬度、高度以及MimeType。
      
      /**
      * 第二種:按采樣大小壓縮
      *
      * @param src        源圖片
      * @param sampleSize 采樣率大小
      * @param recycle    是否回收
      * @return 按采樣率壓縮后的圖片
      */
      public static Bitmap compressBySampleSize(final Bitmap src, final int sampleSize, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
          return null;
      }
      Log.i("yc壓縮圖片","壓縮前大小"+src.getByteCount());
      BitmapFactory.Options options = new BitmapFactory.Options();
      options.inSampleSize = sampleSize;
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
      byte[] bytes = baos.toByteArray();
      if (recycle && !src.isRecycled()) {
          src.recycle();
      }
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
      Log.i("yc壓縮圖片","壓縮后大小"+bitmap.getByteCount());
      return bitmap;
      }

    /**

    • 第二種:按采樣大小壓縮
    • @param src 源圖片
    • @param maxWidth 最大寬度
    • @param maxHeight 最大高度
    • @param recycle 是否回收
    • @return 按采樣率壓縮后的圖片
      */
      public static Bitmap compressBySampleSize(final Bitmap src, final int maxWidth, final int maxHeight, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
      return null;
      }
      Log.i("yc壓縮圖片","壓縮前大小"+src.getByteCount());
      BitmapFactory.Options options = new BitmapFactory.Options();
      options.inJustDecodeBounds = true;
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
      byte[] bytes = baos.toByteArray();
      BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
      options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
      options.inJustDecodeBounds = false;
      if (recycle && !src.isRecycled()) {
      src.recycle();
      }
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
      Log.i("yc壓縮圖片","壓縮后大小"+bitmap.getByteCount());
      return bitmap;
      }

    /**

    • 計(jì)算獲取縮放比例inSampleSize
      /
      private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
      final int height = options.outHeight;
      final int width = options.outWidth;
      int inSampleSize = 1;
      if (height > reqHeight || width > reqWidth) {
      final int heightRatio = Math.round((float) height / (float) reqHeight);
      final int widthRatio = Math.round((float) width / (float) reqWidth);
      inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
      }
      final float totalPixels = width
      height;
      final float totalReqPixelsCap = reqWidth reqHeight 2;
      while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
      inSampleSize++;
      }
      return inSampleSize;
      }
3.3 縮放法壓縮
  • Android中使用Matrix對(duì)圖像進(jìn)行縮放、旋轉(zhuǎn)、平移、斜切等變換的。

    • Matrix提供了一些方法來控制圖片變換:Matrix調(diào)用一系列set,pre,post方法時(shí),可視為將這些方法插入到一個(gè)隊(duì)列。當(dāng)然,按照隊(duì)列中從頭至尾的順序調(diào)用執(zhí)行。其中pre表示在隊(duì)頭插入一個(gè)方法,post表示在隊(duì)尾插入一個(gè)方法。而set表示把當(dāng)前隊(duì)列清空,并且總是位于隊(duì)列的最中間位置。當(dāng)執(zhí)行了一次set后:pre方法總是插入到set前部的隊(duì)列的最前面,post方法總是插入到set后部的隊(duì)列的最后面
      setTranslate(float dx,float dy):控制Matrix進(jìn)行位移。
      setSkew(float kx,float ky):控制Matrix進(jìn)行傾斜,kx、ky為X、Y方向上的比例。
      setSkew(float kx,float ky,float px,float py):控制Matrix以px、py為軸心進(jìn)行傾斜,kx、ky為X、Y方向上的傾斜比例。
      setRotate(float degrees):控制Matrix進(jìn)行depress角度的旋轉(zhuǎn),軸心為(0,0)。
      setRotate(float degrees,float px,float py):控制Matrix進(jìn)行depress角度的旋轉(zhuǎn),軸心為(px,py)。
      setScale(float sx,float sy):設(shè)置Matrix進(jìn)行縮放,sx、sy為X、Y方向上的縮放比例。
      setScale(float sx,float sy,float px,float py):設(shè)置Matrix以(px,py)為軸心進(jìn)行縮放,sx、sy為X、Y方向上的縮放比例。
    • 縮放法壓縮工具類代碼
      
      /**
      * 第三種:按縮放壓縮
      *
      * @param src                   源圖片
      * @param newWidth              新寬度
      * @param newHeight             新高度
      * @param recycle               是否回收
      * @return                      縮放壓縮后的圖片
      */
      public static Bitmap compressByScale(final Bitmap src, final int newWidth, final int newHeight, final boolean recycle) {
      return scale(src, newWidth, newHeight, recycle);
      }

    public static Bitmap compressByScale(final Bitmap src, final float scaleWidth, final float scaleHeight, final boolean recycle) {
    return scale(src, scaleWidth, scaleHeight, recycle);
    }

    /**

    • 縮放圖片
    • @param src 源圖片
    • @param scaleWidth 縮放寬度倍數(shù)
    • @param scaleHeight 縮放高度倍數(shù)
    • @param recycle 是否回收
    • @return 縮放后的圖片
      */
      private static Bitmap scale(final Bitmap src, final float scaleWidth, final float scaleHeight, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
      return null;
      }
      Matrix matrix = new Matrix();
      matrix.setScale(scaleWidth, scaleHeight);
      Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
      if (recycle && !src.isRecycled()) {
      src.recycle();
      }
      return ret;
      }

04.Bitmap回收問題

4.1 recycle()方法
  • 如何調(diào)用這個(gè)recycle()方法
    if (bitmap != null && !bitmap.isRecycled()) {
        bitmap.recycle();
        bitmap = null;
    }
  • 思考以下,為何調(diào)用recycle()需要做非空判斷?這里可以引出bitmap系統(tǒng)回收功能。小楊我如果分析不對(duì),歡迎反饋。
    • 首先看看源碼……順便翻一下該方法的注釋!我是用有道翻譯的,大意如下:釋放與此位圖關(guān)聯(lián)的本機(jī)對(duì)象,并清除對(duì)像素?cái)?shù)據(jù)的引用。這將不會(huì)同步釋放像素?cái)?shù)據(jù);如果沒有其他引用,它只允許垃圾收集。位圖被標(biāo)記為“死”,這意味著如果調(diào)用getPixels()或setPixels(),它將拋出異常,并且不會(huì)繪制任何東西。此操作不能反轉(zhuǎn),因此只有在確定沒有進(jìn)一步使用位圖的情況下才應(yīng)調(diào)用該操作。這是一個(gè)高級(jí)調(diào)用,通常不需要調(diào)用,因?yàn)楫?dāng)沒有對(duì)此位圖的引用時(shí),普通GC進(jìn)程將釋放此內(nèi)存。
      public void recycle() {
      if (!mRecycled && mNativePtr != 0) {
          if (nativeRecycle(mNativePtr)) {
              // return value indicates whether native pixel object was actually recycled.
              // false indicates that it is still in use at the native level and these
              // objects should not be collected now. They will be collected later when the
              // Bitmap itself is collected.
              mNinePatchChunk = null;
          }
          mRecycled = true;
      }
      }
  • 通常不需要調(diào)用?這是為啥?
    • 在Android3.0以后Bitmap是存放在堆中的,只要回收堆內(nèi)存即可。官方建議我們3.0以后使用recycle()方法進(jìn)行回收,該方法可以不主動(dòng)調(diào)用,因?yàn)槔厥掌鲿?huì)自動(dòng)收集不可用的Bitmap對(duì)象進(jìn)行回收。
    • 那么何是進(jìn)行回收呢?這里面涉及到bitmap的緩存算法,還有GC回收垃圾機(jī)制。關(guān)于GC回收機(jī)制可以看我這篇博客:https://blog.csdn.net/m0_37700275/article/details/83651039
    • 大概就是移除最少使用的緩存和使用最久的緩存,先說出結(jié)論,下來接著分析!
4.2 緩存原理
  • LruCache原理
    • LruCache是個(gè)泛型類,內(nèi)部采用LinkedHashMap來實(shí)現(xiàn)緩存機(jī)制,它提供get方法和put方法來獲取緩存和添加緩存,其最重要的方法trimToSize是用來移除最少使用的緩存和使用最久的緩存,并添加最新的緩存到隊(duì)列中。
4.3 Bitmap的復(fù)用
  • Android3.0之后,并沒有強(qiáng)調(diào)Bitmap.recycle();而是強(qiáng)調(diào)Bitmap的復(fù)用。
    • 使用LruCache對(duì)Bitmap進(jìn)行緩存,當(dāng)再次使用到這個(gè)Bitmap的時(shí)候直接獲取,而不用重走編碼流程。
    • Android3.0(API 11之后)引入了BitmapFactory.Options.inBitmap字段,設(shè)置此字段之后解碼方法會(huì)嘗試復(fù)用一張存在的Bitmap。這意味著Bitmap的內(nèi)存被復(fù)用,避免了內(nèi)存的回收及申請(qǐng)過程,顯然性能表現(xiàn)更佳。
    • 使用這個(gè)字段有幾點(diǎn)限制:
      • 聲明可被復(fù)用的Bitmap必須設(shè)置inMutable為true;
      • Android4.4(API 19)之前只有格式為jpg、png,同等寬高(要求苛刻),inSampleSize為1的Bitmap才可以復(fù)用;
      • Android4.4(API 19)之前被復(fù)用的Bitmap的inPreferredConfig會(huì)覆蓋待分配內(nèi)存的Bitmap設(shè)置的inPreferredConfig;
      • Android4.4(API 19)之后被復(fù)用的Bitmap的內(nèi)存必須大于需要申請(qǐng)內(nèi)存的Bitmap的內(nèi)存;
      • Android4.4(API 19)之前待加載Bitmap的Options.inSampleSize必須明確指定為1。

05.Bitmap常見操作

5.1 Bitmap的壓縮方式
  • 常見壓縮方法Api
    • Bitmap.compress(),質(zhì)量壓縮,不會(huì)對(duì)內(nèi)存產(chǎn)生影響;
    • BitmapFactory.Options.inSampleSize,內(nèi)存壓縮;
  • Bitmap.compress()
    • 質(zhì)量壓縮,不會(huì)對(duì)內(nèi)存產(chǎn)生影響
    • 它是在保持像素的前提下改變圖片的位深及透明度等,來達(dá)到壓縮圖片的目的,不會(huì)減少圖片的像素。進(jìn)過它壓縮的圖片文件大小會(huì)變小,但是解碼成bitmap后占得內(nèi)存是不變的。
  • BitmapFactory.Options.inSampleSize
    • 內(nèi)存壓縮
    • 解碼圖片時(shí),設(shè)置BitmapFactory.Options類的inJustDecodeBounds屬性為true,可以在Bitmap不被加載到內(nèi)存的前提下,獲取Bitmap的原始寬高。而設(shè)置BitmapFactory.Options的inSampleSize屬性可以真實(shí)的壓縮Bitmap占用的內(nèi)存,加載更小內(nèi)存的Bitmap。
    • 設(shè)置inSampleSize之后,Bitmap的寬、高都會(huì)縮小inSampleSize倍。例如:一張寬高為2048x1536的圖片,設(shè)置inSampleSize為4之后,實(shí)際加載到內(nèi)存中的圖片寬高是512x384。占有的內(nèi)存就是0.75M而不是12M,足足節(jié)省了15倍。
    • 備注:inSampleSize值的大小不是隨便設(shè)、或者越大越好,需要根據(jù)實(shí)際情況來設(shè)置。inSampleSize比1小的話會(huì)被當(dāng)做1,任何inSampleSize的值會(huì)被取接近2的冪值。
5.2 Bitmap如何復(fù)用
  • Bitmap復(fù)用的實(shí)驗(yàn),代碼如下所示,然后看打印的日志信息

    • 從內(nèi)存地址的打印可以看出,兩個(gè)對(duì)象其實(shí)是一個(gè)對(duì)象,Bitmap復(fù)用成功;
    • bitmapReuse占用的內(nèi)存(4346880)正好是bitmap占用內(nèi)存(1228800)的四分之一;
    • getByteCount()獲取到的是當(dāng)前圖片應(yīng)當(dāng)所占內(nèi)存大小,getAllocationByteCount()獲取到的是被復(fù)用Bitmap真實(shí)占用內(nèi)存大小。雖然bitmapReuse的內(nèi)存只有4346880,但是因?yàn)槭菑?fù)用的bitmap的內(nèi)存,因而其真實(shí)占用的內(nèi)存大小是被復(fù)用的bitmap的內(nèi)存大?。?228800)。這也是getAllocationByteCount()可能比getByteCount()大的原因。

      @RequiresApi(api = Build.VERSION_CODES.KITKAT)
      private void initBitmap() {
      BitmapFactory.Options options = new BitmapFactory.Options();
      // 圖片復(fù)用,這個(gè)屬性必須設(shè)置;
      options.inMutable = true;
      // 手動(dòng)設(shè)置縮放比例,使其取整數(shù),方便計(jì)算、觀察數(shù)據(jù);
      options.inDensity = 320;
      options.inTargetDensity = 320;
      Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_autumn_tree_min, options);
      // 對(duì)象內(nèi)存地址;
      Log.i("ycBitmap", "bitmap = " + bitmap);
      Log.i("ycBitmap", "ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
      // 使用inBitmap屬性,這個(gè)屬性必須設(shè)置;
      options.inBitmap = bitmap; options.inDensity = 320;
      // 設(shè)置縮放寬高為原始寬高一半;
      options.inTargetDensity = 160;
      options.inMutable = true;
      Bitmap bitmapReuse = BitmapFactory.decodeResource(getResources(), R.drawable.bg_kites_min, options);
      // 復(fù)用對(duì)象的內(nèi)存地址;
      Log.i("ycBitmap", "bitmapReuse = " + bitmapReuse);
      Log.i("ycBitmap", "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
      Log.i("ycBitmap", "bitmapReuse:ByteCount = " + bitmapReuse.getByteCount() + ":::bitmapReuse:AllocationByteCount = " + bitmapReuse.getAllocationByteCount());
      
      //11-26 18:24:07.971 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap = android.graphics.Bitmap@9739bff
      //11-26 18:24:07.972 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap:ByteCount = 4346880:::bitmap:AllocationByteCount = 4346880
      //11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmapReuse = android.graphics.Bitmap@9739bff
      //11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap:ByteCount = 1228800:::bitmap:AllocationByteCount = 4346880
      //11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmapReuse:ByteCount = 1228800:::bitmapReuse:AllocationByteCount = 4346880
      }
5.3 Bitmap使用API獲取內(nèi)存
  • getByteCount()
    • getByteCount()方法是在API12加入的,代表存儲(chǔ)Bitmap的色素需要的最少內(nèi)存。API19開始getAllocationByteCount()方法代替了getByteCount()。
  • getAllocationByteCount()
    • API19之后,Bitmap加了一個(gè)Api:getAllocationByteCount();代表在內(nèi)存中為Bitmap分配的內(nèi)存大小。
      public final int getAllocationByteCount() {
      if (mRecycled) {
          Log.w(TAG, "Called getAllocationByteCount() on a recycle()'d bitmap! "
                  + "This is undefined behavior!");
          return 0;
      }
      return nativeGetAllocationByteCount(mNativePtr);
      }
  • 思考: getByteCount()與getAllocationByteCount()的區(qū)別?
    • 一般情況下兩者是相等的;
    • 通過復(fù)用Bitmap來解碼圖片,如果被復(fù)用的Bitmap的內(nèi)存比待分配內(nèi)存的Bitmap大,那么getByteCount()表示新解碼圖片占用內(nèi)存的大?。ú⒎菍?shí)際內(nèi)存大小,實(shí)際大小是復(fù)用的那個(gè)Bitmap的大?。琯etAllocationByteCount()表示被復(fù)用Bitmap真實(shí)占用的內(nèi)存大?。磎Buffer的長(zhǎng)度)。
  • 在復(fù)用Bitmap的情況下,getAllocationByteCount()可能會(huì)比getByteCount()大。
5.4 該博客對(duì)應(yīng)測(cè)試項(xiàng)目地址
  • 歡迎直接查看demo的壓縮效果,https://github.com/yangchong211/YCBanner

關(guān)于其他內(nèi)容介紹

01.關(guān)于博客匯總鏈接
  • 1.技術(shù)博客匯總
  • 2.開源項(xiàng)目匯總
  • 3.生活博客匯總
  • 4.喜馬拉雅音頻匯總
  • 5.其他匯總
02.關(guān)于我的博客
  • 我的個(gè)人站點(diǎn):www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yczbj/activities
  • 簡(jiǎn)書:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
  • 開源中國(guó):https://my.oschina.net/zbj1618/blog
  • 泡在網(wǎng)上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 郵箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
  • 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI