Android 重學(xué)系列 資源管理系統(tǒng) 資源的初始化加載(下)

前言

上一篇文章渗稍,聊到了資源管理中解析Package數(shù)據(jù)模塊中的LoadedPackage::Load方法開始解析Package數(shù)據(jù)塊学搜。本文將會詳細(xì)解析Package數(shù)據(jù)塊的解析,以及AssetManager如何管理咪辱。接下來解析resource.arsc還是依照下面這幅圖進(jìn)行解析:


image.png

如果遇到問題歡迎在這個地址下留言:http://www.reibang.com/p/02a2539890dc

正文

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

LoadedPackage::Load有點長,我拆開兩部分聊。

解析Package數(shù)據(jù)包前的準(zhǔn)備

const static int kAppPackageId = 0x7f;

std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
                                                         const LoadedIdmap* loaded_idmap,
                                                         bool system, bool load_as_shared_library) {

  std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());

//計算結(jié)構(gòu)體的大小
  constexpr size_t kMinPackageSize =
      sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
  const ResTable_package* header = chunk.header<ResTable_package, kMinPackageSize>();


  loaded_package->system_ = system;

  loaded_package->package_id_ = dtohl(header->id);
  if (loaded_package->package_id_ == 0 ||
      (loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) {
    // Package ID of 0 means this is a shared library.
    loaded_package->dynamic_ = true;
  }

  if (loaded_idmap != nullptr) {
    // This is an overlay and so it needs to pretend to be the target package.
    loaded_package->package_id_ = loaded_idmap->TargetPackageId();
    loaded_package->overlay_ = true;
  }

  if (header->header.headerSize >= sizeof(ResTable_package)) {
    uint32_t type_id_offset = dtohl(header->typeIdOffset);
 ...
    loaded_package->type_id_offset_ = static_cast<int>(type_id_offset);
  }

  util::ReadUtf16StringFromDevice(header->name, arraysize(header->name),
                                  &loaded_package->package_name_);

  ...
}

這一段是為解析package數(shù)據(jù)塊的準(zhǔn)備依啰,首先解析Package數(shù)據(jù)塊頭部信息。實際上解析是下面這部分模塊:


image.png

首先取出當(dāng)前的header的id并且獲取交換低高位作為packageId(0x7f000000交換高低位變成0x7f)店枣,如果當(dāng)前的id是0x7f且打開了作為load_as_shared_library的標(biāo)識位速警,或者id是0x00則作為動態(tài)資源加載,如果是第三方資源庫則id為0.

如果上面?zhèn)飨聛砹薼oaded_idmap說明這部分的資源需要重新被覆蓋鸯两。最后設(shè)置loaded_idmap闷旧。

此時可以得知,一般的App應(yīng)用中的apk包中packageID是0x7f

解析Package數(shù)據(jù)

  std::unordered_map<int, std::unique_ptr<TypeSpecPtrBuilder>> type_builder_map;

  ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
  while (iter.HasNext()) {
    const Chunk child_chunk = iter.Next();
    switch (child_chunk.type()) {
      case RES_STRING_POOL_TYPE: {
        const uintptr_t pool_address =
            reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>());
        const uintptr_t header_address = reinterpret_cast<uintptr_t>(header);
        if (pool_address == header_address + dtohl(header->typeStrings)) {
          // This string pool is the type string pool.
          status_t err = loaded_package->type_string_pool_.setTo(
              child_chunk.header<ResStringPool_header>(), child_chunk.size());
         ...
        } else if (pool_address == header_address + dtohl(header->keyStrings)) {
          // This string pool is the key string pool.
          status_t err = loaded_package->key_string_pool_.setTo(
              child_chunk.header<ResStringPool_header>(), child_chunk.size());
      ...
        } else {
         ...
        }
      } break;

      case RES_TABLE_TYPE_SPEC_TYPE: {
        const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>();
      ...
        // The data portion of this chunk contains entry_count 32bit entries,
        // each one representing a set of flags.
        // Here we only validate that the chunk is well formed.
        const size_t entry_count = dtohl(type_spec->entryCount);

        // There can only be 2^16 entries in a type, because that is the ID
        // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
....

        // If this is an overlay, associate the mapping of this type to the target type
        // from the IDMAP.
        const IdmapEntry_header* idmap_entry_header = nullptr;
        if (loaded_idmap != nullptr) {
          idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id);
        }

        std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1];
        if (builder_ptr == nullptr) {
          builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
        } else {
...
        }
      } break;

      case RES_TABLE_TYPE_TYPE: {
        const ResTable_type* type = child_chunk.header<ResTable_type, kResTableTypeMinSize>();
...

        // Type chunks must be preceded by their TypeSpec chunks.
        std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type->id - 1];
        if (builder_ptr != nullptr) {
          builder_ptr->AddType(type);
        } else {
         ...
        }
      } break;

      case RES_TABLE_LIBRARY_TYPE: {
        const ResTable_lib_header* lib = child_chunk.header<ResTable_lib_header>();
   ...

        loaded_package->dynamic_package_map_.reserve(dtohl(lib->count));

        const ResTable_lib_entry* const entry_begin =
            reinterpret_cast<const ResTable_lib_entry*>(child_chunk.data_ptr());
        const ResTable_lib_entry* const entry_end = entry_begin + dtohl(lib->count);
        for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
          std::string package_name;
          util::ReadUtf16StringFromDevice(entry_iter->packageName,
                                          arraysize(entry_iter->packageName), &package_name);

         ...

          loaded_package->dynamic_package_map_.emplace_back(std::move(package_name),
                                                            dtohl(entry_iter->packageId));
        }

      } break;

      default:
       ...
        break;
    }
  }

  ...
  // Flatten and construct the TypeSpecs.
  for (auto& entry : type_builder_map) {
    uint8_t type_idx = static_cast<uint8_t>(entry.first);
    TypeSpecPtr type_spec_ptr = entry.second->Build();
    ...
    // We only add the type to the package if there is no IDMAP, or if the type is
    // overlaying something.
    if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
      // If this is an overlay, insert it at the target type ID.
      if (type_spec_ptr->idmap_entries != nullptr) {
        type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
      }
      loaded_package->type_specs_.editItemAt(type_idx) = std::move(type_spec_ptr);
    }
  }

  return std::move(loaded_package);

上一篇文章稍微總結(jié)這部分源碼聊了什么钧唐,這本將拆開這幾個case看看里面究竟做了什么事情忙灼。

解析Package數(shù)據(jù)包中的字符串池子

      case RES_STRING_POOL_TYPE: {
        const uintptr_t pool_address =
            reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>());
        const uintptr_t header_address = reinterpret_cast<uintptr_t>(header);
        if (pool_address == header_address + dtohl(header->typeStrings)) {
          // This string pool is the type string pool.
          status_t err = loaded_package->type_string_pool_.setTo(
              child_chunk.header<ResStringPool_header>(), child_chunk.size());
         ...
        } else if (pool_address == header_address + dtohl(header->keyStrings)) {
          // This string pool is the key string pool.
          status_t err = loaded_package->key_string_pool_.setTo(
              child_chunk.header<ResStringPool_header>(), child_chunk.size());
      ...
        } else {
         ...
        }
      } break;

在這個case中,實際上做的事情很簡單钝侠,解析的是下面這部分


image.png

和這張圖不一樣的是该园,在這個字符串資源池其實還有一個header,這里面疏漏了帅韧。這里面算法很簡單里初,如下:

資源類型字符串池地址 = ResTable_package.header + typeString (偏移量)

資源項名稱字符串池地址 = ResTable_package.header + keyStrings(偏移量)

最后通過這個地址和當(dāng)前chunk的子chunk比較地址,看看和哪個相等忽舟,相等則賦值到對應(yīng)的資源池双妨。

至此,已經(jīng)有三個全局資源池叮阅,global_string_pool_全局內(nèi)容資源池刁品,loaded_package->type_string_pool_ 資源類型字符串資源池,loaded_package->key_string_pool_資源項名稱字符串資源池帘饶。

解析資源類型數(shù)據(jù)

      case RES_TABLE_TYPE_SPEC_TYPE: {
        const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>();
      ...
        // The data portion of this chunk contains entry_count 32bit entries,
        // each one representing a set of flags.
        // Here we only validate that the chunk is well formed.
        const size_t entry_count = dtohl(type_spec->entryCount);

        // There can only be 2^16 entries in a type, because that is the ID
        // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
....

        // If this is an overlay, associate the mapping of this type to the target type
        // from the IDMAP.
        const IdmapEntry_header* idmap_entry_header = nullptr;
        if (loaded_idmap != nullptr) {
          idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id);
        }

        std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1];
        if (builder_ptr == nullptr) {
          builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
        } else {
...
        }
      } break;

此時解析的是下面這個數(shù)據(jù)塊:


image.png

首先來看看資源類型的結(jié)構(gòu)體:

struct ResTable_typeSpec
{
    struct ResChunk_header header;

    // The type identifier this chunk is holding.  Type IDs start
    // at 1 (corresponding to the value of the type bits in a
    // resource identifier).  0 is invalid.
    uint8_t id;
    
    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;
    
    // Number of uint32_t entry configuration masks that follow.
    uint32_t entryCount;

    enum : uint32_t {
        // Additional flag indicating an entry is public.
        SPEC_PUBLIC = 0x40000000u,

        // Additional flag indicating an entry is overlayable at runtime.
        // Added in Android-P.
        SPEC_OVERLAYABLE = 0x80000000u,
    };
};

該結(jié)構(gòu)體定義了每一個資源類型的id哑诊,以及可以配置的entryCount。id是逐一遞增及刻,而entryCount的意思就是指該資源類型中镀裤,每一個資源項能跟著幾個配置。比如說缴饭,layout中可以跟著幾個layout-v21,v22等等暑劝,里面都包含著對應(yīng)的具體布局文件值資源值。

如果傳下的loaded_idmap 不為空颗搂,則說明這個package需要覆蓋掉某個package的資源類型担猛,就嘗試著通過id去找到IdmapEntry_header。做覆蓋準(zhǔn)備。

此時就做了如下事情:

type_builder_map[typeSpec的id - 1] = 新建一個TypeSpecPtrBuilder(type_spec傅联,IdmapEntry_header)

初步構(gòu)建了每個資源類型和id之間的映射關(guān)系先改。

解析資源項

      case RES_TABLE_TYPE_TYPE: {
        const ResTable_type* type = child_chunk.header<ResTable_type, kResTableTypeMinSize>();
...

        // Type chunks must be preceded by their TypeSpec chunks.
        std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type->id - 1];
        if (builder_ptr != nullptr) {
          builder_ptr->AddType(type);
        } else {
         ...
        }
      } break;

此時解析的是下面這個數(shù)據(jù)塊:


image.png

在上一節(jié)中解析每一個資源類型的時候構(gòu)建了TypeSpecPtrBuilder對象。當(dāng)沒遇到一個新的資源項時候蒸走,將會取出這個TypeSpecPtrBuilder仇奶,并且通過AddType到這個對象中。這樣就完成了id到資源類型到資源項的映射比驻。
看看資源項的數(shù)據(jù)結(jié)構(gòu):

struct ResTable_type
{
    struct ResChunk_header header;

    enum {
        NO_ENTRY = 0xFFFFFFFF
    };
    
    // The type identifier this chunk is holding.  Type IDs start
    // at 1 (corresponding to the value of the type bits in a
    // resource identifier).  0 is invalid.
    uint8_t id;
    
    enum {
        // If set, the entry is sparse, and encodes both the entry ID and offset into each entry,
        // and a binary search is used to find the key. Only available on platforms >= O.
        // Mark any types that use this with a v26 qualifier to prevent runtime issues on older
        // platforms.
        FLAG_SPARSE = 0x01,
    };
    uint8_t flags;

    // Must be 0.
    uint16_t reserved;
    
    // Number of uint32_t entry indices that follow.
    uint32_t entryCount;

    // Offset from header where ResTable_entry data starts.
    uint32_t entriesStart;

    // Configuration this collection of entries is designed for. This must always be last.
    ResTable_config config;
};

能看到每一個資源項里面包含了一個頭部该溯,一個配置(語言環(huán)境),還有entry的偏移量别惦,entry是指什么呢狈茉?


image.png

這個entry就是我們編程讀取到的數(shù)據(jù)。

最后讓我們看看掸掸,TypeSpecPtrBuilder

class TypeSpecPtrBuilder {
 public:
  explicit TypeSpecPtrBuilder(const ResTable_typeSpec* header,
                              const IdmapEntry_header* idmap_header)
      : header_(header), idmap_header_(idmap_header) {
  }

  void AddType(const ResTable_type* type) {
    types_.push_back(type);
  }

  TypeSpecPtr Build() {
    // Check for overflow.
    using ElementType = const ResTable_type*;
    if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) <
        types_.size()) {
      return {};
    }
    TypeSpec* type_spec =
        (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType)));
    type_spec->type_spec = header_;
    type_spec->idmap_entries = idmap_header_;
    type_spec->type_count = types_.size();
    memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType));
    return TypeSpecPtr(type_spec);
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);

  const ResTable_typeSpec* header_;
  const IdmapEntry_header* idmap_header_;
  std::vector<const ResTable_type*> types_;
};

能看到這個數(shù)據(jù)類型很簡單氯庆。里面保存了ResTable_typeSpec對應(yīng)的header,需要覆蓋的idmap_header_猾漫,以及添加進(jìn)來數(shù)據(jù)項点晴。

解析第三方資源庫資源(特指資源共享庫)

      case RES_TABLE_LIBRARY_TYPE: {
        const ResTable_lib_header* lib = child_chunk.header<ResTable_lib_header>();
   ...

        loaded_package->dynamic_package_map_.reserve(dtohl(lib->count));

        const ResTable_lib_entry* const entry_begin =
            reinterpret_cast<const ResTable_lib_entry*>(child_chunk.data_ptr());
        const ResTable_lib_entry* const entry_end = entry_begin + dtohl(lib->count);
        for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
          std::string package_name;
          util::ReadUtf16StringFromDevice(entry_iter->packageName,
                                          arraysize(entry_iter->packageName), &package_name);

         ...

          loaded_package->dynamic_package_map_.emplace_back(std::move(package_name),
                                                            dtohl(entry_iter->packageId));
        }

      } break;

這里也很簡單,實際上就是把每一個ResTable_lib_entry悯周,添加到loaded_package的dynamic_package_map_中管理。而ResTable_lib_entry數(shù)據(jù)也很簡單陪竿,只是記錄了這個資源隸屬于那個package的id以及name

{
    // The package-id this shared library was assigned at build time.
    // We use a uint32 to keep the structure aligned on a uint32 boundary.
    uint32_t packageId;

    // The package name of the shared library. \0 terminated.
    uint16_t packageName[128];
};

構(gòu)建LoadPackage中的映射關(guān)系

  // Flatten and construct the TypeSpecs.
  for (auto& entry : type_builder_map) {
    uint8_t type_idx = static_cast<uint8_t>(entry.first);
    TypeSpecPtr type_spec_ptr = entry.second->Build();
    ...
    // We only add the type to the package if there is no IDMAP, or if the type is
    // overlaying something.
    if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
      // If this is an overlay, insert it at the target type ID.
      if (type_spec_ptr->idmap_entries != nullptr) {
        type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
      }
      loaded_package->type_specs_.editItemAt(type_idx) = std::move(type_spec_ptr);
    }
  }

  return std::move(loaded_package);

能看到禽翼,此時將會把type_builder_map中緩存的每一項數(shù)據(jù)都構(gòu)建成TypeSpecPtr 對象,并且根據(jù)當(dāng)前的id-1保存起來族跛。

因此LoadPackage中就有了所有資源的映射關(guān)系闰挡。提一句,為什么第一個資源目錄anim typeid為1了吧礁哄。這是為了讓下層計算可以從下標(biāo)為0開始长酗。

小結(jié)

在整個AssetManager初始化體系中,所有的字符串資源保存在三個字符串資源池中:

  • 1.global_string_pool_全局內(nèi)容資源池
  • 2.loaded_package->type_string_pool_ 資源類型字符串資源池
  • 3.loaded_package->key_string_pool_資源項名稱字符串資源池桐绒。

接下來就會保存package數(shù)據(jù)塊的數(shù)據(jù)夺脾。所有的package數(shù)據(jù)塊都保存到loadedPackage對象中,該對象保存著所有的TypeSpec對象茉继,這個對象就是一個資源類型咧叭,而這個TypeSpec對象中保存著大量的ResTable_type,這個對象只是用用當(dāng)前具體資源entryID烁竭,還沒有具體的數(shù)據(jù)菲茬。還保存著一個提供給第三方資源庫的動態(tài)映射表

回到ApkAsset

思路離開的有點遠(yuǎn)了,回顧一下,上文中所有的事情都是在NativeLoad完成的事情婉弹,而上述過程僅僅只是填充ApkAssets對象中l(wèi)oaded_arsc_對象做的事情睬魂。

完成了這個事情之后就會回到該方法,把native對象地址回傳給java層镀赌。
文件:/frameworks/base/core/java/android/content/res/ApkAssets.java

    private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
            throws IOException {
        Preconditions.checkNotNull(path, "path");
        mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
    }

接下來會通過nativeGetStringBlock再起一次回去native層汉买,獲取native的StringBlock對象。
文件:/frameworks/base/core/jni/android_content_res_ApkAssets.cpp

static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
  const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
  return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
}
inline const ResStringPool* GetStringPool() const {
    return &global_string_pool_;
 }

能看到此時StringBlock獲取的是從resource.arsc的全局字符串資源池佩脊。內(nèi)含著所有資源具體的值救巷。

此時ApkAssets就持有兩個native對象,一個是native層對應(yīng)的ApkAssets以及字符串資源池甥厦。

AssetManager的創(chuàng)建

上文就知道掘宪,AssetManager的創(chuàng)建實際上是不斷的添加ApkAssets對象到builder對象中,最后調(diào)用build創(chuàng)建歇盼。

public static class Builder {
        private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();

        public Builder addApkAssets(ApkAssets apkAssets) {
            mUserApkAssets.add(apkAssets);
            return this;
        }

        public AssetManager build() {
            // Retrieving the system ApkAssets forces their creation as well.
            final ApkAssets[] systemApkAssets = getSystem().getApkAssets();

            final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size();
            final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];

            System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);

            final int userApkAssetCount = mUserApkAssets.size();
            for (int i = 0; i < userApkAssetCount; i++) {
                apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
            }

            // Calling this constructor prevents creation of system ApkAssets, which we took care
            // of in this Builder.
            final AssetManager assetManager = new AssetManager(false /*sentinel*/);
            assetManager.mApkAssets = apkAssets;
            AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
                    false /*invalidateCaches*/);
            return assetManager;
        }
    }

在build方法中舔痕,可以看到整個ApkAssets被劃分為兩類,一類是System的豹缀,一類是應(yīng)用App的伯复。這兩類ApkAssets都會被AssetManager 持有,并且通過nativeSetApkAssets設(shè)置到native層邢笙。

獲取System資源包

private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
static AssetManager sSystem = null;

    public static AssetManager getSystem() {
        synchronized (sSync) {
            createSystemAssetsInZygoteLocked();
            return sSystem;
        }
    }

    private static void createSystemAssetsInZygoteLocked() {
        if (sSystem != null) {
            return;
        }

        // Make sure that all IDMAPs are up to date.
        nativeVerifySystemIdmaps();

        try {
            final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
            apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
            loadStaticRuntimeOverlays(apkAssets);

            sSystemApkAssetsSet = new ArraySet<>(apkAssets);
            sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
            sSystem = new AssetManager(true /*sentinel*/);
            sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
        } catch (IOException e) {
            throw new IllegalStateException("Failed to create system AssetManager", e);
        }
    }

能看到此時會先構(gòu)建一個靜態(tài)的AssetManager啸如,這個AssetManager只管理一個資源包:/system/framework/framework-res.apk。而且還好根據(jù)/data/resource-cache/overlays.list的復(fù)寫資源文件氮惯,把需要重疊的資源覆蓋在系統(tǒng)apk上叮雳。

打開其中的resource.arsc文件,發(fā)現(xiàn)packageID和應(yīng)用的0x7f不一樣妇汗,是0x01


image.png

AssetManager的構(gòu)建

    private AssetManager(boolean sentinel) {
        mObject = nativeCreate();
        ...
    }

在構(gòu)造函數(shù)中只做了一件事情帘不,通過nativeCreate創(chuàng)建native下的GuardedAssetManager對象。

struct GuardedAssetManager : public ::AAssetManager {
  Guarded<AssetManager2> guarded_assetmanager;
};

static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) {
  // AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and
  // AssetManager2 in a contiguous block (GuardedAssetManager).
  return reinterpret_cast<jlong>(new GuardedAssetManager());
}

這本質(zhì)上是一個包裹著AssetManager2的AAssetManager 對象杨箭。Guarded有點像智能指針寞焙,不過這是讓對象自己持有有mutex,自己的操作保持原子性互婿。

nativeSetApkAssets設(shè)置所有的ApkAssets給AssetManager2對象

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

Guarded<AssetManager2>* AssetManagerForNdkAssetManager(::AAssetManager* assetmanager) {
  if (assetmanager == nullptr) {
    return nullptr;
  }
  return &reinterpret_cast<GuardedAssetManager*>(assetmanager)->guarded_assetmanager;
}

static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) {
  return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr));
}


static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
                               jobjectArray apk_assets_array, jboolean invalidate_caches) {

  const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
  std::vector<const ApkAssets*> apk_assets;
  apk_assets.reserve(apk_assets_len);
  for (jsize i = 0; i < apk_assets_len; i++) {
    jobject obj = env->GetObjectArrayElement(apk_assets_array, i);
    if (obj == nullptr) {
      std::string msg = StringPrintf("ApkAssets at index %d is null", i);
      jniThrowNullPointerException(env, msg.c_str());
      return;
    }

    jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr);
    if (env->ExceptionCheck()) {
      return;
    }
    apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr));
  }

  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
  assetmanager->SetApkAssets(apk_assets, invalidate_caches);
}

實際上邏輯很簡單捣郊,就是把Java的數(shù)組轉(zhuǎn)化為vector設(shè)置到AssetManager2.

AssetManager2構(gòu)建內(nèi)存中的資源表

bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
                                 bool invalidate_caches) {
  apk_assets_ = apk_assets;
  BuildDynamicRefTable();
  RebuildFilterList();
  if (invalidate_caches) {
    InvalidateCaches(static_cast<uint32_t>(-1));
  }
  return true;
}

從方法中可以得知整個AssetManager2會在內(nèi)存中構(gòu)建一個動態(tài)的資源表:

  • 1.BuildDynamicRefTable構(gòu)建動態(tài)的資源引用表
  • 2.RebuildFilterList 構(gòu)建過濾后的配置列表
  • 3.InvalidateCaches刷新緩存
構(gòu)建動態(tài)的資源引用表
void AssetManager2::BuildDynamicRefTable() {
  package_groups_.clear();
  package_ids_.fill(0xff);

  // 0x01 is reserved for the android package.
  int next_package_id = 0x02;
  const size_t apk_assets_count = apk_assets_.size();
  for (size_t i = 0; i < apk_assets_count; i++) {
    const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();

    for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
      // Get the package ID or assign one if a shared library.
      int package_id;
      if (package->IsDynamic()) {
        package_id = next_package_id++;
      } else {
        package_id = package->GetPackageId();
      }

      // Add the mapping for package ID to index if not present.
      uint8_t idx = package_ids_[package_id];
      if (idx == 0xff) {
        package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
        package_groups_.push_back({});
        DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table;
        ref_table.mAssignedPackageId = package_id;
        ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
      }
      PackageGroup* package_group = &package_groups_[idx];

      // Add the package and to the set of packages with the same ID.
      package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
      package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));

      // Add the package name -> build time ID mappings.
      for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {
        String16 package_name(entry.package_name.c_str(), entry.package_name.size());
        package_group->dynamic_ref_table.mEntries.replaceValueFor(
            package_name, static_cast<uint8_t>(entry.package_id));
      }
    }
  }

  // Now assign the runtime IDs so that we have a build-time to runtime ID map.
  const auto package_groups_end = package_groups_.end();
  for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
    const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
    for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
      iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
                                          iter->dynamic_ref_table.mAssignedPackageId);
    }
  }
}

為什么需要構(gòu)建一個動態(tài)的資源映射表?在原本的LoadArsc對象中已經(jīng)構(gòu)建了幾乎所有資源之間的關(guān)系擒悬。

但是有一個問題就出現(xiàn)模她,這個第三方資源的packageID編譯到這個位置的時候,實際上是根據(jù)編譯順序按順序加載并且遞增設(shè)置packageID懂牧。從第一個雙重循環(huán)看來侈净,我們運行中還有一個packageID尊勿,是根據(jù)加載到內(nèi)存的順序。

這樣就出現(xiàn)一個很大的問題畜侦?假如有一個第三方資源庫是0x03的packageId元扔,此時加載順序是第1個,這樣在內(nèi)存中對應(yīng)的packageID就是0x02(0x01永遠(yuǎn)給系統(tǒng))旋膳,這樣就會找錯對象澎语。

因此第二個循環(huán)就是為了解決這個問題。

  • 1.雙重循環(huán)做的事情實際上是收集所有保存在LoadArsc對象中的所有的package放到package_group中验懊,并且為每一個index設(shè)置一個cookie擅羞,這個cookie本質(zhì)上是一個int類型,隨著package的增大而增加义图。緊接著减俏,為每一個動態(tài)資源庫加載自己的packageId。并且設(shè)置到DynamicPackageEntry的mEntries中碱工。
  • 2.獲取package_group中所有的數(shù)據(jù)娃承,循環(huán)所有的package_group,并且調(diào)用addMapping:
status_t DynamicRefTable::addMapping(const String16& packageName, uint8_t packageId)
{
    ssize_t index = mEntries.indexOfKey(packageName);
    if (index < 0) {
        return UNKNOWN_ERROR;
    }
    mLookupTable[mEntries.valueAt(index)] = packageId;
    return NO_ERROR;
}

從上面能知道怕篷,mEntries保存的是編譯時packageID历筝,mLookupTable則保存的是運行時id。這樣就能正確的通過運行時id找到編譯時id廊谓。

RebuildFilterList 構(gòu)建過濾后的entry列表

void AssetManager2::RebuildFilterList() {
  for (PackageGroup& group : package_groups_) {
    for (ConfiguredPackage& impl : group.packages_) {
      // Destroy it.
      impl.filtered_configs_.~ByteBucketArray();

      // Re-create it.
      new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();

      // Create the filters here.
      impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) {
        FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index);
        const auto iter_end = spec->types + spec->type_count;
        for (auto iter = spec->types; iter != iter_end; ++iter) {
          ResTable_config this_config;
          this_config.copyFromDtoH((*iter)->config);
          if (this_config.match(configuration_)) {
            group.configurations.push_back(this_config);
            group.types.push_back(*iter);
          }
        }
      });
    }
  }
}

TypeSpec這個對象就是生成ApkAssets的時候保存著所有資源映射關(guān)系梳猪。
這個方法就是通過循環(huán)篩選當(dāng)前的config(如語言環(huán)境,sim卡環(huán)境)一致的config蹂析,有選擇性的獲取ResTable_type(資源項)舔示。

這樣我們就能把所有的關(guān)系都映射到package_groups_對象中。

清除緩存所有的資源id

void AssetManager2::InvalidateCaches(uint32_t diff) {
  if (diff == 0xffffffffu) {
    // Everything must go.
    cached_bags_.clear();
    return;
  }

  for (auto iter = cached_bags_.cbegin(); iter != cached_bags_.cend();) {
    if (diff & iter->second->type_spec_flags) {
      iter = cached_bags_.erase(iter);
    } else {
      ++iter;
    }
  }
}

cached_bags_實際上緩存著過去生成過資源id电抚,如果需要則會清除,一般這種情況如AssetManager配置發(fā)生變化都會清除一下避免干擾cached_bags_竖共。

經(jīng)過著三個步驟之后蝙叛,native層AssetManager就變相的通過package_group持有apk中資源映射關(guān)系。

總結(jié)

限于篇幅的原因公给,下一篇文章將會和大家剖析Android是如何通過初始化好的資源體系借帘,進(jìn)行資源的查找。你將會看到淌铐,本文還沒有使用過的ResTable_entry以及保存著真實數(shù)據(jù)的Res_Value是如何在資源查找中運作肺然。

首先先上一副,囊括Java層和native層時序圖:


Android資源體系的初始化.png

流程很長腿准,也并不是很全际起,只是照顧到了主干拾碌。可以得知街望,在整個流程在頻繁的和native層不斷交流校翔。僅僅依靠時序圖,可能總結(jié)起來是不夠好灾前。

在這里我們可以得知如下信息:
AssetManager 在Java層會控制著ApkAsset防症。相對的AssetManager會對應(yīng)著native層的AssetManager2,而AssetManager2控制著native層的ApkAsset對象哎甲。


AssetManager設(shè)計.png

換句話說蔫敲,ApkAsset就是資源文件夾單位,而AssetManager只會控制到這個粒度炭玫。同時ApkAsset中存在四個十分重要的數(shù)據(jù)結(jié)構(gòu):

    1. resources_asset_ 象征著一個本質(zhì)上是resource.arsc zip資源FileMap的Asset
    1. loaded_arsc_ 實際上是一個LoadedArsc奈嘿,這個是resource.arsc解析資源后生成的映射關(guān)系對象。

LoadedArsc也有2個很重要的數(shù)據(jù)結(jié)構(gòu):

    1. global_string_pool_ 全局字符串資源池
    1. LoadedPackage package數(shù)據(jù)對象

LoadedPackage里面有著大量的資源對象相關(guān)信息础嫡,以及真實數(shù)據(jù)指么,其中也包含幾個很重要的數(shù)據(jù)結(jié)構(gòu):

    1. type_string_pool_ 資源類型字符串,如layout榴鼎,menu伯诬,anim這些文件夾對應(yīng)的名字
    1. key_string_pool_ 資源項字符串,資源對應(yīng)的名字
    1. type_specs_ 里面保存著所有資源類型和資源項的映射關(guān)系
    1. dynamic_package_map_ 是為了處理第三方資源庫編譯的packgeID和運行時ID沖突而構(gòu)建的2次映射巫财,但是解決沖突不是在這里解決

ApkAsset有了這些信息盗似,才能夠根據(jù)resource.arsc 完整的構(gòu)建出資源之間的關(guān)系。


ApkAssets的構(gòu)成.png

當(dāng)然平项,僅僅又這些還不足赫舒,當(dāng)ApkAsset設(shè)置到AssetManager2中的時候,AssetManager2為了更加快速闽瓢,準(zhǔn)確的加載內(nèi)存做了如下努力:

  • 1.保存著多個PackageGroup對象(內(nèi)含ConfiguredPackage)接癌,里面包含著所有package數(shù)據(jù)塊。
  • 2.構(gòu)建動態(tài)資源表扣讼,放在package_group中缺猛,為了解決packageID運行時和編譯時沖突問題
  • 3.提前篩選出符合當(dāng)前環(huán)境的資源配置到FilteredConfigGroup,為了可以快速訪問椭符。
  • 4.緩存已經(jīng)訪問過的BagID荔燎,也就是完整的資源ID。


    AssetManager2的構(gòu)成.png

所以销钝,才叫Asset的Manager有咨。同時我們能夠看到,在整個流程中蒸健,資源的解析流程將會以resource.arsc為引導(dǎo)座享,解析整個Apk資源婉商。但是本質(zhì)上還是zip解壓縮獲取對應(yīng)的數(shù)據(jù)塊,只有訪問這些zipentry才能真正的訪問數(shù)據(jù)征讲。當(dāng)然据某,相關(guān)的字符串會集中控制在三個字符串緩存池中,如果遇到想要相應(yīng)獲取诗箍,可以從這幾個緩存池對應(yīng)的index獲取癣籽。

那么,我們繼續(xù)接著上一次的緩存話題滤祖,看看Android系統(tǒng)為了讀取的效率又作出什么努力筷狼,這里繼續(xù)總結(jié)一下整個資源的緩存情況:

  • 1.activityResources 一個面向Resources弱引用的ArrayList
  • 2.以ResourcesKey為key,ResourcesImpl的弱引用為value的Map緩存匠童。
  • 3.ApkAssets在內(nèi)存中也有一層緩存埂材,緩存拆成兩部分,mLoadedApkAssets已經(jīng)加載的活躍ApkAssets汤求,mCacheApkAssets已經(jīng)加載了但是不活躍的ApkAssets
  • 4.在ApkAsset保存著三個全局字符串資源池子俏险,提供快速查找,對應(yīng)到Java層的對象一般為StringBlock
  • 5.為了能夠快速查找符合當(dāng)前環(huán)境配置的資源(屏幕密度扬绪,語言環(huán)境等)竖独,同樣在過濾構(gòu)建資源階段,有一個FilteredConfigGroup對象挤牛,提供快速查找莹痢。
  • 6.緩存BagID


    Android資源體系的緩存.png

分析資源管理系統(tǒng),可以總結(jié)出什么Android性能優(yōu)化結(jié)論呢墓赴?

1.包體積的優(yōu)化竞膳,我們可以通過混淆資源文件,是的包體積變小诫硕。為什么呢坦辟?因為通過資源的混淆,就可以減少resource.arsc中字符串資源池的大小章办,從而縮小資源大小长窄。

2.資源管理系統(tǒng)查找資源本質(zhì)上是一個比較耗時的過程,因此Android系統(tǒng)做了6層緩存纲菌。保證資源的可以相對快速的查找。而這也是為什么在如果使用方法卡頓檢測第一次應(yīng)喲啟動的時候疮绷,經(jīng)常會報告資源解析方法卡頓的問題翰舌。解決的方案是打開一個線程池適當(dāng)?shù)慕馕鎏崆敖馕鲑Y源。

3.同樣閱讀這段源碼之后冬骚,我們同樣能夠理解為什么各個插件化椅贱,熱修復(fù)只要涉及到資源的修復(fù)懂算,就必須重新更新StringBlock。以前我沒有解釋庇麦,現(xiàn)在應(yīng)該明白StringBlock里面保存著全局字符串資源池计技,如果修復(fù)之后不及時重新更新資源池,就是出現(xiàn)資源查找異常山橄。當(dāng)然Tinker里面所說的"系統(tǒng)資源提前加載需要清除垮媒,否則導(dǎo)致異常"話處理思路結(jié)果是正確的,但是出現(xiàn)錯誤的根本原因倒是出分析錯了航棱。

4.當(dāng)然睡雇,我們從這個過程中,其實可以察覺到其實整個Android資源體系其實可以進(jìn)一步優(yōu)化的:1.asset等資源文件并沒有壓縮饮醇,我們拿出來的其實就是apk中asset文件夾對應(yīng)的ZipEntry它抱。其實我們可以自己進(jìn)行一次壓縮,拿到數(shù)據(jù)流之后進(jìn)一步解壓縮朴艰。不過只是一種用時間來替代空間的策略罷了观蓄。2.在整個過程中字符串資源池保存的是完整的資源,其實我們可以用哈夫曼編碼進(jìn)一步壓縮字符串資源池中的數(shù)據(jù)祠墅,當(dāng)然這樣就需要入侵到編譯流程中侮穿,現(xiàn)在的我還沒有這種水平。

下一篇文章是資源管理系統(tǒng)最后一篇饵隙,探索一下Android的資源是如何查找的撮珠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市金矛,隨后出現(xiàn)的幾起案子芯急,更是在濱河造成了極大的恐慌,老刑警劉巖驶俊,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娶耍,死亡現(xiàn)場離奇詭異,居然都是意外死亡饼酿,警方通過查閱死者的電腦和手機(jī)榕酒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來故俐,“玉大人想鹰,你說我怎么就攤上這事∫┌妫” “怎么了辑舷?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長槽片。 經(jīng)常有香客問我何缓,道長肢础,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任碌廓,我火速辦了婚禮传轰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谷婆。我一直安慰自己慨蛙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布波材。 她就那樣靜靜地躺著股淡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廷区。 梳的紋絲不亂的頭發(fā)上唯灵,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機(jī)與錄音隙轻,去河邊找鬼埠帕。 笑死,一個胖子當(dāng)著我的面吹牛玖绿,可吹牛的內(nèi)容都是我干的敛瓷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼斑匪,長吁一口氣:“原來是場噩夢啊……” “哼呐籽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚀瘸,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狡蝶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贮勃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贪惹,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年寂嘉,在試婚紗的時候發(fā)現(xiàn)自己被綠了奏瞬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡泉孩,死狀恐怖硼端,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寓搬,我是刑警寧澤显蝌,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響曼尊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脏嚷,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一骆撇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧父叙,春花似錦神郊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至甜癞,卻和暖如春夕晓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悠咱。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工蒸辆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人析既。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓躬贡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眼坏。 傳聞我的和親對象是個殘疾皇子拂玻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容