1.bitmap佔多少內存
getByteCount()方法是在API12加入的,代表存儲Bitmap的色素需要的最少內存。API19開始getAllocationByteCount()方法代替了getByteCount()。
這是API26的
public final int getByteCount() {
if (mRecycled) {
Log.w(TAG, "Called getByteCount() on a recycle()'d bitmap! "
+ "This is undefined behavior!");
return 0;
}
// int result permits bitmaps up to 46,340 x 46,340
return getRowBytes() * getHeight();
}
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()的區別
一般情況下兩者是相等的;如果通過複用Bitmap來解碼圖片,如果被複用的Bitmap的內存比待分配內存的Bitmap大,那麼getByteCount()表示新解碼圖片佔用內存的大小(並非實際內存大小,實際大小是複用的那個Bitmap的大小),getAllocationByteCount()表示被複用Bitmap真實佔用的內存大小
2.如何計算Bitmap佔用的內存
通常情況下認爲
bitmap佔用的內存 = width * height * 一個像素所佔的內存。
下面是API26裏面的一個像素所佔的內存
public enum Config {
ALPHA_8 (1),//With this configuration, each pixel requires 1 byte of memory.
RGB_565 (3),//Each pixel is stored on 2 bytes
@Deprecated
ARGB_4444 (4),//Each pixel is stored on 2 bytes
ARGB_8888 (5),//Each pixel is stored on 4 bytes 如果沒有指明,默認這個
RGBA_F16 (6),//Each pixels is stored on 8 bytes 高色域高動態範圍圖像
HARDWARE (7);//stored only in graphic memory
}
通常用的是RGB_565 和 ARGB_8888(默認)
其實這個說法不全對,沒有說明場景,同時也忽略了一個影響項:Density。
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, 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);
}
因此,加載一張本地資源圖片,那麼它佔用的內存 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一個像素所佔的內存。
// 不做處理,默認縮放。
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.aaa, options);
Log.i(TAG, "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
Log.i(TAG, "width:" + bitmap.getWidth() + ":::height:" + bitmap.getHeight());
Log.i(TAG, "inDensity:" + options.inDensity + ":::inTargetDensity:" + options.inTargetDensity);
Log.i(TAG,"===========================================================================");
// 手動設置inDensity與inTargetDensity,影響縮放比例。
BitmapFactory.Options options_setParams = new BitmapFactory.Options();
options_setParams.inDensity = 320;
options_setParams.inTargetDensity = 160;
Bitmap bitmap_setParams = BitmapFactory.decodeResource(getResources(), R.mipmap.aaa, options_setParams);
Log.i(TAG, "bitmap_setParams:ByteCount = " + bitmap_setParams.getByteCount() + ":::bitmap_setParams:AllocationByteCount = " + bitmap_setParams.getAllocationByteCount());
Log.i(TAG, "width:" + bitmap_setParams.getWidth() + ":::height:" + bitmap_setParams.getHeight());
Log.i(TAG, "inDensity:" + options_setParams.inDensity + ":::inTargetDensity:" + options_setParams.inTargetDensity);
07-15 14:49:06.298 8496-8496/com.company.glidetest I/MainActivity: bitmap:ByteCount = 538800:::bitmap:AllocationByteCount = 538800
07-15 14:49:06.298 8496-8496/com.company.glidetest I/MainActivity: width:449:::height:300
07-15 14:49:06.298 8496-8496/com.company.glidetest I/MainActivity: inDensity:320:::inTargetDensity:320
07-15 14:49:06.298 8496-8496/com.company.glidetest I/MainActivity: ===========================================================================
07-15 14:49:06.318 8496-8496/com.company.glidetest I/MainActivity: bitmap_setParams:ByteCount = 135000:::bitmap_setParams:AllocationByteCount = 135000
07-15 14:49:06.318 8496-8496/com.company.glidetest I/MainActivity: width:225:::height:150
07-15 14:49:06.318 8496-8496/com.company.glidetest I/MainActivity: inDensity:320:::inTargetDensity:160
可以看出:
1.不使用Bitmap複用時,getByteCount()與getAllocationByteCount()的值是一致的;
2.默認情況下,華爲手機在xhdpi的文件夾下,inDensity爲320,inTargetDensity爲320,內存大小爲538800;而538800= 449* 300* 4。
3.手動設置inDensity與inTargetDensity,使其比例爲2,內存大小爲135000,而 449* 300* 0.5 * 0.5 * 4 = 134700。
3.Bitmap如何壓縮
inSampleSize
設置inSampleSize之後,Bitmap的寬、高都會縮小inSampleSize倍。例如:一張寬高爲2048x1536的圖片,設置inSampleSize爲4之後,實際加載到內存中的圖片寬高是512x384。佔有的內存就是0.75M而不是12M,足足節省了15倍。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
// 設置inJustDecodeBounds屬性爲true,只獲取Bitmap原始寬高,不分配內存;
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);// 這裏會找到最適合的inTargetDensity和inDensity
Log.i(TAG, "inTargetDensity = " + options.inTargetDensity);
Log.i(TAG, "inDensity = " + options.inDensity);
Log.i(TAG, "inPreferredConfig = " + options.inPreferredConfig);
// 計算inSampleSize值;
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 真實加載Bitmap;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
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;
// 寬和高比需要的寬高大的前提下最大的inSampleSize
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
Log.i(TAG, "inSampleSize = " + inSampleSize);
return inSampleSize;
}
測試代碼及打印結果
Bitmap bitmap1 = decodeSampledBitmapFromResource(getResources(), R.mipmap.aaa, 100, 100);
Log.i(TAG, "bitmap1 = " + bitmap1.getAllocationByteCount());
07-15 15:06:28.988 23977-23977/com.company.glidetest I/MainActivity: inTargetDensity = 320
07-15 15:06:28.988 23977-23977/com.company.glidetest I/MainActivity: inDensity = 320
07-15 15:06:28.988 23977-23977/com.company.glidetest I/MainActivity: inPreferredConfig = ARGB_8888
07-15 15:06:28.988 23977-23977/com.company.glidetest I/MainActivity: inSampleSize = 2
07-15 15:06:28.998 23977-23977/com.company.glidetest I/MainActivity: bitmap1 = 135000
還是之前的圖片(449* 300)正式算的時候應該是把449向上取整數來算的 450/2* 300/2* 1 * 1 * 4 = 135000
(由此可見其實還和inSampleSize有關)
4.Bitmap如何複用
(1)使用LruCache和DiskLruCache做內存和磁盤緩存;
(2)使用Bitmap複用,同時針對版本進行兼容。
BitmapFactory.Options options = new BitmapFactory.Options();
// 圖片複用,這個屬性必須設置;
options.inMutable = true;
// 手動設置縮放比例,使其取整數,方便計算、觀察數據;
options.inDensity = 320;
options.inTargetDensity = 320;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.aaa, options);
// 使用inBitmap屬性,這個屬性必須設置;
options.inBitmap = bitmap;
options.inDensity = 320;
// 設置縮放寬高爲原始寬高一半;
options.inTargetDensity = 160;
options.inMutable = true;
Bitmap bitmapReuse = BitmapFactory.decodeResource(getResources(), R.mipmap.aaa, options);
// 複用對象的內存地址;
Log.i(TAG, "bitmap = " + bitmap);
Log.i(TAG, "bitmapReuse = " + bitmapReuse);
Log.i(TAG, "bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
Log.i(TAG, "bitmapReuse:ByteCount = " + bitmapReuse.getByteCount() + ":::bitmapReuse:AllocationByteCount = " + bitmapReuse.getAllocationByteCount());
運行結果
07-15 15:24:40.038 8463-8463/com.company.glidetest I/MainActivity: bitmap = android.graphics.Bitmap@20cffbbc
07-15 15:24:40.038 8463-8463/com.company.glidetest I/MainActivity: bitmapReuse = android.graphics.Bitmap@20cffbbc
07-15 15:24:40.038 8463-8463/com.company.glidetest I/MainActivity: bitmap:AllocationByteCount = 538800
07-15 15:24:40.038 8463-8463/com.company.glidetest I/MainActivity: bitmapReuse:ByteCount = 135000:::bitmapReuse:AllocationByteCount = 538800
可以看到,共用同一塊內存,複用的bitmap因爲inTargetDensity 和 inDensity的原因大約佔到了四分之一的內存(全部分配給了它,但是隻用了四分之一)