Android多分辨率适配原理
Android常用度量单位:
- px:是Pixel的缩写,也就是说像素
- inch:是指英寸,设备对角线的长度
- dpi:它表示每英寸上的像素点个数,也就是屏幕密度。例如手机分辨率为1920*1080,先利用勾股定理得其对角线的像素值为2202.91,再除以对角线的长度5,计算出440.582便是该设备的屏幕密度dpi。
- dp:android中常用的使用单位,不要与dpi混淆,下面会详细介绍dp
ldpi、mdpi、hdpi、xhdpi、xxhdpi 这在android开发中非常常见,android根据dpi把设备分成多个级别,详细关系见下图:
可以通过代码获取设备dpi:
Resources resources=getResources(); DisplayMetrics displayMetrics = resources.getDisplayMetrics(); float density = displayMetrics.density; int dpi = displayMetrics.densityDpi;
假设当前设备dpi为480,density就为3。density是一个倍数关系,是当前设备dpi除以160所得。当设备dpi为160时,1px=1dp,所以dpi为480时 1dp=3px
图片加载:
通常为了适配以及防止图片失真,会在mdpi,xhdpi,xxhdpi这类文件夹下分别放入对应的尺寸的图。那么为什么需要这样放,如果把本应该放入xxhdpi的图放入mdpi会发生什么呢
经过测试会发现 假设你的手机显示类别为xxhdpi,图片尺寸100*100.把它放入xxhdpi文件中,然后通过下面代码获取图片宽高:
BitmapDrawable bitmapDrawable = (BitmapDrawable) imageview.getDrawable(); if (null != bitmapDrawable) { Bitmap bitmap = bitmapDrawable.getBitmap(); int width = bitmap.getWidth(); int height = bitmap.getHeight(); }
打印宽高会发现和图片原始尺寸一下是100*100.然后把图片移到mdpi中,运行后会发现图片被放大了3倍,变成了300*300。
从图片加载源码中找找关系,首先来看BitmapFactory中的的decodeResource()方法:
public static Bitmap decodeResource(Resources res, int id, Options opts) { Bitmap bm = null; InputStream is = null; try { final TypedValue value = new TypedValue(); is = res.openRawResource(id, value); bm = decodeResourceStream(res, value, is, null, opts); } catch (Exception e) { } finally { try { if (is != null) is.close(); } catch (IOException e) { } } if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } return bm;}
typevalue保存了和当前设备dpi最接近的图片所在文件夹的destinydpi。比如图片在xxhdpi中 desinydpi就是480.接着看decodeResourceStream方法:
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; 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);}
value.density被赋值给opts.inDensity,保存了图片所在文件夹的dpi 如果放在xxhdpi中 那就是480
opts.inTargetDensity = res.getDisplayMetrics().densityDpi 可以看出inTargetDensity保存的当前设备的dpi。
接着往下看decodeStream方法:
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { // we don't throw in this case, thus allowing the caller to only check // the cache, and not force the image to be decoded. if (is == null) { return null; } Bitmap bm = null; Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); 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; }
在该方法中会调用decodeStreamInternal();它又会继续调用nativeDecodeStream( ),该方法是native的;在BitmapFactory.cpp可见这个方法内部又调用了doDecode()它的核心源码如下:
static jobject doDecode(JNIEnv*env,SkStreamRewindable*stream,jobject padding,jobject options) {...... if (density != 0 && targetDensity != 0 && density != screenDensity) { scale = (float) targetDensity / density; }......if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {scaledWidth = int(scaledWidth * scale + 0.5f);scaledHeight = int(scaledHeight * scale + 0.5f);}if (willScale) {const float sx = scaledWidth / float(decodingBitmap.width());const float sy = scaledHeight / float(decodingBitmap.height());......SkPaint paint;SkCanvas canvas(*outputBitmap);canvas.scale(sx, sy);canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);}......}
可以看到缩放比scale就等于opts.inTargetDensity/opts.inDensity,也就是屏幕dpi除以图片所在文件夹dpi,所以如果把原本在xxhdpi(480)文件夹中图片误放到mdpi(160)中 opts.inTargetDensity/opts.inDensity值就会增大,所以图片被放大到3倍。
正因为会自动选择缩放图片 我们只在xxhdpi中放了一套图 使用mdpi的手机来显示图片就会自动把图片缩小。所以现在很多开发者为了缩小apk大小会选择只在最大众的文件夹中放入一套图 比如xxhdpi。
如果不想让图片被缩放 可以试试把图片放到drawable-nodpi文件夹中哦。
更多相关文章
- Android(安卓)LayoutInflater原理解析
- Android官方架构组件LiveData: 观察者模式领域二三事
- Android(安卓)UnitTest
- Android(安卓)开源框架Universal-Image-Loader完全解析(一)--- 基
- 某android平板项目开发笔记--自定义sharepreference UI
- Android(安卓)小米盒子游戏手柄按键捕获 - 能获取到的 home 键依
- 深入探索 Android(安卓)内存优化(炼狱级别)
- [Unity3D]Unity3D游戏开发之Unity与Android交互调用研究
- Android(安卓)程序之在线词典[2010-05-08更新图片]