有一张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
