Android资源管理中的Theme和Style-------之实现(二)

        前文我们从总体上介绍了theme和style,我们要注意theme和style的本质,以及它们和AssetManager(或者说Android中的资源)的区别和联系。本文我们详细介绍theme和style的创建、appy以及theme中属性的获取和解析 。

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层,如此而已。

theme的Apply

        回到我们刚开始的那段代码:

//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::ThemeResources.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了,不过其实现相对复杂,我们在看之前先说几个点:

  • theme可能是跨包的     最极端的一种情况是,我在package1中,给package2中的一个属性赋值,而这个值是package3中的一项资源。举个例子:
    <!--这个style是在我们的一个应用里-->
    <style name="TextAppearance.Adam.Small.Inverse" >
       <item name="android:textSize">@bmigo:dimen/bmigo_small_inverse_text_size</item>
   </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个包!!!

  • 简要说下bag资源     我们知道在AssetManager(或者resources.arsc)中一般一个资源的存在形式是一个ResTable_entry后面跟一个ResTable_value。其中ResTable_entry表示这个资源的名称等信息,而ResTable_value则表示其值。但是这种形式对资源的描述能力是有限的,面对诸如上面那个style形式的资源,该如何描述呢?这就说到Bag资源了,它的描述形式可以简单理解为ResTable_entry,后面跟一堆key-value对儿。只不过这个ResTable_entry比较特殊,它会多出两个字段,一个用来表示parent,也就是说Bag资源是支持继承和重写的;一个表示key-value对儿的个数,这个特殊的ResTable_entry叫做ResTable_map_entry,而每一个key-value对儿就叫做这个ResTable_map_entry的Bag。具体到上面那个style来说,就是每一个item都是style TextAppearance.Adam.Small.Inverse的一个Bag。
 //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是在我们的一个应用里-->
    <style name="TextAppearance.Adam.Small.Inverse" >
       <item name="android:textSize">@bmigo:dimen/bmigo_small_inverse_text_size</item>
    </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中属性的获取和解析

        我们创建了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我们总结一下:

  •     theme和style是为了方便我们为属性赋值而产生的,theme我们可以认为是大而全,style则是小而灵活,是对theme的补充(或者叫补丁更合适,我们可以往一个theme上打好多个style),两者相互配合,共同保证了我们对属性赋值既不遗漏,又不笨重。
  •     theme和style的本质是从view的属性到Android资源的映射。
  •     theme直观上看是一个一个的属性-值对儿。但在实现上,theme内部只存储值,属性被用作了索引,所以我们根据属性去获取属性值,就是拿索引去取值,而不是去搜索,速度会很快。
  •     要获取一个theme中的属性值,我们要经过两个阶段的解析工作:第一个阶段是theme本身,解析Res_value::TYPE_ATTRIBUTE类型的数据;第二个阶段是ResTable,解析Res_value::TYPE_REFERENCE类型的数据。

        至此,Android资源管理中比较难以理解的部分资源共享库与DynamicReference、Overlay与idmap、Theme与style已经讲完,后面我们可以轻松愉快地讲讲AssetManager与ResTable了,他们构成了Android资源管理的框架。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值