前文我们从总体上介绍了theme和style,我们要注意theme和style的本质,以及它们和AssetManager(或者说Android中的资源)的区别和联系。本文我们详细介绍theme和style的创建、appy以及theme中属性的获取和解析 。
我们还是从Context的实现类ContextImpl说起,至于Activity、ContextWrapper等都会调到ContextImpl。
//frameworks/base/core/java/android/app/ContextImpl.java
//设置theme,这里的resId就是我们资源文件里的一个style了,通常是继承自android中的某个了
@Override
public void setTheme(int resid) {
mThemeResource = resid;
}
@Override
public int getThemeResId() {
return mThemeResource;
}
@Override
public Resources.Theme getTheme() {
if (mTheme == null) {
//如果我们设置过mThemeResource,selectDefaultTheme方法会直接返回mThemeResource
//如果没有设置过,则会根据不同的版本,返回不同的默认theme
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getOuterContext().getApplicationInfo().targetSdkVersion);
mTheme = mResources.newTheme();
mTheme.applyStyle(mThemeResource, true);
}
return mTheme;
}
这是ContextImpl中有关theme的几个方法。我们这里重点关注getTheme方法,它会先去看我们是否设置了mThemeResource,如果没有设置,则会根据系统版本返回一个默认值给mThemeResource。剩下的就是创建theme和把mThemeResource(其实就是我们的一个style的Id)应用到创建的theme里了,我们先看前者:
//frameworks/base/core/java/android/content/res/Resources.java
public final Theme newTheme() {
return new Theme();
}
Theme类是Resources的一个非静态内部类,我们要注意的就是,它是默认持有Resources的引用的。
//frameworks/base/core/java/android/content/res/Resources.java
public final class Theme {
//内部有一个AssetManager对象,也就意味着它是可以访问所有资源的
private final AssetManager mAssets;
//这个是native层对象的地址
private final long mTheme;
/** Resource identifier for the theme. */
private int mThemeResId = 0;
/** Unique key for the series of styles applied to this theme. */
private String mKey = "";
Theme() {
mAssets = Resources.this.mAssets;
mTheme = mAssets.createTheme();
}
public void applyStyle(int resId, boolean force) {
//......
mThemeResId = resId;
mKey += Integer.toHexString(resId) + (force ? "! " : " ");
}
}
Resources.Theme
类只有四个成员,我们这里说一下mKey
。这个变量用来记录我们都给这个Resources.Theme
apply了哪些style,并且这个style如果是强制的,它后面会跟一个!,否则会跟一个空格。这也就是说我们可以给一个Resources.Theme
apply多个style,style小而灵活,是对theme的完美补充,或者说是多个style共同组成了一套theme。另外,Resources.Theme
持有外部类Resources
的引用,进而得到AssetManager的实例,构造的时候会去创建theme:
//frameworks/base/core/java/android/content/res/AssetManager.java
final long createTheme() {
synchronized (this) {
if (!mOpen) {
throw new RuntimeException("Assetmanager has been closed");
}
long res = newTheme();
//引用计数,为0的时候会去release
incRefsLocked(res);
return res;
}
}
newTheme()
是一个native方法:
//frameworks/base/core/jni/android_util_AssetManager.cpp
static jlong android_content_AssetManager_newTheme(JNIEnv* env, jobject clazz)
{
/** *clazz是我们java层的那个mAssets对象,它是AssetManager的一个实例 *AssetManager.java中有一个成员用来保存其native层AssetManager实例的地址 *就跟java层中Theme类的mTheme成员一样。 *这里直接把native层的AssetManager实例取出 */
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
//传入ResTable的实例
return reinterpret_cast<jlong>(new ResTable::Theme(am->getResources()));
}
ResTable::Theme
可以理解为native层的Resources.Theme
,看其实现:
//frameworks/base/include/androidfw/ResourceTypes.h
class Theme {
public:
Theme(const ResTable& table);
~Theme();
//......省略一些代码
private:
Theme(const Theme&);
Theme& operator=(const Theme&);
struct theme_entry {
/** *表示该entry是属于哪个package的 *app?android ? overlay? sharedlibrary? */
ssize_t stringBlock;
uint32_t typeSpecFlags;
Res_value value;
};
//一个type有多个entry
struct type_info {
size_t numEntries;
theme_entry* entries;
};
//一个package可以有多个type
struct package_info {
type_info types[Res_MAXTYPE + 1];
};
void free_package(package_info* pi);
package_info* copy_package(package_info* pi);
//代表可以访问的所有资源
const ResTable& mTable;
/** * 包含多个package,但是overlay package不会在这里体现。 * 因为overlay package并不独立,它里面的内容只是替换target package而已 */
package_info* mPackages[Res_MAXPACKAGE];
};
ResTable::Theme
的概念很好懂,还是Package、Type、Entry的那套。
//frameworks/base/libs/androidfw/ResourceTypes.cpp
ResTable::Theme::Theme(const ResTable& table)
: mTable(table)
{
memset(mPackages, 0, sizeof(mPackages));
}
至此,theme的创建已经完成,在这个阶段做的其实就是创建java层和native层的基本数据,并把native层的地址返回给java层,如此而已。
回到我们刚开始的那段代码:
//frameworks/base/core/java/android/app/ContextImpl.java
@Override
public Resources.Theme getTheme() {
if (mTheme == null) {
//如果我们设置过mThemeResource,selectDefaultTheme方法会直接返回mThemeResource
//如果没有设置过,则会根据不同的版本,返回不同的默认theme
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getOuterContext().getApplicationInfo().targetSdkVersion);
mTheme = mResources.newTheme();
mTheme.applyStyle(mThemeResource, true);
}
return mTheme;
}
我们已经说完了mTheme = mResources.newTheme();
,下面我们接着分析mTheme.applyStyle(mThemeResource, true);
,看看一个表示theme的ResId是如何被应用到ResTable::Theme
和Resources.Theme
中去的。
//frameworks/base/core/java/android/content/res/Resources.java
public void applyStyle(int resId, boolean force) {
/** * mTheme 我们刚刚创建的native层ResTable::Theme的地址 * resId theme的Id * force true */
AssetManager.applyThemeStyle(mTheme, resId, force);
//记录一下theme的Id
mThemeResId = resId;
//前面已经介绍过,不再赘述
mKey += Integer.toHexString(resId) + (force ? "! " : " ");
}
AssetManager.applyThemeStyle
是一个native方法,我们直接到jni层去看:
//frameworks/base/core/jni/android_util_AssetManager.cpp
static void android_content_AssetManager_applyThemeStyle(JNIEnv* env, jobject clazz,
jlong themeHandle, /*java层的mTheme*/
jint styleRes,
jboolean force)/*true*/
{
//既然都是native层的直接强转就可以了
ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
theme->applyStyle(styleRes, force ? true : false);
}
终于又回到ResTable::Theme
了,不过其实现相对复杂,我们在看之前先说几个点:
<style name="TextAppearance.Adam.Small.Inverse" > - "android:textSize"
>@bmigo:dimen/bmigo_small_inverse_text_size style>
这是之前项目的一段代码。先说一下运行环境,我们的应用跑起来后会加载至少3个资源包:app本身(包名:com.xxx.xxx)、Android系统资源包framework-res.apk(包名:android)、我们自己的系统资源包bmigo-framework-res.apk(包名:bmigo)。上面这段代码,在com.xxx.xxx包的sytle中,我们把bmigo包中的一项资源,赋值给了android包中的一个属性textSize。其实还有更复杂的,每个style还可以有一个parent,如果这个parent是第四个包中的style,那么这一个小小的style,哪怕只有一个item,也跨了4个包!!!
//frameworks/base/core/libs/androidfw/ResourceTypes.cpp
status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
{
/*这里一个bag_entry表示resID的一个Bag,也就是我们style的一个item*/
const bag_entry* bag;
uint32_t bagTypeSpecFlags = 0;
mTable.lock();
/** * 获取resID对应的style的所有item,包括这个style的parent的item, * 另外考虑到资源共享库,这里获取到的资源也会去DynamicRefTable中对id做转换 * 也就是说mTable.getBagLocked方法返回的bag_entry已经处理过DynamicRefenence的问题了 */
const ssize_t N = mTable.getBagLocked(resID, &bag, &bagTypeSpecFlags);
TABLE_NOISY(ALOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N));
if (N < 0) {
mTable.unlock();
return N;
}
uint32_t curPackage = 0xffffffff;
ssize_t curPackageIndex = 0;
package_info* curPI = NULL;
uint32_t curType = 0xffffffff;
size_t numEntries = 0;
theme_entry* curEntries = NULL;
const bag_entry* end = bag + N;
//循环,把每个Bag资源写入ResTable::Theme
while (bag < end) {
//这里的attrRes是我们的item中指定的那个attr资源的id,其形式为0xpptteeee
const uint32_t attrRes = bag->map.name.ident;
const uint32_t p = Res_GETPACKAGE(attrRes);
const uint32_t t = Res_GETTYPE(attrRes);
const uint32_t e = Res_GETENTRY(attrRes);
if (curPackage != p) {
//找到我们当前item中的属性在哪个包中
const ssize_t pidx = mTable.getResourcePackageIndex(attrRes);
//正确性检查,略过
//.......
curPackage = p;
curPackageIndex = pidx;
// mPackages[pidx]是ResTable::Theme的成员
curPI = mPackages[pidx];
//如果在ResTable::Theme中尚未给当前包分配内存,则分配之
if (curPI == NULL) {
curPI = (package_info*)malloc(sizeof(package_info));
memset(curPI, 0, sizeof(*curPI));
mPackages[pidx] = curPI;
}
//如果是新包,type标记为为处理
curType = 0xffffffff;
}
if (curType != t) {
//正确性检查,略过
//.......
curType = t;
curEntries = curPI->types[t].entries;
//如果没有为该type的所有entry分配过空间,则分配之
if (curEntries == NULL) {
//拿到对应的PackageGroup,进而拿到TypeList
PackageGroup* const grp = mTable.mPackageGroups[curPackageIndex];
const TypeList& typeList = grp->types[t];
/** * 我们知道当有Overlay Package的时候,overlay包里的资源是放到typeList[1]里面的 * 为什么是typeList[0],而不是typeList[1]呢?因为overlay在theme的概念里 * 并不是一个独立的存在,它里面的entry最终还是对应于target包里的entry的, * 所以这里就去target包的了 */
int cnt = typeList.isEmpty() ? 0 : typeList[0]->entryCount;
//分配内存了
curEntries = (theme_entry*)malloc(cnt*sizeof(theme_entry));
//初始化
memset(curEntries, Res_value::TYPE_NULL, cnt*sizeof(theme_entry));
curPI->types[t].numEntries = cnt;
curPI->types[t].entries = curEntries;
}
numEntries = curPI->types[t].numEntries;
}
//正确性检查,略过
//.......
//注意这里的e其实就是我们的attr在对应包的对应type里的一个索引
//这里直接用这个索引了
theme_entry* curEntry = curEntries + e;
//如果当前theme_entry没有赋过值,或者要强制覆盖写入则把我们每个item的值写入theme_entry
if (force || curEntry->value.dataType == Res_value::TYPE_NULL) {
//虽然名字叫stringBlock,其实是表示当前Bag所属的资源也就是style是哪个包里的
curEntry->stringBlock = bag->stringBlock;
curEntry->typeSpecFlags |= bagTypeSpecFlags;
/** * 当curEntry->value的类型是TYPE_REFERENCE的时候,stringBlock不是必须的 * 因为0xpptteeee的引用已经指明了哪个包(准确地说是哪个PackageGroup) * 当curEntry->value的类型不是引用的时候,比如是一个String,那就有一个问题了 * 该去哪个包中找这个string呢? * 这个时候stringBlock就派上用场了 */
curEntry->value = bag->map.value;
}
bag++;
}
mTable.unlock();
return NO_ERROR;
}
我们看到ResTable::Theme::applyStyle()
方法的实质就是取出我们的style(包括其parent style)中的一个一个的item(Bag),并把它写入到ResTable::Theme
对应的数据结构中。具体写入到哪里呢?还是上面的那个例子:
<style name="TextAppearance.Adam.Small.Inverse" > - "android:textSize"
>@bmigo:dimen/bmigo_small_inverse_text_size style>
它会把bmigo:dimen/bmigo_small_inverse_text_size写入ResTable::Theme::mPackages[0x01].types[0x01].entries[0x0095]
,具体为:
//假设bmigo:dimen/bmigo_small_inverse_text_size的id为0x09050006
ResTable::Theme::mPackages[0x01]->types[0x01]->entries[0x0095]->stringBlock = 0x02;
ResTable::Theme::mPackages[0x01]->types[0x01]->entries[0x0095]->typeSpecFlags = bagTypeSpecFlags;
ResTable::Theme::mPackages[0x01]->types[0x01]->entries[0x0095]->value.data = 0x09050006;
ResTable::Theme::mPackages[0x01]->types[0x01]->entries[0x0095]->value.dataType = Res_value::TYPE_REFERENCE;
我们的app起来的时候会最先加载Android系统资源包framework-res.apk(包名:android,包id:0x01,index:0x00),然后加载我们自己的系统资源包bmigo-framework-res.apk(包名:bmigo,包id:0x09,index:0x01),最后才是我们的app本身(包名:com.xxx.xxx,包id:0x7f,index:0x02)。另外,frameworks/base/core/res/res/values/public.xml中可以看到,android:textSize这个attr的id为0x01010095,所以mPackages[0x01]->types[0x01]->entries[0x0095],stringBlock = 0x02,表示ResTable::Theme::mPackages[0x01]->types[0x01]->entries[0x0095]是app包,也就是com.xxx.xxx中的style的Bag。value.data直接就是bmigo:dimen/bmigo_small_inverse_text_size的id:0x09050006,由于是资源的一个引用,所以value.dataType = Res_value::TYPE_REFERENCE。
ResTable::Theme内部也有一个Package、Type、Entry的数据结构,它主要用来存储属性-值对儿中的值,而属性则直接用索引来表示。另外,关于overlay和DynamicReference,其中DynamicReference会在ResTable::getBagLocked()
方法中处理好,而ResTable::getBagLocked()
在获取Bag资源的过程中,会调用ResTable::getEntry
,overlay相关的东西会在这个方法中得到处理。所以说,theme和style的这些处理,对于有overlay和资源共享库的情况也是适用的,完全不必担心。
另外,对于一个Resources.Theme
或者ResTable::Theme
,我们可以通过public void applyStyle(int resId, boolean force)
方法给它apply多个style,如果force为true,它会强制覆盖前面已经赋过的值属性,所以这种情况下applyStyle
调用的顺序不同,最终的结果也有可能不同。
我们创建了Theme,并且给它apply了style后就可以通过属性来引用它里面的数据了,比如我们经常使用的obtainStyledAttributes
族的方法(Context
的同名方法还是调用Resources.Theme
的,我们就只介绍Resources.Theme
的了),我们这里只介绍它四个参数的,因为别的都是会调到这个的:
//frameworks/base/core/java/android/content/res/Resources.java
/** *这个方法的作用是从theme中获取attrs指定的那些属性的值 *值的来源可以有set、set中的style属性(如果有的话)、 * defStyleAttr和defStyleRes、以及theme本身,请注意顺序。 * 靠前的优先级比靠后的高。 * * set 一组属性-值对儿,比如我们布局文件中设置的一段诸如 * android:layout_width="wrap_content" * android:layout_height="wrap_content" * android:text="Hello World!" * 这样的代码 * attrs 我们要获取哪些属性,int数组表示attrs的id,通常是一个属性组 * defStyleAttr 指定theme当中的属性,这个属性所指向的style,会给attrs中的属性赋值 * defStyleRes 直接指定一个style, 这个style会给attrs中的属性赋值 * @return 获取的属性值,可通过每个属性的索引来获取 */
public TypedArray obtainStyledAttributes(AttributeSet set,
int[] attrs, int defStyleAttr, int defStyleRes) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(Resources.this, len);
//拿到要获取的属性的个数
final int len = attrs.length;
//分配一段内存,用来存储获取的结果
final TypedArray array = TypedArray.obtain(Resources.this, len);
//直接转化为XmlBlock.Parser,因为它本身就是一段xml
final XmlBlock.Parser parser = (XmlBlock.Parser)set;
//交给AssetManager处理
AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);
}
AssetManager.applyStyle
方法中,mTheme表示native层Restable::Theme
对象的地址,array的内存被分成了两部分,一部分存储具体的值,一部分存储索引,这个方法是个native方法:
static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject clazz,
jlong themeToken,
jint defStyleAttr,
jint defStyleRes,
jlong xmlParserToken,
jintArray attrs,
jintArray outValues,
jintArray outIndices)
{
//.....省略次要代码
//native层的地址,直接强转
ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
//还记得吗,ResTable::Theme中有一个ResTable字段
const ResTable& res = theme->getResTable();
//直接转成ResXMLParser对象
ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken);
ResTable_config config;
Res_value value;
const jsize NI = env->GetArrayLength(attrs);
const jsize NV = env->GetArrayLength(outValues);
jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
jint* dest = baseDest;
jint* indices = NULL;
int indicesIdx = 0;
if (outIndices != NULL) {
if (env->GetArrayLength(outIndices) > NI) {
indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
}
}
uint32_t defStyleBagTypeSetFlags = 0;
if (defStyleAttr != 0) {
Res_value value;
//如果我们指定了defStyleAttr,则解析之
if (theme->getAttribute(defStyleAttr, &value, &defStyleBagTypeSetFlags) >= 0) {
//它的值只要不为空,就应该是一个style,类型也应该是TYPE_REFERENCE
//用这个style强制覆盖defStyleRes,注意我们前面讲过的优先级
if (value.dataType == Res_value::TYPE_REFERENCE) {
defStyleRes = value.data;
}
}
}
int style = 0;
uint32_t styleBagTypeSetFlags = 0;
/** *这部分代码对应类似 * android:layout_width="wrap_content" * android:layout_height="wrap_content" * style="?attr/textStyle" * android:text="Hello World!" * 代码中的 style 属性 */
//xmlParse不为空,则去解析style属性
if (xmlParser != NULL) {
ssize_t idx = xmlParser->indexOfStyle();
if (idx >= 0 && xmlParser->getAttributeValue(idx, &value) >= 0) {
//如果解析结果是一个属性,则在theme里去解析这个属性
if (value.dataType == value.TYPE_ATTRIBUTE) {
if (theme->getAttribute(value.data, &value, &styleBagTypeSetFlags) < 0) {
value.dataType = Res_value::TYPE_NULL;
}
}
//正常情况下它的类型应该是一个style,类型自然是TYPE_REFERENCE
if (value.dataType == value.TYPE_REFERENCE) {
style = value.data;
}
}
}
const ResTable::bag_entry* defStyleAttrStart = NULL;
uint32_t defStyleTypeSetFlags = 0;
//解析defStyle,获取它的所有item
ssize_t bagOff = defStyleRes != 0
? res.getBagLocked(defStyleRes, &defStyleAttrStart, &defStyleTypeSetFlags) : -1;
defStyleTypeSetFlags |= defStyleBagTypeSetFlags;
const ResTable::bag_entry* const defStyleAttrEnd = defStyleAttrStart + (bagOff >= 0 ? bagOff : 0);
//构造Finder,这个Finder表示的是defStyleAttr和defStyleRes,优先级较低,仅比theme本身高
BagAttributeFinder defStyleAttrFinder(defStyleAttrStart, defStyleAttrEnd);
//和上面类似,构造Finder,这个Finder表示的是style属性,优先级次高
const ResTable::bag_entry* styleAttrStart = NULL;
uint32_t styleTypeSetFlags = 0;
bagOff = style != 0 ? res.getBagLocked(style, &styleAttrStart, &styleTypeSetFlags) : -1;
styleTypeSetFlags |= styleBagTypeSetFlags;
const ResTable::bag_entry* const styleAttrEnd = styleAttrStart + (bagOff >= 0 ? bagOff : 0);
BagAttributeFinder styleAttrFinder(styleAttrStart, styleAttrEnd);
//和上面类似,构造Finder,这个Finder表示的是AttributeSet,优先级最高
static const ssize_t kXmlBlock = 0x10000000;
XmlAttributeFinder xmlAttrFinder(xmlParser);
const jsize xmlAttrEnd = xmlParser != NULL ? xmlParser->getAttributeCount() : 0;
ssize_t block = 0;
uint32_t typeSetFlags;
//遍历attrs,一个attr一个attr地查找解析
for (jsize ii = 0; ii < NI; ii++) {
//拿到每个属性的id
const uint32_t curIdent = (uint32_t)src[ii];
//初始化
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
typeSetFlags = 0;
config.density = 0;
/** * Try to find a value for this attribute... we prioritize values * coming from, first XML attributes, then XML style, then default * style, and finally the theme. */
//先从XML attributes中找
const jsize xmlAttrIdx = xmlAttrFinder.find(curIdent);
if (xmlAttrIdx != xmlAttrEnd) {
// We found the attribute we were looking for.
block = kXmlBlock;
xmlParser->getAttributeValue(xmlAttrIdx, &value);
DEBUG_STYLES(ALOGI("-> From XML: type=0x%x, data=0x%08x",
value.dataType, value.data));
}
//如果没找到,再从XML style中找
if (value.dataType == Res_value::TYPE_NULL) {
// Walk through the style class values looking for the requested attribute.
const ResTable::bag_entry* const styleAttrEntry = styleAttrFinder.find(curIdent);
if (styleAttrEntry != styleAttrEnd) {
// We found the attribute we were looking for.
block = styleAttrEntry->stringBlock;
typeSetFlags = styleTypeSetFlags;
value = styleAttrEntry->map.value;
DEBUG_STYLES(ALOGI("-> From style: type=0x%x, data=0x%08x",
value.dataType, value.data));
}
}
//如果没找到,再从default style中找
if (value.dataType == Res_value::TYPE_NULL) {
// Walk through the default style values looking for the requested attribute.
const ResTable::bag_entry* const defStyleAttrEntry = defStyleAttrFinder.find(curIdent);
if (defStyleAttrEntry != defStyleAttrEnd) {
// We found the attribute we were looking for.
block = defStyleAttrEntry->stringBlock;
typeSetFlags = styleTypeSetFlags;
value = defStyleAttrEntry->map.value;
DEBUG_STYLES(ALOGI("-> From def style: type=0x%x, data=0x%08x",
value.dataType, value.data));
}
}
//如果我们在前面的步骤中找到了
uint32_t resid = 0;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
/** *解析之,这个方法会经过两个阶段的解析 * *经过第一阶段由theme的解析后value的类型不可能再是TYPE_ATTRIBUTE, *当然也不可能是TYPE_DYNAMIC_REFERENCE,因为在getBagLocked方法中已经处理过了 *只可能是具体的值或者TYPE_REFERENCE * *经过第二阶段由ResTable的解析后,会得到最终的具体值 */
ssize_t newBlock = theme->resolveAttributeReference(&value, block,
&resid, &typeSetFlags, &config);
if (newBlock >= 0) {
block = newBlock;
}
DEBUG_STYLES(ALOGI("-> Resolved attr: type=0x%x, data=0x%08x",
value.dataType, value.data));
} else {
/** *如果我们在前面的步骤中没有找到,那就只能从Theme里找一下了 *另外,这个方法会进行theme里的那个阶段的解析 *所以解析后value的type也只可能是:TYPE_REFERENCE或者具体值了 */
ssize_t newBlock = theme->getAttribute(curIdent, &value, &typeSetFlags);
if (newBlock >= 0) {
//再让ResTable去解析,这样得到的结果就是具体的值了
newBlock = res.resolveReference(&value, block, &resid,
&typeSetFlags, &config);
if (newBlock >= 0) {
//value所属的包,com.xxx.xxx,android还是 bmigo?
block = newBlock;
}
}
}
//到这里属性的获取和解析工作已经全部完成,得到的应该是具体值,否则视为没有找到该
//属性对应的值
if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
DEBUG_STYLES(ALOGI("-> Setting to @null!"));
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
block = kXmlBlock;
}
//写入具体值了
dest[STYLE_TYPE] = value.dataType;
dest[STYLE_DATA] = value.data;
//其实就是包的index+1
dest[STYLE_ASSET_COOKIE] = block != kXmlBlock ?
static_cast<jint>(res.getTableCookie(block)) : -1;
dest[STYLE_RESOURCE_ID] = resid;
dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
dest[STYLE_DENSITY] = config.density;
if (indices != NULL && value.dataType != Res_value::TYPE_NULL) {
indicesIdx++;
indices[indicesIdx] = ii;
}
//下一个
dest += STYLE_NUM_ENTRIES;
}
if (indices != NULL) {
//表示总数
indices[0] = indicesIdx;
env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
}
//释放jni指针
env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
env->ReleasePrimitiveArrayCritical(attrs, src, 0);
return JNI_TRUE;
}
这个方法虽然比较常,但逻辑还是比较简单的,一个一个地去查找每一个属性,优先级为:XML attributes、XML style、defStyleAttr、defStyleRes、theme本身。找到属性值之后还要对属性值进行两个阶段的解析,才能得到最终结果,然后就是对最终结果的封装,包括dest、indices的处理等。下面我们重点看下属性解析的两个阶段:
//frameworks/base/core/libs/androidfw/ResourceTypes.cpp
ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue,
uint32_t* outTypeSpecFlags) const
{
//attr递归引用的层级最多为20级
int cnt = 20;
if (outTypeSpecFlags != NULL) *outTypeSpecFlags = 0;
do {
//根据Package、Type、Entry的索引,直接去找value
const ssize_t p = mTable.getResourcePackageIndex(resID);
const uint32_t t = Res_GETTYPE(resID);
const uint32_t e = Res_GETENTRY(resID);
TABLE_THEME(ALOGI("Looking up attr 0x%08x in theme %p", resID, this));
if (p >= 0) {
//Package
const package_info* const pi = mPackages[p];
TABLE_THEME(ALOGI("Found package: %p", pi));
if (pi != NULL) {
TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, Res_MAXTYPE + 1));
if (t <= Res_MAXTYPE) {
//Type
const type_info& ti = pi->types[t];
TABLE_THEME(ALOGI("Desired entry index is %ld in avail %d", e, ti.numEntries));
if (e < ti.numEntries) {
//entry
const theme_entry& te = ti.entries[e];
if (outTypeSpecFlags != NULL) {
*outTypeSpecFlags |= te.typeSpecFlags;
}
TABLE_THEME(ALOGI("Theme value: type=0x%x, data=0x%08x",
te.value.dataType, te.value.data));
//拿到dataType
const uint8_t type = te.value.dataType;
if (type == Res_value::TYPE_ATTRIBUTE) {
if (cnt > 0) {
cnt--;
//dataType是TYPE_ATTRIBUTE,那就进入下次递归
resID = te.value.data;
continue;
}
ALOGW("Too many attribute references, stopped at: 0x%08x\n", resID);
return BAD_INDEX;
} else if (type != Res_value::TYPE_NULL) {
//dataType不再是TYPE_ATTRIBUTE且不为空,那第一阶段的解析工作就完成了,可以返回了
*outValue = te.value;
return te.stringBlock;
}
return BAD_INDEX;
}
}
}
}
break;
} while (true);
return BAD_INDEX;
}
ResID本身就是个索引,直接去找value,如果value的类型是TYPE_ATTRIBUTE,那就递归去解析,最多20级,非常简单。下面看第二阶段的解析:
//frameworks/base/core/libs/androidfw/ResourceTypes.cpp
ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags,
ResTable_config* outConfig) const
{
int count=0;
//ResTable中资源的引用最多也是20级
while (blockIndex >= 0 && value->dataType == Res_value::TYPE_REFERENCE
&& value->data != 0 && count < 20) {
if (outLastRef) *outLastRef = value->data;
uint32_t lastRef = value->data;
uint32_t newFlags = 0;
//value是getResource的输出参数
const ssize_t newIndex = getResource(value->data, value, true, 0, &newFlags,
outConfig);
if (newIndex == BAD_INDEX) {
return BAD_INDEX;
}
TABLE_THEME(ALOGI("Resolving reference %p: newIndex=%d, type=0x%x, data=%p\n",
(void*)lastRef, (int)newIndex, (int)value->dataType, (void*)value->data));
//printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex);
if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags;
if (newIndex < 0) {
return blockIndex;
}
blockIndex = newIndex;
count++;
}
return blockIndex;
}
在while
循环中getResource
方法会不断地把得到的值放在value变量中,直到它的dataType不再是TYPE_REFERENCE为止。需要说明的是,getResource
方法会去处理overlay package相关的东西,具体是在getEntry
中实现的,我们在Android资源管理中的Runtime Resources Overlay-------之overlay包的生效(五)一文中已经讲过,这里不再赘述。
其实Resources.Theme
中也有属性获取和解析的相关接口,我们可以简单看下:
//frameworks/base/core/java/android/content/res/Resources.java
public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
}
//frameworks/base/core/java/android/content/res/AssetManager.java
final boolean getThemeValue(long theme, int ident,
TypedValue outValue, boolean resolveRefs) {
//这个方法会去获取属性的值,并做两个阶段的解析
int block = loadThemeAttributeValue(theme, ident, outValue, resolveRefs);
//如果拿到了值
if (block >= 0) {
//这里如果不是字符串的话,就是具体的值了,可以直接返回
if (outValue.type != TypedValue.TYPE_STRING) {
return true;
}
//如果是字符串,outValue获取到的将会是字符串在 global string pool中的索引
//mStringBlocks存储所有包的global string pool
StringBlock[] blocks = mStringBlocks;
//如果是空的,说明尚未去获取global string pool,那就去拿一下
if (blocks == null) {
ensureStringBlocks();
blocks = mStringBlocks;
}
//到对应的global string pool中取值
outValue.string = blocks[block].get(outValue.data);
return true;
}
return false;
}
loadThemeAttributeValue
是个native方法:
static jint android_content_AssetManager_loadThemeAttributeValue(
JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve)
{
//拿到native层的theme和ResTable实例
ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
const ResTable& res(theme->getResTable());
Res_value value;
// XXX value could be different in different configs!
uint32_t typeSpecFlags = 0;
//熟悉的getAttribute方法,第一阶段的解析
ssize_t block = theme->getAttribute(ident, &value, &typeSpecFlags);
uint32_t ref = 0;
if (resolve) {
//第二阶段的解析
block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
#endif
}
//结果处理
return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
}
theme的解析到这里也介绍得差不多了,就到这里吧。还有theme相关的一些方法我们没有介绍,有兴趣的同学可以自己去看。关于theme和style我们总结一下:
至此,Android资源管理中比较难以理解的部分资源共享库与DynamicReference、Overlay与idmap、Theme与style已经讲完,后面我们可以轻松愉快地讲讲AssetManager与ResTable了,他们构成了Android资源管理的框架。