## **Bitmap**的分析與使用
- Bitmap的創建
- 創建Bitmap的時候,Java不提供`new Bitmap()`的形式去創建,而是通過`BitmapFactory`中的靜態方法去創建,如:`BitmapFactory.decodeStream(is);//通過InputStream去解析生成Bitmap`(這里就不貼`BitmapFactory`中創建`Bitmap`的方法了,大家可以自己去看它的源碼),我們跟進`BitmapFactory`中創建`Bitmap`的源碼,最終都可以追溯到這幾個native函數
```
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對象,這個Java對象又是從native,也就是C/C++中產生的,所以,在Android中Bitmap的內存管理涉及到兩部分,一部分是*native*,另一部分是*dalvik*,也就是我們常說的java堆(如果對java堆與棧不了解的同學可以戳),到這里基本就已經了解了創建Bitmap的一些內存中的特性(大家可以使用``adb shell dumpsys meminfo``去查看Bitmap實例化之后的內存使用情況)。
- Bitmap的使用
- 我們已經知道了`BitmapFactory`是如何通過各種資源創建`Bitmap`了,那么我們如何合理的使用它呢?以下是幾個我們使用`Bitmap`需要關注的點
1. **Size**
- 這里我們來算一下,在Android中,如果采用`Config.ARGB_8888`的參數去創建一個`Bitmap`,[這是Google推薦的配置色彩參數](https://developer.android.com/reference/android/graphics/Bitmap.Config.html),也是Android4.4及以上版本默認創建Bitmap的Config參數(``Bitmap.Config.inPreferredConfig``的默認值),那么每一個像素將會占用4byte,如果一張手機照片的尺寸為1280×720,那么我們可以很容易的計算出這張圖片占用的內存大小為 1280x720x4 = 3686400(byte) = 3.5M,一張未經處理的照片就已經3.5M了! 顯而易見,在開發當中,這是我們最需要關注的問題,否則分分鐘OOM!
- *那么,我們一般是如何處理Size這個重要的因素的呢?*,當然是調整`Bitmap`的大小到適合的程度啦!辛虧在`BitmapFactory`中,我們可以很方便的通過`BitmapFactory.Options`中的`options.inSampleSize`去設置`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.
很簡潔明了啊!也就是說,只要按計算方法設置了這個參數,就可以完成我們Bitmap的Size調整了。那么,應該怎么調整姿勢才比較舒服呢?下面先介紹其中一種通過``InputStream``的方式去創建``Bitmap``的方法,上一段從Gallery中獲取照片并且將圖片Size調整到合適手機尺寸的代碼:
```
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 圖片實際的寬度,也就是options.outWidth
* @param actualHeight 圖片實際的高度,也就是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;
//這里我們為什么要尋找 與ratio最接近的2的倍數呢?
//原因就在于API中對于inSimpleSize的注釋:最終的inSimpleSize應該為2的倍數,我們應該向上取與壓縮比最接近的2的倍數。
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();
//當這個參數為true的時候,意味著你可以在解析時候不申請內存的情況下去獲取Bitmap的寬和高
//這是調整Bitmap Size一個很重要的參數設置
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;
//當你希望得到Bitmap實例的時候,不要忘了將這個參數設置為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``去創建`Bitmap`的這種方式,以及``BitmapFactory.Options.inSimpleSize`` 和 ``BitmapFactory.Options.inJustDecodeBounds``的使用方法,但將單個Bitmap加載到UI是簡單的,但是如果我們需要一次性加載大量的圖片,事情就會變得復雜起來。`Bitmap`是吃內存大戶,我們不希望多次解析相同的`Bitmap`,也不希望可能不會用到的`Bitmap`一直存在于內存中,所以,這個場景下,`Bitmap`的重用變得異常的重要。
*在這里只介紹一種``BitmapFactory.Options.inBitmap``的重用方式,下一篇文章會介紹使用三級緩存來實現Bitmap的重用。*
根據官方文檔[在Android 3.0 引進了BitmapFactory.Options.inBitmap](https://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inBitmap),如果這個值被設置了,decode方法會在加載內容的時候去重用已經存在的bitmap. 這意味著bitmap的內存是被重新利用的,這樣可以提升性能, 并且減少了內存的分配與回收。然而,使用inBitmap有一些限制。特別是在Android 4.4 之前,只支持同等大小的位圖。
我們看來看看這個參數最基本的運用方法。
```
new BitmapFactory.Options options = new BitmapFactory.Options();
//inBitmap只有當inMutable為true的時候是可用的。
options.inMutable = true;
Bitmap reusedBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.reused_btimap,options);
options.inBitmap = reusedBitmap;
```
這樣,當你在下一次decodeBitmap的時候,將設置了`options.inMutable=true`以及`options.inBitmap`的`Options`傳入,Android就會復用你的Bitmap了,具體實例:
```
@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只有當inMutable為true的時候是可用的。
options.inMutable = true;
BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options);
//壓縮Bitmap到我們希望的尺寸
//確保不會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分辨率的圖片,并且設置在`options.inBitmap`中,然后分別decode了同一張圖片,并且傳入了相同的`options`。最終只占用一份第一次解析`Bitmap`的內存。
3. **Recycle**
一定要記得及時回收Bitmap,否則如上分析,你的native以及dalvik的內存都會被一直占用著,最終導致OOM
```
// 先判斷是否已經回收
if(bitmap != null && !bitmap.isRecycled()){
// 回收并且置為null
bitmap.recycle();
bitmap = null;
}
System.gc();
```
- Enjoy Android :) 如果有誤,輕噴,歡迎指正。
- JavaSE(Java基礎)
- Java基礎知識
- Java中的內存泄漏
- String源碼分析
- Java集合結構
- ArrayList源碼剖析
- HashMap源碼剖析
- Hashtable簡介
- Vector源碼剖析
- LinkedHashMap簡介
- LinkedList簡介
- JVM(Java虛擬機)
- JVM基礎知識
- JVM類加載機制
- Java內存區域與內存溢出
- 垃圾回收算法
- Java并發(JavaConcurrent)
- Java并發基礎知識
- 生產者和消費者問題
- Thread和Runnable實現多線程的區別
- 線程中斷
- 守護線程與阻塞線程的情況
- Synchronized
- 多線程環境中安全使用集合API
- 實現內存可見的兩種方法比較:加鎖和volatile變量
- 死鎖
- 可重入內置鎖
- 使用wait/notify/notifyAll實現線程間通信
- NIO
- 數據結構(DataStructure)
- 數組
- 棧和隊列
- Algorithm(算法)
- 排序
- 選擇排序
- 冒泡排序
- 快速排序
- 歸并排序
- 查找
- 順序查找
- 折半查找
- Network(網絡)
- TCP/UDP
- HTTP
- Socket
- OperatingSystem(操作系統)
- Linux系統的IPC
- android中常用設計模式
- 面向對象六大原則
- 單例模式
- Builder模式
- 原型模式
- 簡單工廠
- 策略模式
- 責任鏈模式
- 觀察者模式
- 代理模式
- 適配器模式
- 外觀模式
- Android(安卓面試點)
- Android基礎知識
- Android內存泄漏總結
- Handler內存泄漏分析及解決
- Android性能優化
- ListView詳解
- RecyclerView和ListView的異同
- AsyncTask源碼分析
- 插件化技術
- 自定義控件
- ANR問題
- Art和Dalvik的區別
- Android關于OOM的解決方案
- Fragment
- SurfaceView
- Android幾種進程
- APP啟動過程
- 圖片三級緩存
- Bitmap的分析與使用
- 熱修復的原理
- AIDL
- Binder機制
- Zygote和System進程的啟動過程
- Android中的MVC,MVP和MVVM
- MVP
- Android開機過程
- EventBus用法詳解
- 查漏補缺
- Git操作