Android 重學(xué)系列 資源的查找

前言

上一篇文章已經(jīng)聊了資源系統(tǒng)的初始化,本文就來看看資源適合查找到的。

如果遇到問題,歡迎在下面這個地址下留言:http://www.itdecent.cn/p/b153d63d60b3

正文

資源的查找,Xml布局文件的讀取

到這里AssetManager就生成了,我們來看看資源是怎么查找的。我們來看看之前我沒有深入探討的解析Xml方法。
文件:/frameworks/base/core/java/android/content/res/Resources.java

    public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
        return loadXmlResourceParser(id, "layout");
    }

    XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
            throws NotFoundException {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValue(id, value, true);
            if (value.type == TypedValue.TYPE_STRING) {
                return impl.loadXmlResourceParser(value.string.toString(), id,
                        value.assetCookie, type);
            }
           ...
        } finally {
            releaseTempTypedValue(value);
        }
    }

這里大致分為兩個步驟:

  • 1.通過ResourcesImpl.getValue獲取當(dāng)前resId對應(yīng)的資源,設(shè)置到TypedValue
  • 2.如果當(dāng)前返回的數(shù)據(jù)類型是String,則直接調(diào)用loadXmlResourceParser 讀取資源具體的內(nèi)容中,如Xml布局文件。
  • 3.緩存當(dāng)前資源

查找resId對應(yīng)的資源

文件:/frameworks/base/core/java/android/content/res/ResourcesImpl.java

    void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
            throws NotFoundException {
        boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
        if (found) {
            return;
        }
        ...
    }

能看到這里面調(diào)用了AssetManager的getResourceValue方法。

文件:/frameworks/base/core/java/android/content/res/AssetManager.java

    boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
            boolean resolveRefs) {
        synchronized (this) {
            ensureValidLocked();
            final int cookie = nativeGetResourceValue(
                    mObject, resId, (short) densityDpi, outValue, resolveRefs);

            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
            }
            return true;
        }
    }

在這里面會調(diào)用nativeGetResourceValue獲取到Asset的cookie,從底層復(fù)制數(shù)據(jù)到TypedValue中。如果判斷到TypedValue中解析出來的數(shù)據(jù)是String類型,則從全局字符串字符串中獲取對應(yīng)TypeValue中data對應(yīng)的字符串?dāng)?shù)據(jù)。

那么我們可以推測,如果當(dāng)前的資源類型是一個字符串(說明找到),那么nativeGetResourceValue方法實際上并不會賦值給outValue.string。因為可以通過字符串資源池更加快速查找字符串的方法。

獲取native層資源id對應(yīng)的資源

文件:/frameworks/base/core/jni/android_util_AssetManager.cpp

using ApkAssetsCookie = int32_t;

enum : ApkAssetsCookie {
  kInvalidCookie = -1,
};
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
                                   jshort density, jobject typed_value,
                                   jboolean resolve_references) {
  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
  Res_value value;
  ResTable_config selected_config;
  uint32_t flags;
  ApkAssetsCookie cookie =
      assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
                                static_cast<uint16_t>(density), &value, &selected_config, &flags);
  if (cookie == kInvalidCookie) {
    return ApkAssetsCookieToJavaCookie(kInvalidCookie);
  }

  uint32_t ref = static_cast<uint32_t>(resid);
  if (resolve_references) {
    cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &ref);
    if (cookie == kInvalidCookie) {
      return ApkAssetsCookieToJavaCookie(kInvalidCookie);
    }
  }
  return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value);
}

在里面設(shè)計到一個比較核心的結(jié)構(gòu)體ApkAssetsCookie。這個對象是在構(gòu)建動態(tài)資源映射表時候,按照順序遞增加入到packageGroup中。這個結(jié)構(gòu)體能看到,是十分簡單的只包含一個int類型。

換句話說,ApkAssetsCookie對應(yīng)到Java層的Cookie實際上就是指當(dāng)前的資源來源于packageGroup中cookie的index,注意加入邏輯,cookie添加的順序?qū)嶋H上和package數(shù)據(jù)包添加的順序是一致,也就是說,可以通過這個cookief反向查找package數(shù)據(jù)包。

能看到這里面有一個比較核心的方法,assetmanager->GetResource。

AssetManager2 GetResource

文件:/frameworks/base/libs/androidfw/AssetManager2.cpp

ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
                                           uint16_t density_override, Res_value* out_value,
                                           ResTable_config* out_selected_config,
                                           uint32_t* out_flags) const {
  FindEntryResult entry;
  ApkAssetsCookie cookie =
      FindEntry(resid, density_override, false /* stop_at_first_match */, &entry);
  if (cookie == kInvalidCookie) {
    return kInvalidCookie;
  }

  if (dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) {
    if (!may_be_bag) {
     ...
      return kInvalidCookie;
    }

    // Create a reference since we can't represent this complex type as a Res_value.
    out_value->dataType = Res_value::TYPE_REFERENCE;
    out_value->data = resid;
    *out_selected_config = entry.config;
    *out_flags = entry.type_flags;
    return cookie;
  }

  const Res_value* device_value = reinterpret_cast<const Res_value*>(
      reinterpret_cast<const uint8_t*>(entry.entry) + dtohs(entry.entry->size));
  out_value->copyFrom_dtoh(*device_value);

  // Convert the package ID to the runtime assigned package ID.
  entry.dynamic_ref_table->lookupResourceValue(out_value);

  *out_selected_config = entry.config;
  *out_flags = entry.type_flags;
  return cookie;
}

這個方法中包含了兩種情況:

  • 1.當(dāng)當(dāng)前的引用屬于比較復(fù)雜的時候,是一個引用,并非真實的資源數(shù)據(jù),則data返回的是當(dāng)前引用中的resid。

  • 2.通過FindEntry,查找每一個資源的entry,還記得上面說過的,每一個entry會包含真實的資源數(shù)據(jù),這個時候out_value會獲取當(dāng)前entry中的真實數(shù)據(jù)。接著會覆蓋當(dāng)前的資源id,以及相關(guān)的配置等信息。

FindEntry查找資源Entry

ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
                                         bool /*stop_at_first_match*/,
                                         FindEntryResult* out_entry) const {
  // Might use this if density_override != 0.
  ResTable_config density_override_config;

  // Select our configuration or generate a density override configuration.
  const ResTable_config* desired_config = &configuration_;
  if (density_override != 0 && density_override != configuration_.density) {
    density_override_config = configuration_;
    density_override_config.density = density_override;
    desired_config = &density_override_config;
  }

  if (!is_valid_resid(resid)) {
    LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
    return kInvalidCookie;
  }

  const uint32_t package_id = get_package_id(resid);
  const uint8_t type_idx = get_type_id(resid) - 1;
  const uint16_t entry_idx = get_entry_id(resid);

  const uint8_t package_idx = package_ids_[package_id];
  if (package_idx == 0xff) {
  ...
    return kInvalidCookie;
  }

  const PackageGroup& package_group = package_groups_[package_idx];
  const size_t package_count = package_group.packages_.size();

  ApkAssetsCookie best_cookie = kInvalidCookie;
  const LoadedPackage* best_package = nullptr;
  const ResTable_type* best_type = nullptr;
  const ResTable_config* best_config = nullptr;
  ResTable_config best_config_copy;
  uint32_t best_offset = 0u;
  uint32_t type_flags = 0u;

  // If desired_config is the same as the set configuration, then we can use our filtered list
  // and we don't need to match the configurations, since they already matched.
  const bool use_fast_path = desired_config == &configuration_;

  for (size_t pi = 0; pi < package_count; pi++) {
    const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
    const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_;
    ApkAssetsCookie cookie = package_group.cookies_[pi];

    // If the type IDs are offset in this package, we need to take that into account when searching
    // for a type.
    const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx);
    if (UNLIKELY(type_spec == nullptr)) {
      continue;
    }

    uint16_t local_entry_idx = entry_idx;

    // If there is an IDMAP supplied with this package, translate the entry ID.
    if (type_spec->idmap_entries != nullptr) {
      if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) {
        // There is no mapping, so the resource is not meant to be in this overlay package.
        continue;
      }
    }

    type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);

    // If the package is an overlay, then even configurations that are the same MUST be chosen.
    const bool package_is_overlay = loaded_package->IsOverlay();

    const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
    if (use_fast_path) {
      const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations;
      const size_t type_count = candidate_configs.size();
      for (uint32_t i = 0; i < type_count; i++) {
        const ResTable_config& this_config = candidate_configs[i];

        // We can skip calling ResTable_config::match() because we know that all candidate
        // configurations that do NOT match have been filtered-out.
        if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
            (package_is_overlay && this_config.compare(*best_config) == 0)) {
          const ResTable_type* type_chunk = filtered_group.types[i];
          const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
          if (offset == ResTable_type::NO_ENTRY) {
            continue;
          }

          best_cookie = cookie;
          best_package = loaded_package;
          best_type = type_chunk;
          best_config = &this_config;
          best_offset = offset;
        }
      }
    } else {

      const auto iter_end = type_spec->types + type_spec->type_count;
      for (auto iter = type_spec->types; iter != iter_end; ++iter) {
        ResTable_config this_config;
        this_config.copyFromDtoH((*iter)->config);

        if (this_config.match(*desired_config)) {
          if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
              (package_is_overlay && this_config.compare(*best_config) == 0)) {
            const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
            if (offset == ResTable_type::NO_ENTRY) {
              continue;
            }

            best_cookie = cookie;
            best_package = loaded_package;
            best_type = *iter;
            best_config_copy = this_config;
            best_config = &best_config_copy;
            best_offset = offset;
          }
        }
      }
    }
  }

  if (UNLIKELY(best_cookie == kInvalidCookie)) {
    return kInvalidCookie;
  }

  const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
  if (UNLIKELY(best_entry == nullptr)) {
    return kInvalidCookie;
  }

  out_entry->entry = best_entry;
  out_entry->config = *best_config;
  out_entry->type_flags = type_flags;
  out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
  out_entry->entry_string_ref =
      StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
  out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
  return best_cookie;
}

在這里,我們先回顧一下資源ID的組成:

資源ID:0xPPTTEEEE 。最高兩位PP是指PackageID,一般編譯之后,應(yīng)用資源包是0x7f,系統(tǒng)資源包是0x01,而第三方資源包,則是從0x02開始逐個遞增。接下來的兩位TT,代表著當(dāng)前資源類型id,如anim文件夾下的資源就是0x01,組合起來就是0x7f01.最后四位是指資源entryID,是指的資源每一項對應(yīng)的id,可能是0000,一般是按照資源編譯順序遞增,如果是0001,則當(dāng)前資源完整就是0x7f010001.

而這個方法就需要尋找通過資源id去尋找正確的資源。步驟如下:

  • 1.解析當(dāng)前資源id中packageID,typeID,entryID
  • 2.獲取AssetManager2中的packageID對應(yīng)的packageGroup,在這個packageGroup中,根據(jù)typeID尋找對應(yīng)資源類型。如果發(fā)現(xiàn)typeSpec中有idmap說明有id需要被覆蓋,就嘗試通過Loadedmap::Lookup,轉(zhuǎn)化一下entryID(從IdEntryMap的數(shù)組獲取對應(yīng)entry數(shù)組中的id),接著從ConfiguredPackage獲取到對應(yīng)的package數(shù)據(jù)。

接下來有兩種情況,一種是和原來的配置config一致,一種是不一致。

  • 1.一致的情況下,在ConfiguredPackage中存放著在根據(jù)config已經(jīng)過濾好的資源列表,直接從里面循環(huán)直接拿到對應(yīng)資源Type,并調(diào)用方法GetEntryOffset根據(jù)資源entryId獲取對應(yīng)的資源entry對象。

    1. 不一致的情況下,則循環(huán)typeSpec中映射好的資源關(guān)系,先尋找合適的config接著在嘗試尋找有沒有對應(yīng)的entryID。
  • 最后確定已經(jīng)存在了資源的存在,則會通過當(dāng)前的資源類型以及資源類型中的偏移數(shù)組通過方法GetEntryFromOffset獲取對應(yīng)的entry。

最后返回當(dāng)前的cookie。

通過id尋找是否存在對應(yīng)的entry

文件;/frameworks/base/libs/androidfw/LoadedArsc.cpp

uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) {

  const size_t entry_count = dtohl(type_chunk->entryCount);
  const size_t offsets_offset = dtohs(type_chunk->header.headerSize);

...

  const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
      reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
  return dtohl(entry_offsets[entry_index]);
}

能看到通過獲取資源ResTable_type起始地址+ResTable_type的頭部大小+頭部起點地址,來找到entry偏移數(shù)組。


image.png

通過偏移數(shù)組中的結(jié)果來確定當(dāng)前的entry是否存在。

GetEntryFromOffset 查找具體的內(nèi)容

const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk,
                                                        uint32_t offset) {
...
  return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) +
                                                 offset + dtohl(type_chunk->entriesStart));
}

因為在type中記錄對應(yīng)entry中數(shù)據(jù)的偏移量,因此,可以通過簡單的相加找到對應(yīng)的地址。


image.png

而entry當(dāng)中就有一個Res_value對象.這個對象保存著真實的數(shù)據(jù),最后會通過CopyValue的方法,把Res_value.data拷貝到TypeValue中。此時就擁有了當(dāng)前資源真實數(shù)據(jù)。換到當(dāng)前情景就是指,找到了布局文件,layout/xxx.xml字符串對應(yīng)的index。

通過cookie以及解析資源信息嘗試查找非Asset資源中具體內(nèi)容

上一個步驟中已經(jīng)準備好了資源包對應(yīng)的cookie,確認了資源的存在以及位置,可以嘗試的著讀取數(shù)據(jù)。

文件:/frameworks/base/core/java/android/content/res/ResourcesImpl.java

    XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
            @NonNull String type)
            throws NotFoundException {
        if (id != 0) {
            try {
                synchronized (mCachedXmlBlocks) {
                    final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
                    final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
                    final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
                    // First see if this block is in our cache.
                    final int num = cachedXmlBlockFiles.length;
                    for (int i = 0; i < num; i++) {
                        if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
                                && cachedXmlBlockFiles[i].equals(file)) {
                            return cachedXmlBlocks[i].newParser();
                        }
                    }

                   
                    final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
                    if (block != null) {
                        final int pos = (mLastCachedXmlBlockIndex + 1) % num;
                        mLastCachedXmlBlockIndex = pos;
                        final XmlBlock oldBlock = cachedXmlBlocks[pos];
                        if (oldBlock != null) {
                            oldBlock.close();
                        }
                        cachedXmlBlockCookies[pos] = assetCookie;
                        cachedXmlBlockFiles[pos] = file;
                        cachedXmlBlocks[pos] = block;
                        return block.newParser();
                    }
                }
            } catch (Exception e) {
               ...
            }
        }

        ...
    }

能看見在整個ResourcesImpl中會對所有加載過的Xml文件有一層mCachedXmlBlockFiles緩存,如果找不到則會嘗試著通過openXmlBlockAsset從native查找數(shù)據(jù)。最后會通過XmlBlock生成解析器。

我們看看AssetManager的openXmlBlockAsset方法。

    @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
        Preconditions.checkNotNull(fileName, "fileName");
        synchronized (this) {
            ensureOpenLocked();
            final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
            if (xmlBlock == 0) {
                throw new FileNotFoundException("Asset XML file: " + fileName);
            }
            final XmlBlock block = new XmlBlock(this, xmlBlock);
            incRefsLocked(block.hashCode());
            return block;
        }
    }

該方法會調(diào)用native方法 nativeOpenXmlAsset。這個方法最后會獲取native層數(shù)據(jù)塊對應(yīng)的地址,并且交給XmlBlock進行操控。

文件:/frameworks/base/core/jni/android_util_AssetManager.cpp

static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie,
                                jstring asset_path) {
  ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
  ScopedUtfChars asset_path_utf8(env, asset_path);
...
  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
  std::unique_ptr<Asset> asset;
  if (cookie != kInvalidCookie) {
    asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
  } else {
    asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie);
  }
  ....

  const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);

  std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
  status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
  asset.reset();
 ...
  return reinterpret_cast<jlong>(xml_tree.release());
}

這里大致上分為如下幾個步驟:

  • 1.通過OpenNonAsset讀取資源名稱對應(yīng)的Asset
  • 2.獲取AssetManager2的動態(tài)映射表,并且把cookie對應(yīng)的動態(tài)映射表轉(zhuǎn)化為ResXMLTree,讀取asset中對應(yīng)的數(shù)據(jù),設(shè)置到ResXMLTree中。等待解析。

OpenNonAsset讀取資源名稱對應(yīng)的Asset

文件:/frameworks/base/libs/androidfw/AssetManager2.cpp
這個方法是要打開所有非Asset資源對象,這種資源還是以zip的entry方式讀取出來。

std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
                                                   Asset::AccessMode mode,
                                                   ApkAssetsCookie* out_cookie) const {
  for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
    std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
    if (asset) {
      if (out_cookie != nullptr) {
        *out_cookie = i;
      }
      return asset;
    }
  }

  if (out_cookie != nullptr) {
    *out_cookie = kInvalidCookie;
  }
  return {};
}

循環(huán)每一個AssetManager2管理的ApkAsset對象,獲取對應(yīng)資源Asset。此時的Asset,就是之前我們nativeLoad的時候解析resource.arsc生成的對象。

而方法名中的Asset的這個Asset并非是native層的解析resource.arsc的Asset資源,而是指Asset文件夾

ApkAssets::Open

std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
  CHECK(zip_handle_ != nullptr);

  ::ZipString name(path.c_str());
  ::ZipEntry entry;
  int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
 ...
  if (entry.method == kCompressDeflated) {
    std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
  ....
    std::unique_ptr<Asset> asset =
        Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
  ...
    return asset;
  } else {
    std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
    ...

    std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), mode);
   ...
    return asset;
  }
}

這里的邏輯其實和之前說過解包resource.arsc的邏輯一樣。注意這里這個方法名也叫FindEntry,不過找的是zip包中壓縮單位,而不是資源數(shù)據(jù)中的資源entry。這樣就能找到資源的layout布局文件,在資源目錄下的數(shù)據(jù)。

XmlBlock生成解析器

    XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
        mAssets = assets;
        mNative = xmlBlock;
        mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
    }

此時XmlBlock會持有之前從zip包中解析出來的xmlBlock,以及全局字符串,方便之后解析Xml文件。

接下來就是解析一個樹狀的數(shù)據(jù)結(jié)構(gòu),獲取里面所有的屬性了,這里就不贅述。

AssetManager查找Asset資源

上面一大段聊了非Asset資源的查找,接下來讓我們看看Asset資源的查找。而方法名中的Asset的這個Asset并非是native層的解析resource.arsc的Asset資源,而是指Asset文件夾。

當(dāng)我們想要打開Asset目錄下資源文件的時候,一般會調(diào)用如下方法:

    public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException {
        Preconditions.checkNotNull(fileName, "fileName");
        synchronized (this) {
            ensureOpenLocked();
            final long asset = nativeOpenAsset(mObject, fileName, accessMode);
            if (asset == 0) {
                throw new FileNotFoundException("Asset file: " + fileName);
            }
            final AssetInputStream assetInputStream = new AssetInputStream(asset);
            incRefsLocked(assetInputStream.hashCode());
            return assetInputStream;
        }
    }

而這個方法最后會調(diào)用native層下的如下方法:

std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie,
                                           Asset::AccessMode mode) const {
  const std::string new_path = "assets/" + filename;
  return OpenNonAsset(new_path, cookie, mode);
}

系統(tǒng)這種方式,設(shè)置了相對路徑。一樣還是從OpenNonAsset中查找數(shù)據(jù)。最后會把Asset返回回去。此時AssetInputStream就會嘗試著操作這個Asset對象,會持有著對應(yīng)zipEntry,生成的FileMap。

當(dāng)我們嘗試著讀取數(shù)據(jù)的時候會調(diào)用AssetInputStream中的read方法:
文件:/frameworks/base/core/jni/android_util_AssetManager.cpp

static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
  Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
  uint8_t b;
  ssize_t res = asset->read(&b, sizeof(b));
  return res == sizeof(b) ? static_cast<jint>(b) : -1;
}

資源屬性的獲取

在資源管理體系中,還有一個很重要的知識點,那就是解析View標簽中寫的屬性。在此之前,想要理解整個資源Theme屬性是如何獲取的,先來看看Theme是如何初始化的。

Theme的初始化

文件:/frameworks/base/core/java/android/app/ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
....
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }
....
}

一般在Activity的onCreate的生命周期,會調(diào)用setTheme設(shè)置從Xml中解析出來的主題Theme。而這個方法最后會調(diào)用到ContextImpl中:

    @Override
    public void setTheme(int resId) {
        synchronized (mSync) {
            if (mThemeResource != resId) {
                mThemeResource = resId;
                initializeTheme();
            }
        }
    }

    private void initializeTheme() {
        if (mTheme == null) {
            mTheme = mResources.newTheme();
        }
        mTheme.applyStyle(mThemeResource, true);
    }

能看到其實整個主題是有一個Theme對象在控制,最后才把resID設(shè)置到Theme對象中進去。
文件:/frameworks/base/core/java/android/content/res/Resources.java

    public final Theme newTheme() {
        Theme theme = new Theme();
        theme.setImpl(mResourcesImpl.newThemeImpl());
        synchronized (mThemeRefs) {
            mThemeRefs.add(new WeakReference<>(theme));

            if (mThemeRefs.size() > mThemeRefsNextFlushSize) {
                mThemeRefs.removeIf(ref -> ref.get() == null);
                mThemeRefsNextFlushSize = Math.max(MIN_THEME_REFS_FLUSH_SIZE,
                        2 * mThemeRefs.size());
            }
        }
        return theme;
    }

在Resources中,能看到每一個主題都換緩存到弱引用下來,方便下次查找。核心方法是實例化了一個ThemeImpl對象。
文件:/frameworks/base/core/java/android/content/res/ResourcesImpl.java

    ThemeImpl newThemeImpl() {
        return new ThemeImpl();
    }

    public class ThemeImpl {
        /**
         * Unique key for the series of styles applied to this theme.
         */
        private final Resources.ThemeKey mKey = new Resources.ThemeKey();

        @SuppressWarnings("hiding")
        private final AssetManager mAssets;
        private final long mTheme;

        /**
         * Resource identifier for the theme.
         */
        private int mThemeResId = 0;

        /*package*/ ThemeImpl() {
            mAssets = ResourcesImpl.this.mAssets;
            mTheme = mAssets.createTheme();
        }
...
}

文件:/frameworks/base/core/java/android/content/res/AssetManager.java

    long createTheme() {
        synchronized (this) {
            ensureValidLocked();
            long themePtr = nativeThemeCreate(mObject);
            incRefsLocked(themePtr);
            return themePtr;
        }
    }

調(diào)用native方法實例化底層的Theme對象.

文件;/frameworks/base/core/jni/android_util_AssetManager.cpp

static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
  return reinterpret_cast<jlong>(assetmanager->NewTheme().release());
}
std::unique_ptr<Theme> AssetManager2::NewTheme() {
  return std::unique_ptr<Theme>(new Theme(this));
}

這樣就對應(yīng)著Java層中ThemeImpl,在native中同樣生成一樣的Theme。

ThemeImpl.applyStyle

文件:/frameworks/base/core/java/android/content/res/ResourcesImpl.java

        void applyStyle(int resId, boolean force) {
            synchronized (mKey) {
                mAssets.applyStyleToTheme(mTheme, resId, force);
                mThemeResId = resId;
                mKey.append(resId, force);
            }
        }

文件:/frameworks/base/core/java/android/content/res/AssetManager.java

    void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) {
        synchronized (this) {
            ensureValidLocked();
            nativeThemeApplyStyle(mObject, themePtr, resId, force);
        }
    }

static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
                                  jint resid, jboolean force) {

  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
  Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
  CHECK(theme->GetAssetManager() == &(*assetmanager));
  (void) assetmanager;
  theme->ApplyStyle(static_cast<uint32_t>(resid), force);
}

可以看到在設(shè)置Theme過程中,核心方法是Theme的ApplyStyle。

Theme的ApplyStyle

文件:/frameworks/base/libs/androidfw/AssetManager2.cpp

bool Theme::ApplyStyle(uint32_t resid, bool force) {

  const ResolvedBag* bag = asset_manager_->GetBag(resid);

  type_spec_flags_ |= bag->type_spec_flags;

  int last_type_idx = -1;
  int last_package_idx = -1;
  Package* last_package = nullptr;
  ThemeType* last_type = nullptr;

  using reverse_bag_iterator = std::reverse_iterator<const ResolvedBag::Entry*>;
  const auto bag_iter_end = reverse_bag_iterator(begin(bag));
  for (auto bag_iter = reverse_bag_iterator(end(bag)); bag_iter != bag_iter_end; ++bag_iter) {
    const uint32_t attr_resid = bag_iter->key;

    const int package_idx = get_package_id(attr_resid);
    const int type_idx = get_type_id(attr_resid);
    const int entry_idx = get_entry_id(attr_resid);

    if (last_package_idx != package_idx) {
      std::unique_ptr<Package>& package = packages_[package_idx];
      if (package == nullptr) {
        package.reset(new Package());
      }
      last_package_idx = package_idx;
      last_package = package.get();
      last_type_idx = -1;
    }

    if (last_type_idx != type_idx) {
      util::unique_cptr<ThemeType>& type = last_package->types[type_idx];
      if (type == nullptr) {
        type.reset(reinterpret_cast<ThemeType*>(
            calloc(sizeof(ThemeType) + (entry_idx + 1) * sizeof(ThemeEntry), 1)));
        type->entry_count = entry_idx + 1;
      } else if (entry_idx >= type->entry_count) {
        const int new_count = entry_idx + 1;
        type.reset(reinterpret_cast<ThemeType*>(
            realloc(type.release(), sizeof(ThemeType) + (new_count * sizeof(ThemeEntry)))));

        memset(type->entries + type->entry_count, 0,
               (new_count - type->entry_count) * sizeof(ThemeEntry));
        type->entry_count = new_count;
      }
      last_type_idx = type_idx;
      last_type = type.get();
    }

    ThemeEntry& entry = last_type->entries[entry_idx];
    if (force || (entry.value.dataType == Res_value::TYPE_NULL &&
                  entry.value.data != Res_value::DATA_NULL_EMPTY)) {
      entry.cookie = bag_iter->cookie;
      entry.type_spec_flags |= bag->type_spec_flags;
      entry.value = bag_iter->value;
    }
  }
  return true;
}
  • 1.首先先通過resId找到對應(yīng)的bag,通過Bag獲取對應(yīng)的packageID,typeID,entryID
  • 2.檢查當(dāng)前Theme對象中,之前是否緩存了當(dāng)前要查找的packageID對應(yīng)的Package對象,沒有則繼續(xù)啊檢查是否包含對應(yīng)packageID的package對象。都沒有就會創(chuàng)建一個新的Package對象。
  • 3.檢查當(dāng)前的package對象中,是否緩存了當(dāng)前要查找typeID,是否包含對應(yīng)typeID的ThemeType對象,沒有則創(chuàng)建一個新的(大小為entry的大小*typeID),如果typeID超過了當(dāng)前ThemeType的容量,則擴容。
  • 4.根據(jù)entryID,查找ThemeType的entries對應(yīng)index的ThemeEntry,最后把bag中的數(shù)據(jù)賦值進來。

因此在這里面有一個核心方法GetBag。

AssetManager2的GetBag

const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) {
  auto cached_iter = cached_bags_.find(resid);
  if (cached_iter != cached_bags_.end()) {
    return cached_iter->second.get();
  }

  FindEntryResult entry;
  ApkAssetsCookie cookie =
      FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
  if (cookie == kInvalidCookie) {
    return nullptr;
  }

  if (dtohs(entry.entry->size) < sizeof(ResTable_map_entry) ||
      (dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) == 0) {
    // Not a bag, nothing to do.
    return nullptr;
  }

  const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry.entry);
  const ResTable_map* map_entry =
      reinterpret_cast<const ResTable_map*>(reinterpret_cast<const uint8_t*>(map) + map->size);
  const ResTable_map* const map_entry_end = map_entry + dtohl(map->count);

  child_resids.push_back(resid);

  uint32_t parent_resid = dtohl(map->parent.ident);
  if (parent_resid == 0 || std::find(child_resids.begin(), child_resids.end(), parent_resid)
      != child_resids.end()) {

    const size_t entry_count = map_entry_end - map_entry;
    util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>(
        malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))};
    ResolvedBag::Entry* new_entry = new_bag->entries;
    for (; map_entry != map_entry_end; ++map_entry) {
      uint32_t new_key = dtohl(map_entry->name.ident);

      new_entry->cookie = cookie;
      new_entry->key = new_key;
      new_entry->key_pool = nullptr;
      new_entry->type_pool = nullptr;
      new_entry->value.copyFrom_dtoh(map_entry->value);
      status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
      
      ++new_entry;
    }
    new_bag->type_spec_flags = entry.type_flags;
    new_bag->entry_count = static_cast<uint32_t>(entry_count);
    ResolvedBag* result = new_bag.get();
    cached_bags_[resid] = std::move(new_bag);
    return result;
  }

  entry.dynamic_ref_table->lookupResourceId(&parent_resid);

  const ResolvedBag* parent_bag = GetBag(parent_resid, child_resids);

  const size_t max_count = parent_bag->entry_count + dtohl(map->count);
  util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>(
      malloc(sizeof(ResolvedBag) + (max_count * sizeof(ResolvedBag::Entry))))};
  ResolvedBag::Entry* new_entry = new_bag->entries;

  const ResolvedBag::Entry* parent_entry = parent_bag->entries;
  const ResolvedBag::Entry* const parent_entry_end = parent_entry + parent_bag->entry_count;

  while (map_entry != map_entry_end && parent_entry != parent_entry_end) {
    uint32_t child_key = dtohl(map_entry->name.ident);

    if (child_key <= parent_entry->key) {

      new_entry->cookie = cookie;
      new_entry->key = child_key;
      new_entry->key_pool = nullptr;
      new_entry->type_pool = nullptr;
      new_entry->value.copyFrom_dtoh(map_entry->value);
      status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);

      ++map_entry;
    } else {
      // Take the parent entry as-is.
      memcpy(new_entry, parent_entry, sizeof(*new_entry));
    }

    if (child_key >= parent_entry->key) {
      // Move to the next parent entry if we used it or it was overridden.
      ++parent_entry;
    }
    // Increment to the next entry to fill.
    ++new_entry;
  }


  while (map_entry != map_entry_end) {
    uint32_t new_key = dtohl(map_entry->name.ident);

    new_entry->cookie = cookie;
    new_entry->key = new_key;
    new_entry->key_pool = nullptr;
    new_entry->type_pool = nullptr;
    new_entry->value.copyFrom_dtoh(map_entry->value);
    status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);

    ++map_entry;
    ++new_entry;
  }

  if (parent_entry != parent_entry_end) {

    const size_t num_entries_to_copy = parent_entry_end - parent_entry;
    memcpy(new_entry, parent_entry, num_entries_to_copy * sizeof(*new_entry));
    new_entry += num_entries_to_copy;
  }

  const size_t actual_count = new_entry - new_bag->entries;
  if (actual_count != max_count) {
    new_bag.reset(reinterpret_cast<ResolvedBag*>(realloc(
        new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry)))));
  }

  new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags;
  new_bag->entry_count = static_cast<uint32_t>(actual_count);
  ResolvedBag* result = new_bag.get();
  cached_bags_[resid] = std::move(new_bag);
  return result;
}
  • 1.首先通過FindEntry找到對應(yīng)的FindEntryResult對象,里面包含著ResTable_entry,相關(guān)的配置,以及資源池中指向的字符串。
struct FindEntryResult {
//查找到的entry結(jié)果
  const ResTable_entry* entry;
//配置
  ResTable_config config;

  uint32_t type_flags;
//動態(tài)映射表
  const DynamicRefTable* dynamic_ref_table;

//entry對應(yīng)的類型名
  StringPoolRef type_string_ref;
//entry名
  StringPoolRef entry_string_ref;
};
  • 2.如果當(dāng)前的ResTable_entry不是ResTable_map_entry對象則返回,通過大小的來確定。并且獲取后面ResTable_map對象。ResTable_map_entry的數(shù)據(jù)結(jié)構(gòu)如下:
struct ResTable_map_entry : public ResTable_entry
{
    //指向父ResTable_map_entry的引用
    ResTable_ref parent;
    // 后面ResTable_map的數(shù)量
    uint32_t count;
};

ResTable_map的位置計算如下:

ResTable_map = ResTable_map_entry的起點+ResTable_map_entry大小

剛好在ResTable_map_entry后面。ResTable_map的數(shù)據(jù)結(jié)構(gòu)如下:

struct ResTable_map
{
    // The resource identifier defining this mapping's name.  For attribute
    // resources, 'name' can be one of the following special resource types
    // to supply meta-data about the attribute; for all other resource types
    // it must be an attribute resource.
    ResTable_ref name;

....
    // This mapping's value.
    Res_value value;
};

能看到ResTable_map才是真正持有Res_value的對象。ResTable_map里面包含著鍵值對。分別指的是當(dāng)前資源當(dāng)前的命名以及資源中的值。

  • 3.接下來分為兩種情況,一種是ResTable_map_entry不包含父資源或者已經(jīng)在原來child_resids找到了,一種的是包含父資源。
    1 .當(dāng)不包含父資源的時候,則循環(huán)ResTable_map中的引用,把里面的包含的資源真實數(shù)據(jù),cookie都設(shè)置到ResolveBag的Entry指針中。這樣ResolveBag就包含了當(dāng)前ResTable_entry中所有的數(shù)據(jù)。一般的一個ResolveBag就包含一個ResTable_map。
    2 .當(dāng)包含父資源的時候,將會通過動態(tài)映射表去查找對應(yīng)的父資源的packageID,遞歸當(dāng)前的方法,找到所有父資源的數(shù)據(jù),獲取父ResolveBag。當(dāng)遇到每一個子資源和父資源沖突,則讓子資源覆蓋父資源。

這樣就把所有的父子資源壓縮到一起交給了ResolveBag中保管了,并且緩存到cached_bags_中。

查找當(dāng)前主題下的屬性的值常用方法

接下來,讓我們探索一下,當(dāng)我們編寫自定義View,以及自定義屬性時候常用三種在當(dāng)前主題下查找對應(yīng)的資源屬性中的值。

public TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {
            return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
        }

        TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
                @NonNull int[] values,
                @NonNull int[] attrs) {
            synchronized (mKey) {
                final int len = attrs.length;
                if (values == null || len != values.length) {
                    throw new IllegalArgumentException(
                            "Base attribute values must the same length as attrs");
                }

                final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
                mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
                array.mTheme = wrapper;
                array.mXml = null;
                return array;
            }
        }

        boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
            synchronized (mKey) {
                return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
            }
        }

而這兩個方法都會調(diào)用ThemeImpl中的下面方法。

        TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
                @NonNull int[] values,
                @NonNull int[] attrs) {
            synchronized (mKey) {
                final int len = attrs.length;
                if (values == null || len != values.length) {
                    throw new IllegalArgumentException(
                            "Base attribute values must the same length as attrs");
                }

                final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
                mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
                array.mTheme = wrapper;
                array.mXml = null;
                return array;
            }
        }


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

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

其核心原理是十分相似。首先在ActivityThread的handleLaunch階段,會設(shè)置一個Theme。這個Theme就是ThemeImpl,同時會在native層中生成一個Theme對象。

最后分別調(diào)用AssetManager的resolveAttrs以及applyStyle方法。

    void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
            @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
            long outIndicesAddress) {
        synchronized (this) {

            nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
                    parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
                    outIndicesAddress);
        }
    }

    boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
            @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues,
            @NonNull int[] outIndices) {
        synchronized (this) {

            return nativeResolveAttrs(mObject,
                    themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices);
        }
    }

    boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
            boolean resolveRefs) {
        Preconditions.checkNotNull(outValue, "outValue");
        synchronized (this) {
            ensureValidLocked();
            final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue,
                    resolveRefs);
            if (cookie <= 0) {
                return false;
            }

            // Convert the changing configurations flags populated by native code.
            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
            }
            return true;
        }
    }

obtainStyledAttributes 工作原理

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

核心方法調(diào)用ApplyStyle
文件;/frameworks/base/libs/androidfw/AttributeResolution.cpp

void 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) {


  AssetManager2* assetmanager = theme->GetAssetManager();
  ResTable_config config;
  Res_value value;

  int indices_idx = 0;

  uint32_t def_style_flags = 0u;
  if (def_style_attr != 0) {
    Res_value value;
    if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
      if (value.dataType == Res_value::TYPE_REFERENCE) {
        def_style_resid = value.data;
      }
    }
  }

  // Retrieve the style resource ID associated with the current XML tag's style attribute.
  uint32_t style_resid = 0u;
  uint32_t style_flags = 0u;
  if (xml_parser != nullptr) {
    ssize_t idx = xml_parser->indexOfStyle();
    if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) {
      if (value.dataType == value.TYPE_ATTRIBUTE) {
        // Resolve the attribute with out theme.
        if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) {
          value.dataType = Res_value::TYPE_NULL;
        }
      }

      if (value.dataType == value.TYPE_REFERENCE) {
        style_resid = value.data;
      }
    }
  }

  const ResolvedBag* default_style_bag = nullptr;
  if (def_style_resid != 0) {
    default_style_bag = assetmanager->GetBag(def_style_resid);
    if (default_style_bag != nullptr) {
      def_style_flags |= default_style_bag->type_spec_flags;
    }
  }

  BagAttributeFinder def_style_attr_finder(default_style_bag);

  const ResolvedBag* xml_style_bag = nullptr;
  if (style_resid != 0) {
    xml_style_bag = assetmanager->GetBag(style_resid);
    if (xml_style_bag != nullptr) {
      style_flags |= xml_style_bag->type_spec_flags;
    }
  }

  BagAttributeFinder xml_style_attr_finder(xml_style_bag);

  XmlAttributeFinder xml_attr_finder(xml_parser);

  for (size_t ii = 0; ii < attrs_length; ii++) {
    const uint32_t cur_ident = attrs[ii];

    ApkAssetsCookie cookie = kInvalidCookie;
    uint32_t type_set_flags = 0u;

    value.dataType = Res_value::TYPE_NULL;
    value.data = Res_value::DATA_NULL_UNDEFINED;
    config.density = 0;

    const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
    if (xml_attr_idx != xml_attr_finder.end()) {
      xml_parser->getAttributeValue(xml_attr_idx, &value);
    }

    if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
      const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident);
      if (entry != xml_style_attr_finder.end()) {
        cookie = entry->cookie;
        type_set_flags = style_flags;
        value = entry->value;
      }
    }

    if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
      const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident);
      if (entry != def_style_attr_finder.end()) {
        cookie = entry->cookie;
        type_set_flags = def_style_flags;
        value = entry->value;
      }
    }

    uint32_t resid = 0u;
    if (value.dataType != Res_value::TYPE_NULL) {
      ApkAssetsCookie new_cookie =
          theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
      if (new_cookie != kInvalidCookie) {
        cookie = new_cookie;
      }

    } else if (value.data != Res_value::DATA_NULL_EMPTY) {
      ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
      if (new_cookie != kInvalidCookie) {
        new_cookie =
            assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
        if (new_cookie != kInvalidCookie) {
          cookie = new_cookie;
        }
      }
    }

    if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
      value.dataType = Res_value::TYPE_NULL;
      value.data = Res_value::DATA_NULL_UNDEFINED;
      cookie = kInvalidCookie;
    }

    out_values[STYLE_TYPE] = value.dataType;
    out_values[STYLE_DATA] = value.data;
    out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
    out_values[STYLE_RESOURCE_ID] = resid;
    out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
    out_values[STYLE_DENSITY] = config.density;

    if (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) {
      indices_idx++;
      out_indices[indices_idx] = ii;
    }

    out_values += STYLE_NUM_ENTRIES;
  }

  out_indices[0] = indices_idx;
}
  • 1.首先通過GetAttribute檢查當(dāng)前傳進來的默認的屬性,如果當(dāng)前傳進來了XML當(dāng)前塊的解析對象,則獲取style的位置之后,嘗試獲取style中的值,是引用則記錄當(dāng)前的id。如果是Attribute則通過GetAttribute獲取值。
  • 2.如果默認的style的id不為0,則獲取styleID對應(yīng)的ResolveBag作為默認對象。
  • 3.此時獲取從上面?zhèn)飨聛淼腶ttr引用指針,在這個情況一般是值R.styleable.xxx的一個數(shù)組,里面含有大量的屬性。開始循環(huán)傳下來的attr數(shù)組,逐一查找對應(yīng)數(shù)組中每一個資源值對應(yīng)的index。
  • 4.如果xml_attr_finder找到對應(yīng)的index,則通過xml_parser->getAttributeValue(xml_attr_idx, &value);解析里面內(nèi)容,并且拷貝到outValue中。記住如果是引用,則會通過ResolveReference方法解包引用,通過GetResources找到真正的值。如果數(shù)據(jù)為空,則從默認的style中讀取。

能看到這個過程中有2個核心方法我們未曾接觸過,讓我們著重看看里面做了什么事情:

  • GetAttribute
  • xml_parser->getAttributeValue

GetAttribute 獲取屬性

ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value,
                                    uint32_t* out_flags) const {
  int cnt = 20;

  uint32_t type_spec_flags = 0u;

  do {
    const int package_idx = get_package_id(resid);
    const Package* package = packages_[package_idx].get();
    if (package != nullptr) {
      // The themes are constructed with a 1-based type ID, so no need to decrement here.
      const int type_idx = get_type_id(resid);
      const ThemeType* type = package->types[type_idx].get();
      if (type != nullptr) {
        const int entry_idx = get_entry_id(resid);
        if (entry_idx < type->entry_count) {
          const ThemeEntry& entry = type->entries[entry_idx];
          type_spec_flags |= entry.type_spec_flags;

          if (entry.value.dataType == Res_value::TYPE_ATTRIBUTE) {
            if (cnt > 0) {
              cnt--;
              resid = entry.value.data;
              continue;
            }
            return kInvalidCookie;
          }

          // @null is different than @empty.
          if (entry.value.dataType == Res_value::TYPE_NULL &&
              entry.value.data != Res_value::DATA_NULL_EMPTY) {
            return kInvalidCookie;
          }

          *out_value = entry.value;
          *out_flags = type_spec_flags;
          return entry.cookie;
        }
      }
    }
    break;
  } while (true);
  return kInvalidCookie;
}

能看到實際上很簡單,在native層已經(jīng)保存了當(dāng)前主題中所有xml的映射關(guān)系,因此可以通過當(dāng)前Package,ThemeType找到對應(yīng)的ResTable_entry中的數(shù)據(jù)。因此我們可以得知,在obtainStyledAttributes中,設(shè)置默認的屬性不是什么都可以設(shè)置,需要設(shè)置Theme中有的才能正常運作。

getAttributeValue解析Xml數(shù)據(jù)塊中的屬性值

文件:/frameworks/base/libs/androidfw/ResourceTypes.cpp

ssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const
{
    if (mEventCode == START_TAG) {
        const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
        if (idx < dtohs(tag->attributeCount)) {
            const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
                (((const uint8_t*)tag)
                 + dtohs(tag->attributeStart)
                 + (dtohs(tag->attributeSize)*idx));
            outValue->copyFrom_dtoh(attr->typedValue);
            if (mTree.mDynamicRefTable != NULL &&
                    mTree.mDynamicRefTable->lookupResourceValue(outValue) != NO_ERROR) {
                return BAD_TYPE;
            }
            return sizeof(Res_value);
        }
    }
    return BAD_TYPE;
}

十分簡單,就是通過當(dāng)前保存的解析樹中ResXMLTree_attribute對應(yīng)index的屬性值。還有一個resolveAttribute的方法本質(zhì)上還是從ApplyStyle中查找方法。

到這里我們已經(jīng)理解了obtainStyledAttributes的工作流程,讓我們看看resolveAttributes。

resolveAttributes的工作原理

這個方法最終會調(diào)用native的ResolveAttrs方法。
文件:/frameworks/base/libs/androidfw/AttributeResolution.cpp

bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
                  uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
                  size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
  AssetManager2* assetmanager = theme->GetAssetManager();
  ResTable_config config;
  Res_value value;

  int indices_idx = 0;

  // Load default style from attribute, if specified...
  uint32_t def_style_flags = 0u;
  if (def_style_attr != 0) {
    Res_value value;
    if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
      if (value.dataType == Res_value::TYPE_REFERENCE) {
        def_style_res = value.data;
      }
    }
  }

  const ResolvedBag* default_style_bag = nullptr;
  if (def_style_res != 0) {
    default_style_bag = assetmanager->GetBag(def_style_res);
    if (default_style_bag != nullptr) {
      def_style_flags |= default_style_bag->type_spec_flags;
    }
  }

  BagAttributeFinder def_style_attr_finder(default_style_bag);

  for (size_t ii = 0; ii < attrs_length; ii++) {
    const uint32_t cur_ident = attrs[ii];

    ApkAssetsCookie cookie = kInvalidCookie;
    uint32_t type_set_flags = 0;

    value.dataType = Res_value::TYPE_NULL;
    value.data = Res_value::DATA_NULL_UNDEFINED;
    config.density = 0;

    if (src_values_length > 0 && src_values[ii] != 0) {
      value.dataType = Res_value::TYPE_ATTRIBUTE;
      value.data = src_values[ii];
    } else {
      const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident);
      if (entry != def_style_attr_finder.end()) {
        cookie = entry->cookie;
        type_set_flags = def_style_flags;
        value = entry->value;
      }
    }

    uint32_t resid = 0;
    if (value.dataType != Res_value::TYPE_NULL) {
      ApkAssetsCookie new_cookie =
          theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
      if (new_cookie != kInvalidCookie) {
        cookie = new_cookie;
      }
    } 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!
      ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
      if (new_cookie != kInvalidCookie) {
        new_cookie =
            assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
        if (new_cookie != kInvalidCookie) {
          cookie = new_cookie;
        }
      }
    }

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

    if (out_indices != nullptr &&
        (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) {
      indices_idx++;
      out_indices[indices_idx] = ii;
    }

    out_values += STYLE_NUM_ENTRIES;
  }

  if (out_indices != nullptr) {
    out_indices[0] = indices_idx;
  }
  return true;
}

這里的邏輯和上面的obtainStyledAttributes十分相似。唯一不同的是,obtainStyledAttributes解析的是Xml中的屬性。而這里不需要解析Xml的屬性,而是直接通過BagAttributeFinder查找有沒有對應(yīng)的屬性。obtainStyledAttributes當(dāng)沒有在Xml中找到也是通過BagAttributeFinder去查找默認的屬性。而這個方法本質(zhì)上就是ResolvedBag這個數(shù)組指針,迭代查找Theme中所有的屬性中的值。

  mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);

resolveAttributes在Java層并沒有傳遞默認的style以及attr,因此獲取的是當(dāng)前values中index對應(yīng)的屬性值。我之前試著使用這個方法獲取的時候,也是出現(xiàn)了問題,找錯了方法,看了源碼之后才明白是怎么回事。

總結(jié)

總結(jié)資源查找的原理。對于AssetManager來說,資源大致分為兩類:

  • 1.非Asset文件夾下的資源
  • 2.Asset文件夾下的資源
  • 3.查找Theme中的屬性

對于非Asset文件夾下的資源來說,查找過程一般遵循如下流程:

  • 1.解析資源ID,根據(jù)packageID從package_groups中獲取到對應(yīng)的PackageGroup,接著獲取每一個Group當(dāng)中對應(yīng)的LoadPackage對象。

  • 2.LoadPackage對象中保存著TypeSpec對象。這個對象保存著資源類型和資源類型內(nèi)容之間的映射關(guān)系。Android系統(tǒng)為了加速資源加載提前把當(dāng)前資源環(huán)境一直的資源另外放置在一個FilteredConfigGroup中。一旦發(fā)現(xiàn)是一致的環(huán)境則從這個快速通道進行查找,否則則進行全局的遍歷。最后通過GetEntryFromOffset找到偏移數(shù)組并且賦值到TypedValue中

  • 3.最后就可以從TypedValue讀取數(shù)據(jù)。如果是布局這種大型文件,就會保存到native的Xml的解析器中,通過mmap的方式映射讀取內(nèi)存數(shù)據(jù)。

對于Asset文件夾下的文件來說,查找過程很簡單一般遵循如下步驟:

  • 1.為路徑新增一個asset的路徑前綴。
  • 2.把zip數(shù)據(jù)流交給AssetInputStream 這個Stream對象處理

對于Theme中的屬性,分為初始化Theme以及從Theme中查找2個步驟:

  • 1.初始化Native下的Theme的時候,會通過ApplyStyle方法先解析當(dāng)前的資源結(jié)構(gòu)。通過FindEntry的方法和解析一個ResTable_entry對象出來.

  • 2.拿到ResTable_entry之后,會通過起點加上該對象的大小能拿到對應(yīng)的ResTable_map對象,而這個對象中就保存著ResTable_value,也就是資源的真實內(nèi)容;接著循環(huán)從子資源一路向父資源查找所有的資源屬性,子資源將會覆蓋掉父資源并壓縮到ResloveBag中;最后保存到cached_bags_緩存中。

  • 3.最后底層的Theme對象,將會層層緩存,根據(jù)packageid,typeid,緩存下來。并遍歷ResloveBag中所有的數(shù)據(jù)出處到Theme的entry數(shù)組中

  • 4.當(dāng)進行查找時候,將會解析當(dāng)前Theme中保存下來的映射對象,并且返回到Java層。

后話

其實剛好這段時間在擺弄公司的基礎(chǔ)庫,抽離了一個ui公共庫。經(jīng)常接觸這些資源解析,資源查找,因此也運氣挺好的順手寫了這些東西,總結(jié)了之前的所用。

到這里,所有常規(guī)的資源查找方法就全部解析完畢。接下來,我會開啟Android渲染體系的文章系列。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容