有一张64x64的图片,(手机为480dpi)我们先放在drawable-xhdpi目录下。
效果如下
同样的手机,我们把图片放到drawable-xxxhdpi目录下。
效果如下
图片很明显变小了。
BitmapFactory.Options options=new BitmapFactory.Options();
options.inDensity=320;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.zan1, options);
imageView.setImageBitmap(bitmap);
但是,如果我们更改inDensity的值,它又变回来了。
1、屏幕密度,DPI,PX,DP
1、屏幕密度
(Screen density)指的是屏幕里像素值浓度,分辨率/屏幕尺寸可以反映出手机密度。为了方便,Android将屏幕密度分为6种,low, medium, high, extra-high, extra-extra-high, and extra-extra-extra-high.
2、DPI
每英寸像素数,可以反映屏幕的清晰度,计算公式为屏幕对角线的像素点/对角线的长度(物理长度)
如 小米2s 屏幕分辨率为720px*1280px,4.3英寸。
dpi=√ (720^2 +1280^2) /4.3 = 342ppi。
3、PX
像素,电子屏幕上组成一幅图画或照片的最基本单元,如分辨率为720px*1280px,就是横向720个像素,纵向1280个像素。
4、DP
是安卓开发用的长度单位,1dp表示的是在每英寸像素数为160时1px的长度。
举个例子,在一个屏幕密度为480dpi的手机上。
1px=dp*(dpi/160)=3dp。
在屏幕密度为160dpi的手机上。
1px=dp*(dpi/160)=1dp。
所以Android一般用dp来作为单位,从而动态适配控件的大小,已达到最佳效果。
5、具体的数据
- ldpi (low) ~120dpi
- mdpi (medium) ~160dpi
- hdpi (high) ~240dpi
- xhdpi (extra-high) ~320dpi
- xxhdpi (extra-extra-high) ~480dpi
- xxxhdpi (extra-extra-extra-high) ~640dpi
2、BitmapFactory解析Bitmap的原理。
BitmapFactory提供了六种解析Bitmap的方法
- BitmapFactory.decodeResource();
- BitmapFactory.decodeByteArray();
- BitmapFactory.decodeFile();
- BitmapFactory.decodeStream();
- BitmapFactory.decodeFileDescriptor();
- BitmapFactory.decodeResourceStream();
分别从资源,字节数组,文件和流中加载一个Bitmap对象,而一般常用的是前四种。而,decodeFile和decodeResource以及decodeResourceStream最终又调用了decodeStream方法。
这些方法最终都是在Android的底层实现,对应着BitmapFactory的几个native方法。
这里面有一个值得注意的地方,decodeResource在调用decodeStream之前,多调用了一个decodeResourceStream 方法。根据当前设备屏幕像素密度densityDpi的值进行缩放适配操作
/**
* Decode a new Bitmap from an InputStream. This InputStream was obtained from
* resources, which we pass to be able to scale the bitmap accordingly.
如果这个输入流是从resource,我们可以调整bitmap适应的比例
*/
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
//像素密度--根据资源文件不同位置,值不同
final int density = value.density;
//如果是默认位置,比如drawable文件,则是160
if (density == TypedValue.DENSITY_DEFAULT) {
//160像素密度 标准 1dp=1px
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);
}
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
/
Bitmap bm = null;
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
bm = nativeDecodeAsset(asset, outPadding, opts);
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
setDensityFromOptions(bm, opts);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
return bm;
}
private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
if (outputBitmap == null || opts == null) return;
final int density = opts.inDensity;
if (density != 0) {
outputBitmap.setDensity(density);
final int targetDensity = opts.inTargetDensity;
if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
return;
}
byte[] np = outputBitmap.getNinePatchChunk();
final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
if (opts.inScaled || isNinePatch) {
outputBitmap.setDensity(targetDensity);
}
} else if (opts.inBitmap != null) {
// bitmap was reused, ensure density is reset
outputBitmap.setDensity(Bitmap.getDefaultDensity());
}
}
这段代码,给opts.inDensity 和opts.inTargetDensity进行赋值。
opts.inDensity=value.density,这个值是根据资源文件的容器动态改变的,比如在xxhdpi中 值为480,在xxxhdpi中值为640。
而opts.inTargetDensity=res.getDisplayMetrics().densityDpi,这个值则是固定的屏幕密度值,根据每台手机获取不同,但是不会变的值,是固定的。
在把值赋给options之后,调用native方法解析bitmap,而在解析完成之后,又调用了setDensityFromOptions,先把density赋值给Bitmap,然后判断targetDensity是否为0,density 是否等于targetDensity,density是否等于屏幕密度,如果满足,就不会把targetDensity设置给Bitmap,总之这段代码,通过inScaled,inDensity,inTargetDensity来给bitmap的inDensity参数经行赋值。
公式:输出图片的宽高= 原图片的宽高 / inSampleSize * (inTargetDensity / inDensity)
BitmapFactory.Options options=new BitmapFactory.Options();
options.inDensity=160;
options.inTargetDensity=480;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.zan1, options);
Log.d("AAA","width "+bitmap.getWidth()=192);
Log.d("AAA","height "+bitmap.getHeight())=192;
2、Bitmap的优化策略
这里主要用到BitmapFactory.options的inSampleSize的属性,也就是采样率。
举个例子,如一个ImageView的尺寸只有100x100,但是图片的大小为200x200,如果不经处理加载给imageView,是完全没必要的,我们可以将它缩小为100x100之后再加载给imageView,从而有效提高Bitmap加载时的性能。
inSampleSize默认为1,最小也是1,采样的大小跟原图一样大。Google文档中指出,inSampleSize的取值为2的指数,也就是1,2,4,8等。
如inSampleSize为2,图片的宽高就是一半也就是1/2,那图片的大小就缩小了1/4。
以ARGB888为例,一个像素占用4字节。一张1024x1024像素的图片占用内存为 1024x1024x4=4M。采样率为2时,就为512x512x4=1M。
直接上代码
public Bitmap decodeResource(Resources res,int resId,int rWidth,int rHeight){
BitmapFactory.Options options=new BitmapFactory.Options();
//为true,表示只获取参数信息,如宽高等,而不会把bitmap加载到内存.
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(getResources(),R.drawable.image,options);
//计算inSampleSize
options.inSampleSize= caculateInSampleSize(options,rWidth,rHeight);
options.inJustDecodeBounds=false;
return BitmapFactory.decodeResource(getResources(),R.drawable.image,options);
}
private int caculateInSampleSize(BitmapFactory.Options options, int rWidth, int rHeight) {
if(rWidth==1||rHeight==1){
return 1;
}
int inSampleSize=1;
int width = options.outWidth;
int height = options.outHeight;
if(width>rWidth||height>rHeight){
int halfWidth = width / 2;
int haflHeight = height / 2;
while ((halfWidth/inSampleSize)>=rWidth&&
(haflHeight/inSampleSize)>=rHeight){
inSampleSize*=2;
}
}
return inSampleSize;
}
最后,放上BitmapFactory.Options的属性解析,以备查阅
inBitmap——在解析Bitmap时重用该Bitmap,不过必须等大的Bitmap而且inMutable须为true
inMutable——配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段
inJustDecodeBounds——为true仅返回Bitmap的宽高等属性
inSampleSize——须>=1,表示Bitmap的压缩比例,如:inSampleSize=4,将返回一个是原始图的1/16大小的Bitmap
inPreferredConfig——Bitmap.Config.ARGB_8888等
inDither——是否抖动,默认为false
inPremultiplied——默认为true,一般不改变它的值
inDensity——Bitmap的像素密度
inTargetDensity——Bitmap最终的像素密度
inScreenDensity——当前屏幕的像素密度
inScaled——是否支持缩放,默认为true,当设置了这个,Bitmap将会以inTargetDensity的值进行缩放
inPurgeable——当存储Pixel的内存空间在系统内存不足时是否可以被回收
inInputShareable——inPurgeable为true情况下才生效,是否可以共享一个InputStream
inPreferQualityOverSpeed——为true则优先保证Bitmap质量其次是解码速度
outWidth——返回的Bitmap的宽
outHeight——返回的Bitmap的高
inTempStorage——解码时的临时空间,建议16*1024