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)存使用情況)茬底。 - 創(chuàng)建Bitmap的時(shí)候凹髓,Java不提供
-
Bitmap的使用
- 我們已經(jīng)知道了
BitmapFactory
是如何通過各種資源創(chuàng)建Bitmap
了沪悲,那么我們?nèi)绾魏侠淼氖褂盟兀恳韵率菐讉€(gè)我們使用Bitmap
需要關(guān)注的點(diǎn)-
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ī)尺寸的代碼: - 這里我們來算一下阱表,在Android中殿如,如果采用
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 :) 如果有誤柒爸,輕噴,歡迎指正事扭。
- 我們已經(jīng)知道了