Bitmap的分析與使用

Bitmap的分析與使用

  • Bitmap的創(chuàng)建

    • 創(chuàng)建Bitmap的時(shí)候凹髓,Java不提供new Bitmap()的形式去創(chuàng)建欺旧,而是通過BitmapFactory中的靜態(tài)方法去創(chuàng)建,如:BitmapFactory.decodeStream(is);//通過InputStream去解析生成Bitmap(這里就不貼BitmapFactory中創(chuàng)建Bitmap的方法了,大家可以自己去看它的源碼)窄瘟,我們跟進(jìn)BitmapFactory中創(chuàng)建Bitmap的源碼跷坝,最終都可以追溯到這幾個(gè)native函數(shù)
        private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
                    Rect padding, Options opts);
        private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
                Rect padding, Options opts);
        private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
        private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
                int length, Options opts);
    

    Bitmap又是Java對象侣诵,這個(gè)Java對象又是從native,也就是C/C++中產(chǎn)生的边苹,所以陵且,在Android中Bitmap的內(nèi)存管理涉及到兩部分,一部分是native个束,另一部分是dalvik慕购,也就是我們常說的java堆(如果對java堆與棧不了解的同學(xué)可以戳),到這里基本就已經(jīng)了解了創(chuàng)建Bitmap的一些內(nèi)存中的特性(大家可以使用adb shell dumpsys meminfo去查看Bitmap實(shí)例化之后的內(nèi)存使用情況)茬底。

  • Bitmap的使用

    • 我們已經(jīng)知道了BitmapFactory是如何通過各種資源創(chuàng)建Bitmap了沪悲,那么我們?nèi)绾魏侠淼氖褂盟兀恳韵率菐讉€(gè)我們使用Bitmap需要關(guān)注的點(diǎn)
      1. Size

        • 這里我們來算一下阱表,在Android中殿如,如果采用Config.ARGB_8888的參數(shù)去創(chuàng)建一個(gè)Bitmap這是Google推薦的配置色彩參數(shù)最爬,也是Android4.4及以上版本默認(rèn)創(chuàng)建Bitmap的Config參數(shù)(Bitmap.Config.inPreferredConfig的默認(rèn)值)涉馁,那么每一個(gè)像素將會(huì)占用4byte,如果一張手機(jī)照片的尺寸為1280×720爱致,那么我們可以很容易的計(jì)算出這張圖片占用的內(nèi)存大小為 1280x720x4 = 3686400(byte) = 3.5M烤送,一張未經(jīng)處理的照片就已經(jīng)3.5M了! 顯而易見,在開發(fā)當(dāng)中蒜鸡,這是我們最需要關(guān)注的問題胯努,否則分分鐘OOM!
        • 那么,我們一般是如何處理Size這個(gè)重要的因素的呢逢防?叶沛,當(dāng)然是調(diào)整Bitmap的大小到適合的程度啦!辛虧在BitmapFactory中忘朝,我們可以很方便的通過BitmapFactory.Options中的options.inSampleSize去設(shè)置Bitmap的壓縮比灰署,官方給出的說法是

        If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory....For example, inSampleSize == 4 returns
        an image that is 1/4 the width/height of the original, and 1/16 the
        number of pixels. Any value <= 1 is treated the same as 1.

        很簡潔明了啊局嘁!也就是說溉箕,只要按計(jì)算方法設(shè)置了這個(gè)參數(shù),就可以完成我們Bitmap的Size調(diào)整了悦昵。那么肴茄,應(yīng)該怎么調(diào)整姿勢才比較舒服呢?下面先介紹其中一種通過InputStream的方式去創(chuàng)建Bitmap的方法但指,上一段從Gallery中獲取照片并且將圖片Size調(diào)整到合適手機(jī)尺寸的代碼:

          static final int PICK_PICS = 9;
          
          public void startGallery(){
              Intent i = new Intent();
              i.setAction(Intent.ACTION_PICK);
              i.setType("image/*");
              startActivityForResult(i,PICK_PICS);
          }
          
           private int[] getScreenWithAndHeight(){
              WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
              DisplayMetrics dm = new DisplayMetrics();
              wm.getDefaultDisplay().getMetrics(dm);
              return new int[]{dm.widthPixels,dm.heightPixels};
          }
          
          /**
           *
           * @param actualWidth 圖片實(shí)際的寬度寡痰,也就是options.outWidth
           * @param actualHeight 圖片實(shí)際的高度抗楔,也就是options.outHeight
           * @param desiredWidth 你希望圖片壓縮成為的目的寬度
           * @param desiredHeight 你希望圖片壓縮成為的目的高度
           * @return
           */
          private int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
              double wr = (double) actualWidth / desiredWidth;
              double hr = (double) actualHeight / desiredHeight;
              double ratio = Math.min(wr, hr);
              float n = 1.0f;
              //這里我們?yōu)槭裁匆獙ふ?與ratio最接近的2的倍數(shù)呢?
              //原因就在于API中對于inSimpleSize的注釋:最終的inSimpleSize應(yīng)該為2的倍數(shù)拦坠,我們應(yīng)該向上取與壓縮比最接近的2的倍數(shù)连躏。
              while ((n * 2) <= ratio) {
                  n *= 2;
              }
      
              return (int) n;
          }
      
          @Override
          protected void onActivityResult(int requestCode, int resultCode, Intent data) {
              if(resultCode == RESULT_OK){
                  switch (requestCode){
                      case PICK_PICS:
                          Uri uri = data.getData();
                          InputStream is = null;
                          try {
                              is = getContentResolver().openInputStream(uri);
                          } catch (FileNotFoundException e) {
                              e.printStackTrace();
                          }
      
                          BitmapFactory.Options options = new BitmapFactory.Options();
                          //當(dāng)這個(gè)參數(shù)為true的時(shí)候,意味著你可以在解析時(shí)候不申請內(nèi)存的情況下去獲取Bitmap的寬和高
                          //這是調(diào)整Bitmap Size一個(gè)很重要的參數(shù)設(shè)置
                          options.inJustDecodeBounds = true;
                          BitmapFactory.decodeStream( is,null,options );
      
                          int realHeight = options.outHeight;
                          int realWidth = options.outWidth;
      
                          int screenWidth = getScreenWithAndHeight()[0];
                          
                          int simpleSize = findBestSampleSize(realWidth,realHeight,screenWidth,300);
                          options.inSampleSize = simpleSize;
                          //當(dāng)你希望得到Bitmap實(shí)例的時(shí)候,不要忘了將這個(gè)參數(shù)設(shè)置為false
                          options.inJustDecodeBounds = false;
      
                          try {
                              is = getContentResolver().openInputStream(uri);
                          } catch (FileNotFoundException e) {
                              e.printStackTrace();
                          }
      
                          Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);
      
                          iv.setImageBitmap(bitmap);
      
                          try {
                              is.close();
                              is = null;
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
      
                          break;
                  }
              }
              super.onActivityResult(requestCode, resultCode, data);
          }
      

    我們來看看這段代碼的功效:
    壓縮前:

    壓縮前

    壓縮后:
    壓縮后

    對比條件為:1080P的魅族Note3拍攝的高清無碼照片

     2. **Reuse**
     上面介紹了``BitmapFactory``通過``InputStream``去創(chuàng)建`Bitmap`的這種方式贞滨,以及``BitmapFactory.Options.inSimpleSize`` 和 ``BitmapFactory.Options.inJustDecodeBounds``的使用方法入热,但將單個(gè)Bitmap加載到UI是簡單的,但是如果我們需要一次性加載大量的圖片晓铆,事情就會(huì)變得復(fù)雜起來勺良。`Bitmap`是吃內(nèi)存大戶,我們不希望多次解析相同的`Bitmap`尤蒿,也不希望可能不會(huì)用到的`Bitmap`一直存在于內(nèi)存中郑气,所以,這個(gè)場景下腰池,`Bitmap`的重用變得異常的重要尾组。
     *在這里只介紹一種``BitmapFactory.Options.inBitmap``的重用方式,下一篇文章會(huì)介紹使用三級緩存來實(shí)現(xiàn)Bitmap的重用示弓。*
     
           根據(jù)官方文檔[在Android 3.0 引進(jìn)了BitmapFactory.Options.inBitmap](https://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inBitmap)讳侨,如果這個(gè)值被設(shè)置了,decode方法會(huì)在加載內(nèi)容的時(shí)候去重用已經(jīng)存在的bitmap. 這意味著bitmap的內(nèi)存是被重新利用的奏属,這樣可以提升性能, 并且減少了內(nèi)存的分配與回收跨跨。然而,使用inBitmap有一些限制囱皿。特別是在Android 4.4 之前勇婴,只支持同等大小的位圖。
         我們看來看看這個(gè)參數(shù)最基本的運(yùn)用方法嘱腥。
         
         ```
         new BitmapFactory.Options options = new BitmapFactory.Options();
         //inBitmap只有當(dāng)inMutable為true的時(shí)候是可用的耕渴。
         options.inMutable = true;
         Bitmap reusedBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.reused_btimap,options);
         options.inBitmap = reusedBitmap;
         ```
         
           這樣,當(dāng)你在下一次decodeBitmap的時(shí)候齿兔,將設(shè)置了`options.inMutable=true`以及`options.inBitmap`的`Options`傳入橱脸,Android就會(huì)復(fù)用你的Bitmap了,具體實(shí)例:
         
         ```
         @Override
         protected void onCreate(@Nullable Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(reuseBitmap());
         }
    
         private LinearLayout reuseBitmap(){
             LinearLayout linearLayout = new LinearLayout(this);
             linearLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
             linearLayout.setOrientation(LinearLayout.VERTICAL);
     
             ImageView iv = new ImageView(this);
             iv.setLayoutParams(new ViewGroup.LayoutParams(500,300));
     
             options = new BitmapFactory.Options();
             options.inJustDecodeBounds = true;
             //inBitmap只有當(dāng)inMutable為true的時(shí)候是可用的分苇。
             options.inMutable = true;
             BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options);
             
             //壓縮Bitmap到我們希望的尺寸
             //確保不會(huì)OOM
             options.inSampleSize = findBestSampleSize(options.outWidth,options.outHeight,500,300);
             options.inJustDecodeBounds = false;
     
             Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options);
             options.inBitmap = bitmap;
     
             iv.setImageBitmap(bitmap);
     
             linearLayout.addView(iv);
     
             ImageView iv1 = new ImageView(this);
             iv1.setLayoutParams(new ViewGroup.LayoutParams(500,300));
             iv1.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));
             linearLayout.addView(iv1);
     
             ImageView iv2 = new ImageView(this);
             iv2.setLayoutParams(new ViewGroup.LayoutParams(500,300));
             iv2.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));
             linearLayout.addView(iv2);
     
     
             return linearLayout;
         }
         ```
         
          以上代碼中添诉,我們在解析了一次一張1080P分辨率的圖片,并且設(shè)置在`options.inBitmap`中医寿,然后分別decode了同一張圖片栏赴,并且傳入了相同的`options`。最終只占用一份第一次解析`Bitmap`的內(nèi)存靖秩。
         
     3. **Recycle**
     一定要記得及時(shí)回收Bitmap须眷,否則如上分析乌叶,你的native以及dalvik的內(nèi)存都會(huì)被一直占用著,最終導(dǎo)致OOM
     
     
     ```
     // 先判斷是否已經(jīng)回收
     if(bitmap != null && !bitmap.isRecycled()){
         // 回收并且置為null
         bitmap.recycle();
         bitmap = null;
     }
     System.gc();
     ```
    
    • Enjoy Android :) 如果有誤柒爸,輕噴,歡迎指正事扭。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捎稚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子求橄,更是在濱河造成了極大的恐慌今野,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罐农,死亡現(xiàn)場離奇詭異条霜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涵亏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門宰睡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人气筋,你說我怎么就攤上這事拆内。” “怎么了宠默?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵麸恍,是天一觀的道長。 經(jīng)常有香客問我搀矫,道長抹沪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任瓤球,我火速辦了婚禮融欧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冰垄。我一直安慰自己蹬癌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布虹茶。 她就那樣靜靜地躺著逝薪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蝴罪。 梳的紋絲不亂的頭發(fā)上董济,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音要门,去河邊找鬼虏肾。 笑死廓啊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的封豪。 我是一名探鬼主播谴轮,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吹埠!你這毒婦竟也來了第步?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缘琅,失蹤者是張志新(化名)和其女友劉穎粘都,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刷袍,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翩隧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呻纹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堆生。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雷酪,靈堂內(nèi)的尸體忽然破棺而出顽频,到底是詐尸還是另有隱情,我是刑警寧澤太闺,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布糯景,位于F島的核電站,受9級特大地震影響省骂,放射性物質(zhì)發(fā)生泄漏蟀淮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一钞澳、第九天 我趴在偏房一處隱蔽的房頂上張望怠惶。 院中可真熱鬧,春花似錦轧粟、人聲如沸策治。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽通惫。三九已至,卻和暖如春混蔼,著一層夾襖步出監(jiān)牢的瞬間履腋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遵湖,地道東北人悔政。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像延旧,于是被迫代替她去往敵國和親谋国。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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