BitMap加載後,佔用內存大小:
width * height *每個像素佔的字節數
代碼中獲取bitmap佔用內存大小的計算:
public final int getByteCount() @Bitmap.java{
return getRowBytes() * getHeight();
}
其中的getRowBytes()調用的是native方法,具體在android源碼中就是SkBitmap.cpp中的實現。
SkBitmap.h
/** Returns row bytes, the interval from one pixel row to the next. Row bytes
is at least as large as width() * info().bytesPerPixel().
Returns zero if colorType() is kUnknown_SkColorType, or if row bytes supplied to
setInfo() is not large enough to hold a row of pixels.
@return byte length of pixel row
*/
size_t rowBytes() const { return fPixmap.rowBytes(); }
常見的顏色類型kRGB_565_SkColorType,一個像素佔2個字節,
kRGBA_8888_SkColorType 一個像素佔4個字節。
佔用內存跟容器格式也就是文件後綴名沒有關係,但是跟文件放在drawable的那個目錄有關係,前提同一張圖片,同時只在一個drawable目錄存在。
一張477*550的圖片,放在drawable-xhdpi中,
如代碼:
public void decodeBitmap() {
Bitmap jpgFile = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.pngfile);
Log.d(TAG,"jpgfile,width="+jpgFile.getWidth()+",jpgfile,height="+jpgFile.getHeight()
+",jpgfile,byteCount="+jpgFile.getByteCount()+",,,="+jpgFile.getDensity());
Bitmap bmpFile = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.bmpfile);
Log.d(TAG,"pngfile,width="+bmpFile.getWidth()+",pngfile,height="+bmpFile.getHeight()
+",pngfile,byteCount="+bmpFile.getByteCount());
Bitmap pngFile = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.jpgfile);
Log.d(TAG,"pngfile,width="+pngFile.getWidth()+",pngfile,height="+pngFile.getHeight()
+",pngfile,byteCount="+pngFile.getByteCount()+",,,="+pngFile.getDensity());
}
/*output*/
2020-01-02 09:18:10.967 9850-9850/? D/com.jlq.mainthreaddemo.DecodeBitmap: jpgfile,width=477,jpgfile,height=550,jpgfile,byteCount=1049400,,,=320
2020-01-02 09:18:10.986 9850-9850/? D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=477,pngfile,height=550,pngfile,byteCount=1049400
2020-01-02 09:18:11.000 9850-9850/? D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=477,pngfile,height=550,pngfile,byteCount=1049400,,,=320
把那三張圖片放在drawable-xxhdpi中,會縮小圖片
/*output*/
2020-01-02 09:31:07.659 10130-10130/? D/com.jlq.mainthreaddemo.DecodeBitmap: jpgfile,width=318,jpgfile,height=367,jpgfile,byteCount=466824,,,=320
2020-01-02 09:31:07.680 10130-10130/? D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=318,pngfile,height=367,pngfile,byteCount=466824
2020-01-02 09:31:07.696 10130-10130/? D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=318,pngfile,height=367,pngfile,byteCount=466824,,,=320
放在drawable-hdpi中,會放大圖片。
/*output*/
2020-01-02 09:35:23.787 10252-10252/com.jlq.mainthreaddemo D/com.jlq.mainthreaddemo.DecodeBitmap: jpgfile,width=636,jpgfile,height=733,jpgfile,byteCount=1864752,,,=320
2020-01-02 09:35:23.821 10252-10252/com.jlq.mainthreaddemo D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=636,pngfile,height=733,pngfile,byteCount=1864752
2020-01-02 09:35:23.850 10252-10252/com.jlq.mainthreaddemo D/com.jlq.mainthreaddemo.DecodeBitmap: pngfile,width=636,pngfile,height=733,pngfile,byteCount=1864752,,,=320
獲取當前設備的屏幕密度:
public void getDisplayMetrics() {
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
mDensity = dm.density;
mDensityDpi = dm.densityDpi;
TypedValue tv = new TypedValue();
Log.d(TAG,"mDensity="+mDensity+",mDensityDpi="+mDensityDpi);
}
2020-01-02 09:35:23.747 10252-10252/com.jlq.mainthreaddemo D/com.jlq.mainthreaddemo.DecodeBitmap: mDensity=2.0,mDensityDpi=320
跟當前設備匹配的資源目錄是drawable-xhdpi,對應屏幕密度是320dpi。
設備常用的屏幕密度:
-
ldpi(低): ~120 dpi 3
-
mdpi(中):120~160dpi
-
hdpi(高):160~240dpi
-
xhdpi(超高):240~320dpi
-
xxhdpi(超超高):320~480dpi
-
xxxhdpi(超超超高):480~640dpi
通過上面的測試,圖片放的位置如果跟當前的屏幕密度不一致,會對圖片做縮放。具體的縮放算法:
實際加載後的圖片尺寸 = (原圖片的尺寸 * 目標設備的屏幕密度) / 圖片放置目錄的dpi等級。
依據這個公式可以算出,在把圖片放置在drawable-xxhdpi中時,實際的圖片寬度 = (477(原圖寬度)* 320(屏幕密度))/480 (xxhdpi的等級) 。結果是318,跟代碼輸入是一致的。
下面分析下具體代碼實現是不是這樣的。
第一步,從decodeResource開始看起,
//僅關心跟圖片縮放相關代碼
public static Bitmap decodeResource(Resources res, int id, Options opts) @BitmapFactory.java{
validate(opts);
Bitmap bm = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
//從文件讀取圖片,轉成輸入流,這裏會解析圖片放置目錄的dpi等級,並以出參value返回
is = res.openRawResource(id, value);
//根據解析出的圖片目錄的dpi等級,及設備密度,做縮放
bm = decodeResourceStream(res, value, is, null, opts);
}
return bm;
}
第二步,跟蹤openRawResource的執行,跳過參數透傳,看AssetManager.java中的實現。
//從參數 TypedValue outValue的名字有out前綴,可以看出,這是一個出參,也就是說BitmapFactory中創建
//的TypedValue對象,只是一個空殼,裏面的實際參數值,會在接下來的解析中去填充。
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs)@AssetManager.java {
synchronized (this) {
final int cookie = nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
// Convert the changing configurations flags populated by native code.
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
}
return true;
}
}
調到jni層
//解析資源是通過AssetManager2來執行的
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
jshort density, jobject typed_value,
jboolean resolve_references) @android_util_AssetManager.cpp{
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
Res_value value;
ResTable_config selected_config;
uint32_t flags;
ApkAssetsCookie cookie =
assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
static_cast<uint16_t>(density), &value, &selected_config, &flags);
}
先用鎖裝飾了AAssetManager,實際完成解析的還是AAssetManager2中的實現,也就是AssetManager2.cpp中的
ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
uint16_t density_override, Res_value* out_value,
ResTable_config* out_selected_config,
uint32_t* out_flags) const {}
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
bool /*stop_at_first_match*/,
FindEntryResult* out_entry) const {}
其中的結構體,ResTable_config,對應了資源的類別。
對資源的解析,需要後續在做補充。。。
解析的圖片所在目錄的dpi等級,會作用到BitmapFactory.Options中,
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) {
//這裏value.density的值,會隨着圖片放置的目錄變化,如log打印
final int density = value.density;
Log.e("BitmapFactory", "decodeResourceStream:" + 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);
}
把圖片分別放在hdpi,xxhdpi時:
2020-01-20 16:59:45.739 3723-3723/com.jlq.mainthreaddemo E/BitmapFactory: decodeResourceStream:240
2020-01-20 17:00:33.697 3806-3806/? E/BitmapFactory: decodeResourceStream:480
第三步,把元數據轉成流後,接下來就是從流解碼成bitmap,並做縮放。
具體從BitmapFactory.java開始
public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
@Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {}
轉到native代碼;
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
jobject padding, jobject options)@BitmapFactory.cpp {}
static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
jobject padding, jobject options) @BitmapFactory.cpp{
// Update with options supplied by the client.
if (options != NULL) {
sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
// Correct a non-positive sampleSize. sampleSize defaults to zero within the
// options object, which is strange.
if (sampleSize <= 0) {
sampleSize = 1;
}
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
const int density = env->GetIntField(options, gOptions_densityFieldID);
const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
if (density != 0 && targetDensity != 0 && density != screenDensity) {
//這裏計算出縮放因子
scale = (float) targetDensity / density;
}
}
}
//對圖片做縮放
// Scale is necessary due to density differences.
if (scale != 1.0f) {
willScale = true;
scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
}
}