资源
资源的种类#####
Android将资源分成了以下几类,每一类资源都需要放置在指定的位置。
- 动画资源。放置在res/anim文件夹,引用格式:R.anim.xx.
- 颜色资源。放置在res/color文件夹,引用格式.R.color.xx。
- 图片资源。放置在res/drawable文件夹,引用格式.R.drawable.xx。
- 布局资源。放置在res/layout文件夹,引用格式R.layout.xx。
- 菜单资源。放置在res/menu文件夹,引用格式R.menu.xx.
- 字符资源。放置在res/values文件夹下面,引用格式R.string.xx,R.array.xx,R.plurals.
- 样式资源。放置在res/values文件夹,引用格式R.style.xx.
- 其他值资源。例如booleans,integers..。也放置在res/values文件夹,引用格式R.bool.xx,R.integer.xx,根据相应的值类型格式引用该资源。
资源匹配规则和优先级#####
资源匹配规则######
Android提供了资源匹配机制,支持多种配置,以及不同国家的Android设备。具体的匹配方式是这样子的:
在res文件夹下建立支持的具体配置命名的文件夹,命名规则:资源类型-配置后缀-配置后缀2..。配置可以叠加,意思就是同时符合所有配置才使用该资源,编写时需要按照优先级排列。例如为hdpi密度的设备提供相应的图片资源,那么需要新建drawable-hdpi文件夹,将图片资源放置在该文件夹下面。如果需要支持一个hdpi密度且是屏幕方向是横向的设备,那么需要新建drawable-land-hdpi文件夹。
配置后缀表&优先级######
下面是常用的配置和相应的匹配后缀以及优先级(优先根据顺序排列,越上面优先级越高):
配置 | 例子 | 描述 |
---|---|---|
MCC and MNC | Examples:mcc310 | 电话国家码 |
Language and region | Examples:en | 国家标识 |
smallestWidth | sw |
支持的最小宽度 |
Screen size | small,normal,large,xlarge | 屏幕大小 |
Screen orientation | port,land | 屏幕方向 |
Night mode | night,notnight | 是否夜间模式,通过UiManager类改变 |
Screen pixel density (dpi) | ldpi,mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi,nodpi,tvdpi,anydpi | 屏幕密度 |
Platform Version (API level) | Examples:v3 | 系统版本 |
图片资源#####
单位计算公式#####
px = dp * (dpi / 160),其中dpi是设备的密度。
屏幕配置类别######
图片资源与目标设备屏幕息息相关,为了提高适配率,我们应该为不同屏幕配置提供不同的图片资源。下面是屏幕配置的类别。
- 尺寸。有small,normal,large,xlarge这几种。下面是尺寸表:
参数 | 描述 |
---|---|
xlarge | 至少960dp x 720dp |
large | 至少 640dp x 480dp |
normal | 至少470dp x 320dp |
small | 至少426dp x 320dp |
- 密度。主要有 nodpi,ldpi,mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi,nodpi。下面是具体的密度表:
参数 | 描述 |
---|---|
nodpi | 适配任何dpi。资源加载的时候,不做缩放等处理 |
ldpi | 至少120dpi |
mdpi | 至少160dpi,这是基准dpi,res/drawable默认就是mdpi |
hdpi | 至少240dpi |
xhdpi | 至少320dpi |
xxhdpi | 至少480dpi |
xxxhdpi | 至少640dpi |
- 方向。有竖屏和横屏。
图片资源的加载过程#####
资源的加载涉及到哪些类?######
资源的加载有两个关键类:Resources、AssetManager。AssetManager获取资源文件流,Resources提供获取不同资源的接口。下面是涉及资源加载的主要类:
- Resources。直接面向应用层提供获取不同资源的接口。
- AssetManager。低级API,通过JNI方式获取资源文件流。
- TypeValue。放置资源信息。
- ConstantState。主要用于Drawable资源,通过ConstanState可以获取Drawable实例,ConstantState的最终实现类放在具体Drawable类的内部。例如 BitmapDrawable的ConstantState就是其内部类BitmapState。
图片资源的加载过程######
我们知道,适配不同屏幕需要将图片资源放置在不同dpi文件夹里。那么为什么要这么放?系统又是怎么选择正确dpi文件夹里图片?下面以Android-23为例子解析整个过程。
首先,通过下面语句就可以获得图片资源:
Drawable drawable = getResources().getDrawable(R.drawable.ic_blue_statistics);
实际调用的是Resources().getDrawable(int id,Theme theme);
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
if (value == null) {
value = new TypedValue();
} else {
mTmpValue = null;
}
getValue(id, value, true);//这里通过AssetManager获取资源信息。
}
final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
return res;
}
getValue(..)通过调用AssetManager.getValue(...)获取了资源信息,包括文件地址、图片的密度等。这里获取的图片密度是根据我们放置的文件夹获取的。假如我们将资源放置在xxhdpi里面,那么这里获取到的图片密度就是480。这里解答了一开始的一个问题, 系统怎么选择相应密度的资源:通过AssetManager.getValue()获取相应密度的资源。
接下来我们看加载图片资源的过程。在Resources().loadDrawable(...)里面。
@Nullable
Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
/**省略变量声明.....***/
//返回缓存图片
if (!mPreloading) {
final Drawable cachedDrawable = caches.getInstance(key, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
//获取缓存的ConstantState。
final ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
/**省略.....***/
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(this);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
//获取新图片资源,下面
dr = loadDrawableForCookie(value, id, null);
}
/**....缓存图片和ConstantState**/
return dr;
}
loadDrawble(..) 判断是否有图片资源的缓存,有则直接返回,没有则通过loadDrawableForCookie(..)加载新图片资源。
private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
/***省略***/
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(this, rp, theme);
rp.close();
} else {
//获取资源文件流
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
//根据文件流创建Drawable资源。
dr = Drawable.createFromResourceStream(this, value, is, file, null);
is.close();
}
} catch (Exception e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return dr;
}
loadDrawableForCookie(...)通过AssetManager获取资源流,并通过Drawable.createFromResourceStream()创建Drawable对象。Drawable.createFromResourcesStream(..)最终调用了BitmapFactory.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);
BitmapFactory.decodeResourceStream配置了图片选项,然后调用了decodeStream获取Bitmap对象。
这里就解答了第一个问题,为什么支持不同密度的资源需要放在相应的文件夹里面。因为系统会根据放置的文件夹得到相应的图片密度与目标屏幕的密度匹配,作放大或者缩小处理。一个ARGB_8888 模式的Bitmap对象占用内存 = 宽度x高度 x4 bytes。一个高密度的屏幕加载一个低密度资源,会将资源图片放大处理。反则,缩小。放大处理后,会占用更大的内存空间,但是图片在界面上显示的质量是同等等。所以才需要为不同屏幕配置相应的图片资源,并且放置到相应的文件夹里面。