Android TypedArray简单分析(二)源代码分析

源代码分析

  从Context类的obtainStyledAttributes()方法一直往下看,

    /**
     * Retrieve styled attribute information in this Context's theme.  See
     * {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
     * for more information.
     *
     * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
     */
    @NonNull
    public final TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
            @NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
            @StyleRes int defStyleRes) {
    
    
        return getTheme().obtainStyledAttributes(
            set, attrs, defStyleAttr, defStyleRes);
    }

  首先调用了getTheme,这个Context一般都是Activity实例,在这调用的是ContextThemeWrapper类的getTheme,这个其实就是得到当前Activity的主题。Activity的Theme可以在Manifest文件中通过Activity的标签属性android:theme来进行设置,如果没有设置,这个主题就是在Application的标签属性android:theme来进行设置。如果这俩都没有进行设置,则会根据当前的目标SDK版本来选择默认的Theme。选择的默认Theme可以看下面的代码:

   /**
     * Returns the most appropriate default theme for the specified target SDK version.
     * <ul>
     * <li>Below API 11: Gingerbread
     * <li>APIs 12 thru 14: Holo
     * <li>APIs 15 thru 23: Device default dark
     * <li>APIs 24 and above: Device default light with dark action bar
     * </ul>
     *
     * @param curTheme The current theme, or 0 if not specified.
     * @param targetSdkVersion The target SDK version.
     * @return A theme resource identifier
     * @hide
     */
    @UnsupportedAppUsage
    public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
    
    
        return selectSystemTheme(curTheme, targetSdkVersion,
                com.android.internal.R.style.Theme,
                com.android.internal.R.style.Theme_Holo,
                com.android.internal.R.style.Theme_DeviceDefault,
                com.android.internal.R.style.Theme_DeviceDefault_Light_DarkActionBar);
    }

    /** @hide */
    public static int selectSystemTheme(int curTheme, int targetSdkVersion, int orig, int holo,
            int dark, int deviceDefault) {
    
    
        if (curTheme != ID_NULL) {
    
    
            return curTheme;
        }
        if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
    
    
            return orig;
        }
        if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    
    
            return holo;
        }
        if (targetSdkVersion < Build.VERSION_CODES.N) {
    
    
            return dark;
        }
        return deviceDefault;
    }

  接着会去调用Theme类的obtainStyledAttributes()方法:

        @NonNull
        public TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
                @NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
                @StyleRes int defStyleRes) {
    
    
            synchronized (mLock) {
    
    
                return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr,
                        defStyleRes);
            }
        }

  可见又调用到ResourcesImpl类的内部类ThemeImpl的方法obtainStyledAttributes

       @NonNull
       TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
               AttributeSet set,
               @StyleableRes int[] attrs,
               @AttrRes int defStyleAttr,
               @StyleRes int defStyleRes) {
    
    
           final int len = attrs.length;
           final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);

           // XXX note that for now we only work with compiled XML files.
           // To support generic XML files we will need to manually parse
           // out the attributes from the XML file (applying type information
           // contained in the resources and such).
           final XmlBlock.Parser parser = (XmlBlock.Parser) set;
           mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
                   array.mDataAddress, array.mIndicesAddress);
           array.mTheme = wrapper;
           array.mXml = parser;
           return array;
       }

  该方法先初始化TypedArray,接着将参数set转化成XmlBlock.Parser类,该类对应着C++层中的ResXMLParser,它是解析二进制资源文件的工具类,包含着布局xml文件中该控件对应的所有属性的值。最后调用AssetManager类实例mAssets的applyStyle方法,最后将结果返回。

TypedArray的初始化

先看看TypedArray的初始化

    static TypedArray obtain(Resources res, int len) {
    
    
            TypedArray attrs = res.mTypedArrayPool.acquire();
            if (attrs == null) {
    
    
                 attrs = new TypedArray(res);
            }
            attrs.mRecycled = false;
            // Reset the assets, which may have changed due to configuration changes
            // or further resource loading.
            attrs.mAssets = res.getAssets();
            attrs.mMetrics = res.getDisplayMetrics();
            attrs.resize(len);
            return attrs;
        }

  首先从res的缓存池里获取,如果没有获取成功,初始化TypedArray。然后设置相应的属性,之后调用attrs.resize(len)方法,设置长度大小。可以看到这个长度大小是从obtainStyledAttributes()函数的参数attrs的长度,这个参数就是要找到的属性的整数值的集合。

    private void resize(int len) {
    
    
            mLength = len;
            final int dataLen = len * STYLE_NUM_ENTRIES;
            final int indicesLen = len + 1;
            final VMRuntime runtime = VMRuntime.getRuntime();
            if (mDataAddress == 0 || mData.length < dataLen) {
    
    
                 mData = (int[]) runtime.newNonMovableArray(int.class, dataLen);
                 mDataAddress = runtime.addressOf(mData);
                 mIndices = (int[]) runtime.newNonMovableArray(int.class, indicesLen);
                 mIndicesAddress = runtime.addressOf(mIndices);
            }
        }

  这个方法主要就是设置TypedArray 类的成员变量mLength,mData,mDataAddress,mIndices,mIndicesAddress。前面那篇文章 Android TypedArray简单分析(一)使用 也介绍了该类成员mData和mIndices。可以看到设置的对应长度就是按照属性的大小来设置的。STYLE_NUM_ENTRIES的值是7,mData数组的长度为7*len,mIndices的长度为len+1。mData,mIndices这两个数组都是通过runtime.newNonMovableArray方法来定义的,表明数组的地址不能移动。接着又通过runtime.addressOf()方法获得数组的地址来指定mDataAddress和mIndicesAddress的值。mDataAddress和mIndicesAddress会作为参数传递到c++层,这样在c++层就能通过地址找到mData和mIndices。

AssetManager类实例mAssets的applyStyle方法

  再接着就是调用AssetManager类实例mAssets的applyStyle方法,可以看到参数5和参数6确实是TypedArray类的mDataAddress和mIndicesAddress:

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
            @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
            long outIndicesAddress) {
    
    
        Objects.requireNonNull(inAttrs, "inAttrs");
        synchronized (this) {
    
    
            // Need to synchronize on AssetManager because we will be accessing
            // the native implementation of AssetManager.
            ensureValidLocked();
            nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
                    parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
                    outIndicesAddress);
        }
    }

  下面就会调用本地方法nativeApplyStyle()。在调用本地方法之前,会将parser的成员mParseState 作为参数传进去,它是C++层中ResXMLParser类对象的地址,等进入到C++层中,通过它就可以找到ResXMLParser类对象。上面也说了,它包含着布局xml文件中该控件对应的所有属性的值。
  从这里能看到Theme通过成员变量ThemeImpl实现,而ThemeImpl则通过它的成员变量mTheme指向C++层的类对象来实现。ThemeImpl类的成员类型AssetManager变量mAssets则通过其成员变量mObject指向C++层的类对象来实现。
  看下C++层实现,实现在platform\frameworks\base\core\jni\android_util_AssetManager.cpp

static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
                             jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr,
                             jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) {
    
    
  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
  Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
  CHECK(theme->GetAssetManager() == &(*assetmanager));
  (void) assetmanager;

  ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
  uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr);
  uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr);

  jsize attrs_len = env->GetArrayLength(java_attrs);
  jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
  if (attrs == nullptr) {
    
    
    return;
  }

  ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr),
             static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len,
             out_values, out_indices);
  env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
}

  从这看到,Java层的AssetManager对应C++层的AssetManager2类,Java层的Theme对应C++层的Theme类。还通过参数xml_parser_ptr找到对应的ResXMLParser类对象,
  out_values 和out_indices 分别是上面说的Java类TypedArray类对象的mData和mIndices数组的地址。
  再往下就是调用ApplyStyle()函数,它在platform\frameworks\base\libs\androidfw\AttributeResolution.cpp,看下它

base::expected<std::monostate, IOError> ApplyStyle(Theme* theme, ResXMLParser* xml_parser,
                                                   uint32_t def_style_attr,
                                                   uint32_t def_style_resid,
                                                   const uint32_t* attrs, size_t attrs_length,
                                                   uint32_t* out_values, uint32_t* out_indices) {
    
    
  DEBUG_LOG("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme,
            def_style_attr, def_style_resid, xml_parser);

  int indices_idx = 0;
  const AssetManager2* assetmanager = theme->GetAssetManager();

  // Load default style from attribute, if specified...
  uint32_t def_style_theme_flags = 0U;
  const auto default_style_bag = GetStyleBag(theme, def_style_attr, def_style_resid,
                                             &def_style_theme_flags);
  if (IsIOError(default_style_bag)) {
    
    
    return base::unexpected(GetIOError(default_style_bag.error()));
  }

  // Retrieve the style resource ID associated with the current XML tag's style attribute.
  uint32_t xml_style_theme_flags = 0U;
  const auto xml_style_bag = GetXmlStyleBag(theme, xml_parser, &def_style_theme_flags);
  if (IsIOError(xml_style_bag)) {
    
    
    return base::unexpected(GetIOError(xml_style_bag.error()));
  }

  BagAttributeFinder def_style_attr_finder(default_style_bag.value_or(nullptr));
  BagAttributeFinder xml_style_attr_finder(xml_style_bag.value_or(nullptr));
  XmlAttributeFinder xml_attr_finder(xml_parser);

  // Now iterate through all of the attributes that the client has requested,
  // filling in each with whatever data we can find.
  for (size_t ii = 0; ii < attrs_length; ii++) {
    
    
    const uint32_t cur_ident = attrs[ii];
    DEBUG_LOG("RETRIEVING ATTR 0x%08x...", cur_ident);

    AssetManager2::SelectedValue value{
    
    };
    uint32_t value_source_resid = 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.

    // Walk through the xml attributes looking for the requested attribute.
    const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
    if (xml_attr_idx != xml_attr_finder.end()) {
    
    
      // We found the attribute we were looking for.
      Res_value attribute_value{
    
    };
      xml_parser->getAttributeValue(xml_attr_idx, &attribute_value);
      value.type = attribute_value.dataType;
      value.data = attribute_value.data;
      value_source_resid = xml_parser->getSourceResourceId();
      DEBUG_LOG("-> From XML: type=0x%x, data=0x%08x", value.type, value.data);
    }

    if (value.type == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
    
    
      // Walk through the style class values looking for the requested attribute.
      const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident);
      if (entry != xml_style_attr_finder.end()) {
    
    
        value = AssetManager2::SelectedValue(*xml_style_bag, *entry);
        value.flags |= xml_style_theme_flags;
        value_source_resid = entry->style;
        DEBUG_LOG("-> From style: type=0x%x, data=0x%08x, style=0x%08x", value.type, value.data,
                  value_source_resid);
      }
    }

    if (value.type == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
    
    
      // Walk through the default style values looking for the requested attribute.
      const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident);
      if (entry != def_style_attr_finder.end()) {
    
    
        value = AssetManager2::SelectedValue(*default_style_bag, *entry);
        value.flags |= def_style_theme_flags;
        value_source_resid = entry->style;
        DEBUG_LOG("-> From def style: type=0x%x, data=0x%08x, style=0x%08x", value.type, value.data,
                  entry->style);
      }
    }

    if (value.type != Res_value::TYPE_NULL) {
    
    
      // Take care of resolving the found resource to its final value.
      auto result = theme->ResolveAttributeReference(value);
      if (UNLIKELY(IsIOError(result))) {
    
    
        return base::unexpected(GetIOError(result.error()));
      }
      DEBUG_LOG("-> Resolved attr: type=0x%x, data=0x%08x", value.type, value.data);
    } else if (value.data != Res_value::DATA_NULL_EMPTY) {
    
    
      // If we still don't have a value for this attribute, try to find it in the theme!
      if (auto attr_value = theme->GetAttribute(cur_ident)) {
    
    
        value = *attr_value;
        DEBUG_LOG("-> From theme: type=0x%x, data=0x%08x", value.type, value.data);

        auto result = assetmanager->ResolveReference(value, true /* cache_value */);
        if (UNLIKELY(IsIOError(result))) {
    
    
          return base::unexpected(GetIOError(result.error()));
        }
        DEBUG_LOG("-> Resolved theme: type=0x%x, data=0x%08x", value.type, value.data);
        // TODO: set value_source_resid for the style in the theme that was used.
      }
    }

    // Deal with the special @null value -- it turns back to TYPE_NULL.
    if (value.type == Res_value::TYPE_REFERENCE && value.data == 0U) {
    
    
      DEBUG_LOG("-> Setting to @null!");
      value.type = Res_value::TYPE_NULL;
      value.data = Res_value::DATA_NULL_UNDEFINED;
      value.cookie = kInvalidCookie;
    }

    DEBUG_LOG("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.type, value.data);

    // Write the final value back to Java.
    out_values[STYLE_TYPE] = value.type;
    out_values[STYLE_DATA] = value.data;
    out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(value.cookie);
    out_values[STYLE_RESOURCE_ID] = value.resid;
    out_values[STYLE_CHANGING_CONFIGURATIONS] = value.flags;
    out_values[STYLE_DENSITY] = value.config.density;
    out_values[STYLE_SOURCE_RESOURCE_ID] = value_source_resid;

    if (value.type != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) {
    
    
      // out_indices must NOT be nullptr.
      out_indices[++indices_idx] = ii;
    }
    out_values += STYLE_NUM_ENTRIES;
  }

  // out_indices must NOT be nullptr.
  out_indices[0] = indices_idx;
  return {
    
    };
}

  14行代码处,GetStyleBag()方法,得到的结果是从主题中设置的属性,或者自己设置的style中得到结果集default_style_bag。如果主题中能取到对应的属性,就不会再去取自己设置的style中取值。如果主题中未设置对应属性,则会去自己设置的style中取值。这个结果集对应着上文说的优先级 3、参数 defStyleAttr和4、参数 defStyleRes
  22行代码处,GetXmlStyleBag()方法,通过布局文件中设置的对应style属性中取到结果集xml_style_bag。这个对应着上文说的优先级 2、XML控件设置的style属性
  27、28行将上面两个结果集再转成BagAttributeFinder类对象,后面会通过该对象,去查询需要查找的属性值。
  29行得到XmlAttributeFinder对象,这个是从布局文件的直接设置的属性中查询对应结果。这个对应着上文说的优先级 1、XML控件里直接设置属性
  接着,是一个循环。attrs_length就是需要查询的属性队列的长度。通过这个循环,将需要查询的数据的值,都设置到TypedArray中的成员变量mData和mIndices中。
  45行,先从xml_attr_finder中搜索,这个就是从布局文件设置的单个属性中查找,如果从这里找到了正确值,可以看到,就不会再从其他地方取值了。所以布局XML控件里直接设置属性,优先级最高。
  56行,如果上面没有取到正确值,则会从xml_style_attr_finder中取值。它对应布局文件中设置的style属性。所以XML控件设置的style属性的优先级排在第二。
  最后会从def_style_attr_finder中取值。它对应着,对应的主题中设置的属性值或者自己定义设置的style。并且上面说了,这俩相比,会先取主题中设置的属性值,也就是参数 defStyleAttr优先级第三、参数 defStyleRes优先级第四。这样就能解释清楚 Android TypedArray简单分析(一)使用 文章中例子取值顺序的问题了。
  代码80行处,如果得到的值是引用类型,还会继续调用theme->ResolveAttributeReference(value)继续解析。
  也可以看到,out_indices中从第1位开始存储属性的序列值。第0位存储的是indices_idx,它就是查找到属性值得数量。
  上面是说的ApplyStyle()方法的整体流程,具体的细节见下文Android 得到主题中对应的属性的结果或自己设置的style中的结果

猜你喜欢

转载自blog.csdn.net/q1165328963/article/details/124516572