Android 资源文件匹配规则
Android 资源文件,可以加各种限定标注,类似我们常用的layout-hdpi ,layout-xhpdi,value-zh-rCN,layout-400x800等等。官方支持的请参考:
应用资源概览
那么Android是怎么去判断使用哪个一个呢?
资源查找
我们知道,不管定义多少个限制符的资源文件,ID只有一个,所以到底取哪个文件夹下面的呢?
以Resource#getDrawable开启。主要流程是:Resource#getDrawable-》Resource#getDrawableForDensity-》ResourcesImpl#getValueForDensity-》Resource#loadDrawable。关键性的选择是在ResourcesImpl#getValueForDensity。
这里进入的是AssetManager#getResourceValue。看源码:
@UnsupportedAppUsage
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
Objects.requireNonNull(outValue, "outValue");
synchronized (this) {
ensureValidLocked();
final int cookie = nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
if (cookie <= 0) {
return false;
}
// Convert the changing configurations flags populated by native code.
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = getPooledStringForCookie(cookie, outValue.data);
}
return true;
}
}
这个方法是不给外部使用的,真正干活的是nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
下面就是真正的源码了,必须下载aosp.
//Volumes/AOSP/aosp/frameworks/base/core/jni/android_util_AssetManager.cpp
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
jshort density, jobject typed_value,
jboolean resolve_references) {
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);
if (cookie == kInvalidCookie) {
return ApkAssetsCookieToJavaCookie(kInvalidCookie);
}
....
}
具体的分析就不继续了,我给大家贴下需要的文件路径。
/Volumes/AOSP/aosp/frameworks/base/libs/androidfw/AssetManager2.cpp
/Volumes/AOSP/aosp/frameworks/base/libs/androidfw/ResourceTypes.cpp
那么上面那个多限定符的优先级是在哪里确定的?
//Volumes/AOSP/aosp/frameworks/base/libs/androidfw/AssetManager2.cpp
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
bool /*stop_at_first_match*/,
bool ignore_configuration,
FindEntryResult* out_entry) const {
.....
if (!overlay_result.config.isBetterThan(out_entry->config, desired_config)
&& overlay_result.config.compare(out_entry->config) != 0) {
// The configuration of the entry for the overlay must be equal to or better than the target
// configuration to be chosen as the better value.
continue;
}
.....
}
//Volumes/AOSP/aosp/frameworks/base/libs/androidfw/ResourceTypes.cpp
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested){
if (imsi || o.imsi) {
if ((mcc != o.mcc) && requested->mcc) {
return (mcc);
}
if ((mnc != o.mnc) && requested->mnc) {
return (mnc);
}
}
if (isLocaleBetterThan(o, requested)) {
return true;
}
if (screenLayout || o.screenLayout) {
if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0
&& (requested->screenLayout & MASK_LAYOUTDIR)) {
int myLayoutDir = screenLayout & MASK_LAYOUTDIR;
int oLayoutDir = o.screenLayout & MASK_LAYOUTDIR;
return (myLayoutDir > oLayoutDir);
}
}
if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
// The configuration closest to the actual size is best.
// We assume that larger configs have already been filtered
// out at this point. That means we just want the largest one.
if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
return smallestScreenWidthDp > o.smallestScreenWidthDp;
}
}
if (screenSizeDp || o.screenSizeDp) {
// "Better" is based on the sum of the difference between both
// width and height from the requested dimensions. We are
// assuming the invalid configs (with smaller dimens) have
// already been filtered. Note that if a particular dimension
// is unspecified, we will end up with a large value (the
// difference between 0 and the requested dimension), which is
// good since we will prefer a config that has specified a
// dimension value.
int myDelta = 0, otherDelta = 0;
if (requested->screenWidthDp) {
myDelta += requested->screenWidthDp - screenWidthDp;
otherDelta += requested->screenWidthDp - o.screenWidthDp;
}
if (requested->screenHeightDp) {
myDelta += requested->screenHeightDp - screenHeightDp;
otherDelta += requested->screenHeightDp - o.screenHeightDp;
}
if (kDebugTableSuperNoisy) {
ALOGI("Comparing this %dx%d to other %dx%d in %dx%d: myDelta=%d otherDelta=%d",
screenWidthDp, screenHeightDp, o.screenWidthDp, o.screenHeightDp,
requested->screenWidthDp, requested->screenHeightDp, myDelta, otherDelta);
}
if (myDelta != otherDelta) {
return myDelta < otherDelta;
}
}
if (screenLayout || o.screenLayout) {
if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0
&& (requested->screenLayout & MASK_SCREENSIZE)) {
// A little backwards compatibility here: undefined is
// considered equivalent to normal. But only if the
// requested size is at least normal; otherwise, small
// is better than the default.
int mySL = (screenLayout & MASK_SCREENSIZE);
int oSL = (o.screenLayout & MASK_SCREENSIZE);
int fixedMySL = mySL;
int fixedOSL = oSL;
if ((requested->screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) {
if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL;
if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL;
}
// For screen size, the best match is the one that is
// closest to the requested screen size, but not over
// (the not over part is dealt with in match() below).
if (fixedMySL == fixedOSL) {
// If the two are the same, but 'this' is actually
// undefined, then the other is really a better match.
if (mySL == 0) return false;
return true;
}
if (fixedMySL != fixedOSL) {
return fixedMySL > fixedOSL;
}
}
if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0
&& (requested->screenLayout & MASK_SCREENLONG)) {
return (screenLayout & MASK_SCREENLONG);
}
}
if (screenLayout2 || o.screenLayout2) {
if (((screenLayout2^o.screenLayout2) & MASK_SCREENROUND) != 0 &&
(requested->screenLayout2 & MASK_SCREENROUND)) {
return screenLayout2 & MASK_SCREENROUND;
}
}
if (colorMode || o.colorMode) {
if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0 &&
(requested->colorMode & MASK_WIDE_COLOR_GAMUT)) {
return colorMode & MASK_WIDE_COLOR_GAMUT;
}
if (((colorMode^o.colorMode) & MASK_HDR) != 0 &&
(requested->colorMode & MASK_HDR)) {
return colorMode & MASK_HDR;
}
}
if ((orientation != o.orientation) && requested->orientation) {
return (orientation);
}
if (uiMode || o.uiMode) {
if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0
&& (requested->uiMode & MASK_UI_MODE_TYPE)) {
return (uiMode & MASK_UI_MODE_TYPE);
}
if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0
&& (requested->uiMode & MASK_UI_MODE_NIGHT)) {
return (uiMode & MASK_UI_MODE_NIGHT);
}
}
if (screenType || o.screenType) {
。。。。。。。。
if (screenSize || o.screenSize) {
// "Better" is based on the sum of the difference between both
// width and height from the requested dimensions. We are
// assuming the invalid configs (with smaller sizes) have
// already been filtered. Note that if a particular dimension
// is unspecified, we will end up with a large value (the
// difference between 0 and the requested dimension), which is
// good since we will prefer a config that has specified a
// size value.
int myDelta = 0, otherDelta = 0;
if (requested->screenWidth) {
myDelta += requested->screenWidth - screenWidth;
otherDelta += requested->screenWidth - o.screenWidth;
}
if (requested->screenHeight) {
myDelta += requested->screenHeight - screenHeight;
otherDelta += requested->screenHeight - o.screenHeight;
}
if (myDelta != otherDelta) {
return myDelta < otherDelta;
}
}
。。。。。。
}
ResourceTypes.cpp 里面来判断匹配和匹配规则。
所以,限定符的顺序,必须按照上面贴的那个链接顺序来,解析的时候也是按照那个优先级来处理的。其中要说一下,这里还有个判断screenSize,就是我们填写的400x800,我不知道官方为啥把这部分拿掉了,可能是不希望我们这么做吧。
关于screenSize 这个好像是要减去状态栏,这个等我找找是在哪里的定义?
具体比如说,hdpi xhdpi优先级确定,这个部分在isBetterThan方法里面有描述。