Android TypedArray Simple Analysis (2) Source Code Analysis

source code analysis

  Looking down from the obtainStyledAttributes() method of the Context class,

    /**
     * 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);
    }

  First, getTheme is called. The Context is usually an Activity instance, and the getTheme of the ContextThemeWrapper class is called here. This is actually the theme of the current Activity. Activity's Theme can be set in the Manifest file through the Activity's label attribute android:theme, if not set, the theme is set in the Application's label attribute android:theme. If neither of these are set, the default Theme will be selected according to the current target SDK version. The selected default Theme can see the following code:

   /**
     * 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;
    }

  Then call the obtainStyledAttributes() method of the Theme class:

        @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);
            }
        }

  It can be seen that the method obtainStyledAttributes of the internal class ThemeImpl of the ResourcesImpl class is called

       @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;
       }

  This method first initializes TypedArray, and then converts the parameter set into the XmlBlock.Parser class, which corresponds to ResXMLParser in the C++ layer, which is a tool class for parsing binary resource files, and contains all attributes corresponding to the control in the layout xml file value. Finally, call the applyStyle method of the AssetManager class instance mAssets, and finally return the result.

Initialization of TypedArray

First look at the initialization of 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;
        }

  First get it from the cache pool of res, if not, initialize TypedArray. Then set the corresponding attributes, and then call the attrs.resize(len) method to set the length. It can be seen that this length is the length of the parameter attrs from the obtainStyledAttributes() function, which is the set of integer values ​​​​of the attributes to be found.

    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);
            }
        }

  This method is mainly to set the member variables mLength, mData, mDataAddress, mIndices, mIndicesAddress of the TypedArray class. The previous article Simple Analysis of Android TypedArray (1) Use also introduced the members of this class mData and mIndices. It can be seen that the corresponding length of the setting is set according to the size of the attribute. The value of STYLE_NUM_ENTRIES is 7, the length of the mData array is 7*len, and the length of mIndices is len+1. The two arrays mData and mIndices are defined by the runtime.newNonMovableArray method, indicating that the address of the array cannot be moved. Then, the address of the array is obtained through the runtime.addressOf() method to specify the values ​​of mDataAddress and mIndicesAddress. mDataAddress and mIndicesAddress will be passed to the c++ layer as parameters, so that mData and mIndices can be found by address in the c++ layer.

The applyStyle method of AssetManager class instance mAssets

  The next step is to call the applyStyle method of the AssetManager class instance mAssets. You can see that parameters 5 and 6 are indeed mDataAddress and mIndicesAddress of the TypedArray class:

    @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);
        }
    }

  The native method nativeApplyStyle() will be called below. Before calling the local method, the member mParseState of the parser will be passed in as a parameter, which is the address of the ResXMLParser class object in the C++ layer. After entering the C++ layer, the ResXMLParser class object can be found through it. As mentioned above, it contains the values ​​of all attributes corresponding to the control in the layout xml file.
  From here, we can see that Theme is implemented through the member variable ThemeImpl, and ThemeImpl is implemented through its member variable mTheme pointing to the class object of the C++ layer. The member type AssetManager variable mAssets of the ThemeImpl class is realized by pointing to the class object of the C++ layer through its member variable mObject.
  Look at the C++ layer implementation, which is implemented in 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);
}

  It can be seen from this that the AssetManager of the Java layer corresponds to the AssetManager2 class of the C++ layer, and the Theme of the Java layer corresponds to the Theme class of the C++ layer. Also find the corresponding ResXMLParser class object through the parameter xml_parser_ptr,
  and out_values ​​and out_indices are the addresses of the mData and mIndices arrays of the above-mentioned Java class TypedArray class object respectively.
  Going down is to call the ApplyStyle() function, which is in platform\frameworks\base\libs\androidfw\AttributeResolution.cpp, look at it

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 {
    
    };
}

  At the 14th line of code, the GetStyleBag() method obtains the result set default_style_bag from the attributes set in the theme, or the style set by yourself. If the corresponding attribute can be obtained in the theme, it will not take the value in the style set by itself. If the corresponding attribute is not set in the theme, it will go to the style set by itself to get the value. This result set corresponds to the priority 3, parameter defStyleAttr and 4, parameter defStyleRes mentioned above .   At line 22 of the code, the GetXmlStyleBag() method gets the result set xml_style_bag from the corresponding style attribute set in the layout file. This corresponds to the priority 2 mentioned above , and the style attribute 27 and 28 set by the XML control   will convert the above two result sets into a BagAttributeFinder class object, which will be used later to query the attribute value that needs to be found.   Line 29 gets the XmlAttributeFinder object, which is to query the corresponding result from the directly set attributes of the layout file. This corresponds to the priority 1 mentioned above , setting properties directly in the XML control   and then, it is a cycle. attrs_length is the length of the attribute queue to be queried. Through this cycle, the values ​​of the data to be queried are set to the member variables mData and mIndices in TypedArray.   Line 45, first search from xml_attr_finder, which is to search from a single attribute set in the layout file. If you find the correct value from here, you can see that it will not get the value from other places. so the layout




Setting properties directly in the XML control has the highest priority.
  Line 56, if the correct value is not obtained above, the value will be obtained from xml_style_attr_finder. It corresponds to the style attribute set in the layout file. Therefore, the style attribute set by the XML control has the second priority.
  Finally, the value will be taken from def_style_attr_finder. It corresponds to the attribute value set in the corresponding theme or the style defined by yourself. And as mentioned above, comparing the two, the attribute value set in the theme will be taken first, that is, the parameter defStyleAttr has the third priority, and the parameter defStyleRes has the fourth priority. This can explain the simple analysis of Android TypedArray (1) The problem of the value order of the examples in the article.
  At line 80 of the code, if the obtained value is a reference type, it will continue to call theme->ResolveAttributeReference(value) to continue parsing.
  It can also be seen that the sequence value of the attribute is stored from the first bit in out_indices. The 0th bit stores indices_idx, which is the number of attribute values ​​found.
  The above is the overall process of the ApplyStyle() method. For details, see below. Android gets the results of the corresponding attributes in the theme or the results in the style you set .

Guess you like

Origin blog.csdn.net/q1165328963/article/details/124516572
Recommended