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);
}
}