androidfw: Add support for compact resource entries
Bug: 237583012
Given the large number of simple resources such as strings in
Android resources, their ResTable_entry and Res_value can be
encoded together in a compact way. This allows a significant
saving in both storage and memory footprint.
The basic observations for simple resources are:
* ResTable_entry.size will always be sizeof(ResTable_entry)
unless it's a complex entry
* ResTable_entry.key is unlikely to exceed 16-bit
* ResTable_entry.flags only uses 3 bits for now
* Res_value.size will always be sizeof(Res_value)
Given the above, we could well encode the information into
a compact/compatible structure.
struct compact {
uint16_t key;
uint16_t flags;
uint32_t data;
};
The layout of this structure will allow maximum backward
compatibility. e.g. the flags will be at the same offset,
and a
`dtohs((ResTable_entry *)entry->flags) & FLAG_COMPACT`
would tell if this entry is a compact one or not. For a
compact entry:
struct compact *entry;
entry_size == sizeof(*entry)
entry_key == static_cast<uint32_t>(dtohs(entry->key))
entry_flags == dtohs(entry->flags) & 0xff // low 8-bit
data_type == dtohs(entry->flags) >> 8 // high 8-bit
data_size == sizeof(Res_value)
data_value == dtohl(entry->data)
To allow minimum code change and backward compatibility,
we change 'struct ResTable_entry' to 'union ResTable_entry',
with an anonymous structure inside that's fully backward
compatible. Thus, any existing reference such as:
ResTable_entry *entry = ...
if (dtohs(entry->flags) & ResTable_entry::FLAG_COMPLEX) ...
would still work.
However, special care needs to be taken after an entry is
obtained, and when value needs to be extracted.
A compact entry will not encode a complex value, and hence
complex entries/values are handled the same way.
Change-Id: I15d97a4f5e85fab28c075496f7f0cf6b1fcd73e3
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 47750fc..4e597fb 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -3743,15 +3743,15 @@
size_t amt = 0;
ResTable_entry header;
memset(&header, 0, sizeof(header));
- header.size = htods(sizeof(header));
+ header.full.size = htods(sizeof(header));
const type ty = mType;
if (ty == TYPE_BAG) {
- header.flags |= htods(header.FLAG_COMPLEX);
+ header.full.flags |= htods(header.FLAG_COMPLEX);
}
if (isPublic) {
- header.flags |= htods(header.FLAG_PUBLIC);
+ header.full.flags |= htods(header.FLAG_PUBLIC);
}
- header.key.index = htodl(mNameIndex);
+ header.full.key.index = htodl(mNameIndex);
if (ty != TYPE_BAG) {
status_t err = data->writeData(&header, sizeof(header));
if (err != NO_ERROR) {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index f9e52b4..df87889 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -687,32 +687,32 @@
continue;
}
- printer_->Print((entry->flags & ResTable_entry::FLAG_COMPLEX) ? "[ResTable_map_entry]"
- : "[ResTable_entry]");
+ if (entry->is_complex()) {
+ printer_->Print("[ResTable_map_entry]");
+ } else if (entry->is_compact()) {
+ printer_->Print("[ResTable_entry_compact]");
+ } else {
+ printer_->Print("[ResTable_entry]");
+ }
+
printer_->Print(StringPrintf(" id: 0x%04x", it.index()));
printer_->Print(StringPrintf(
- " name: %s",
- android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index))
- .c_str()));
- printer_->Print(
- StringPrintf(" keyIndex: %u", android::util::DeviceToHost32(entry->key.index)));
- printer_->Print(StringPrintf(" size: %u", android::util::DeviceToHost32(entry->size)));
- printer_->Print(StringPrintf(" flags: 0x%04x", android::util::DeviceToHost32(entry->flags)));
+ " name: %s", android::util::GetString(key_pool_, entry->key()).c_str()));
+ printer_->Print(StringPrintf(" keyIndex: %u", entry->key()));
+ printer_->Print(StringPrintf(" size: %zu", entry->size()));
+ printer_->Print(StringPrintf(" flags: 0x%04x", entry->flags()));
printer_->Indent();
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- auto map_entry = (const ResTable_map_entry*)entry;
- printer_->Print(
- StringPrintf(" count: 0x%04x", android::util::DeviceToHost32(map_entry->count)));
+ if (auto map_entry = entry->map_entry()) {
+ uint32_t map_entry_count = android::util::DeviceToHost32(map_entry->count);
+ printer_->Print(StringPrintf(" count: 0x%04x", map_entry_count));
printer_->Print(StringPrintf(" parent: 0x%08x\n",
android::util::DeviceToHost32(map_entry->parent.ident)));
// Print the name and value mappings
- auto maps = (const ResTable_map*)((const uint8_t*)entry +
- android::util::DeviceToHost32(entry->size));
- for (size_t i = 0, count = android::util::DeviceToHost32(map_entry->count); i < count;
- i++) {
+ auto maps = (const ResTable_map*)((const uint8_t*)entry + entry->size());
+ for (size_t i = 0; i < map_entry_count; i++) {
PrintResValue(&(maps[i].value), config, type);
printer_->Print(StringPrintf(
@@ -725,9 +725,8 @@
printer_->Print("\n");
// Print the value of the entry
- auto value =
- (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
- PrintResValue(value, config, type);
+ Res_value value = entry->value();
+ PrintResValue(&value, config, type);
}
printer_->Undent();
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index cffcdf2..5fdfb66 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -159,6 +159,9 @@
AddOptionalSwitch("--enable-sparse-encoding",
"This decreases APK size at the cost of resource retrieval performance.",
&options_.use_sparse_encoding);
+ AddOptionalSwitch("--enable-compact-entries",
+ "This decreases APK size by using compact resource entries for simple data types.",
+ &options_.table_flattener_options.use_compact_entries);
AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.",
&legacy_x_flag_);
AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index d9e379d..8291862 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -384,21 +384,16 @@
continue;
}
- const ResourceName name(
- package->name, *parsed_type,
- android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index)));
+ const ResourceName name(package->name, *parsed_type,
+ android::util::GetString(key_pool_, entry->key()));
const ResourceId res_id(package_id, type->id, static_cast<uint16_t>(it.index()));
std::unique_ptr<Value> resource_value;
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-
+ if (auto mapEntry = entry->map_entry()) {
// TODO(adamlesinski): Check that the entry count is valid.
resource_value = ParseMapEntry(name, config, mapEntry);
} else {
- const Res_value* value =
- (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
- resource_value = ParseValue(name, config, *value);
+ resource_value = ParseValue(name, config, entry->value());
}
if (!resource_value) {
@@ -419,7 +414,7 @@
.SetId(res_id, OnIdConflict::CREATE_ENTRY)
.SetAllowMangled(true);
- if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+ if (entry->flags() & ResTable_entry::FLAG_PUBLIC) {
Visibility visibility{Visibility::Level::kPublic};
auto spec_flags = entry_type_spec_flags_.find(res_id);
diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp
index 8832c24..9dc205f 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter.cpp
@@ -24,11 +24,6 @@
namespace aapt {
-using android::BigBuffer;
-using android::Res_value;
-using android::ResTable_entry;
-using android::ResTable_map;
-
struct less_style_entries {
bool operator()(const Style::Entry* a, const Style::Entry* b) const {
if (a->key.id) {
@@ -189,26 +184,40 @@
};
template <typename T>
-void WriteEntry(const FlatEntry* entry, T* out_result) {
+void WriteEntry(const FlatEntry* entry, T* out_result, bool compact = false) {
static_assert(std::is_same_v<ResTable_entry, T> || std::is_same_v<ResTable_entry_ext, T>,
"T must be ResTable_entry or ResTable_entry_ext");
ResTable_entry* out_entry = (ResTable_entry*)out_result;
+ uint16_t flags = 0;
+
if (entry->entry->visibility.level == Visibility::Level::kPublic) {
- out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
+ flags |= ResTable_entry::FLAG_PUBLIC;
}
if (entry->value->IsWeak()) {
- out_entry->flags |= ResTable_entry::FLAG_WEAK;
+ flags |= ResTable_entry::FLAG_WEAK;
}
if constexpr (std::is_same_v<ResTable_entry_ext, T>) {
- out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
+ flags |= ResTable_entry::FLAG_COMPLEX;
}
- out_entry->flags = android::util::HostToDevice16(out_entry->flags);
- out_entry->key.index = android::util::HostToDevice32(entry->entry_key);
- out_entry->size = android::util::HostToDevice16(sizeof(T));
+ if (!compact) {
+ out_entry->full.flags = android::util::HostToDevice16(flags);
+ out_entry->full.key.index = android::util::HostToDevice32(entry->entry_key);
+ out_entry->full.size = android::util::HostToDevice16(sizeof(T));
+ } else {
+ Res_value value;
+ CHECK(entry->entry_key < 0xffffu) << "cannot encode key in 16-bit";
+ CHECK(compact && (std::is_same_v<ResTable_entry, T>)) << "cannot encode complex entry";
+ CHECK(ValueCast<Item>(entry->value)->Flatten(&value)) << "flatten failed";
+
+ flags |= ResTable_entry::FLAG_COMPACT | (value.dataType << 8);
+ out_entry->compact.flags = android::util::HostToDevice16(flags);
+ out_entry->compact.key = android::util::HostToDevice16(entry->entry_key);
+ out_entry->compact.data = value.data;
+ }
}
int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer) {
@@ -222,57 +231,26 @@
return offset;
}
-void WriteItemToPair(const FlatEntry* item_entry, ResEntryValuePair* out_pair) {
- static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
- "ResEntryValuePair must not have padding between entry and value.");
+template <bool compact_entry, typename T>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer) {
+ int32_t offset = buffer->size();
+ T* out_entry = buffer->NextBlock<T>();
- WriteEntry<ResTable_entry>(item_entry, &out_pair->entry);
-
- CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_pair->value)) << "flatten failed";
- out_pair->value.size = android::util::HostToDevice16(sizeof(out_pair->value));
-}
-
-int32_t SequentialResEntryWriter::WriteMap(const FlatEntry* entry) {
- return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t SequentialResEntryWriter::WriteItem(const FlatEntry* entry) {
- int32_t offset = entries_buffer_->size();
- auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
- WriteItemToPair(entry, out_pair);
- return offset;
-}
-
-std::size_t ResEntryValuePairContentHasher::operator()(const ResEntryValuePairRef& ref) const {
- return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(ResEntryValuePair));
-}
-
-bool ResEntryValuePairContentEqualTo::operator()(const ResEntryValuePairRef& a,
- const ResEntryValuePairRef& b) const {
- return std::memcmp(a.ptr, b.ptr, sizeof(ResEntryValuePair)) == 0;
-}
-
-int32_t DeduplicateItemsResEntryWriter::WriteMap(const FlatEntry* entry) {
- return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t DeduplicateItemsResEntryWriter::WriteItem(const FlatEntry* entry) {
- int32_t initial_offset = entries_buffer_->size();
-
- auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
- WriteItemToPair(entry, out_pair);
-
- auto ref = ResEntryValuePairRef{*out_pair};
- auto [it, inserted] = entry_offsets.insert({ref, initial_offset});
- if (inserted) {
- // If inserted just return a new offset as this is a first time we store
- // this entry.
- return initial_offset;
+ if constexpr (compact_entry) {
+ WriteEntry(item_entry, out_entry, true);
+ } else {
+ WriteEntry(item_entry, &out_entry->entry);
+ CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_entry->value)) << "flatten failed";
+ out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value));
}
- // If not inserted this means that this is a duplicate, backup allocated block to the buffer
- // and return offset of previously stored entry.
- entries_buffer_->BackUp(sizeof(ResEntryValuePair));
- return it->second;
+ return {offset, out_entry};
}
-} // namespace aapt
\ No newline at end of file
+// explicitly specialize both versions
+template std::pair<int32_t, ResEntryValue<false>*> WriteItemToBuffer<false>(
+ const FlatEntry* item_entry, BigBuffer* buffer);
+
+template std::pair<int32_t, ResEntryValue<true>*> WriteItemToBuffer<true>(
+ const FlatEntry* item_entry, BigBuffer* buffer);
+
+} // namespace aapt
diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h
index a36ceec..c11598e 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.h
+++ b/tools/aapt2/format/binary/ResEntryWriter.h
@@ -27,6 +27,11 @@
namespace aapt {
+using android::BigBuffer;
+using android::Res_value;
+using android::ResTable_entry;
+using android::ResTable_map;
+
struct FlatEntry {
const ResourceTableEntryView* entry;
const Value* value;
@@ -39,28 +44,42 @@
// We introduce this structure for ResEntryWriter to a have single allocation using
// BigBuffer::NextBlock which allows to return it back with BigBuffer::Backup.
struct ResEntryValuePair {
- android::ResTable_entry entry;
- android::Res_value value;
+ ResTable_entry entry;
+ Res_value value;
};
-// References ResEntryValuePair object stored in BigBuffer used as a key in std::unordered_map.
-// Allows access to memory address where ResEntryValuePair is stored.
-union ResEntryValuePairRef {
- const std::reference_wrapper<const ResEntryValuePair> pair;
+static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
+ "ResEntryValuePair must not have padding between entry and value.");
+
+template <bool compact>
+using ResEntryValue = std::conditional_t<compact, ResTable_entry, ResEntryValuePair>;
+
+// References ResEntryValue object stored in BigBuffer used as a key in std::unordered_map.
+// Allows access to memory address where ResEntryValue is stored.
+template <bool compact>
+union ResEntryValueRef {
+ using T = ResEntryValue<compact>;
+ const std::reference_wrapper<const T> ref;
const u_char* ptr;
- explicit ResEntryValuePairRef(const ResEntryValuePair& ref) : pair(ref) {
+ explicit ResEntryValueRef(const T& rev) : ref(rev) {
}
};
-// Hasher which computes hash of ResEntryValuePair using its bytes representation in memory.
-struct ResEntryValuePairContentHasher {
- std::size_t operator()(const ResEntryValuePairRef& ref) const;
+// Hasher which computes hash of ResEntryValue using its bytes representation in memory.
+struct ResEntryValueContentHasher {
+ template <typename R>
+ std::size_t operator()(const R& ref) const {
+ return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(typename R::T));
+ }
};
// Equaler which compares ResEntryValuePairs using theirs bytes representation in memory.
-struct ResEntryValuePairContentEqualTo {
- bool operator()(const ResEntryValuePairRef& a, const ResEntryValuePairRef& b) const;
+struct ResEntryValueContentEqualTo {
+ template <typename R>
+ bool operator()(const R& a, const R& b) const {
+ return std::memcmp(a.ptr, b.ptr, sizeof(typename R::T)) == 0;
+ }
};
// Base class that allows to write FlatEntries into entries_buffer.
@@ -79,9 +98,9 @@
}
protected:
- ResEntryWriter(android::BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
+ ResEntryWriter(BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
}
- android::BigBuffer* entries_buffer_;
+ BigBuffer* entries_buffer_;
virtual int32_t WriteItem(const FlatEntry* entry) = 0;
@@ -91,18 +110,29 @@
DISALLOW_COPY_AND_ASSIGN(ResEntryWriter);
};
+int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer);
+
+template <bool compact_entry, typename T=ResEntryValue<compact_entry>>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer);
+
// ResEntryWriter which writes FlatEntries sequentially into entries_buffer.
// Next entry is always written right after previous one in the buffer.
+template <bool compact_entry = false>
class SequentialResEntryWriter : public ResEntryWriter {
public:
- explicit SequentialResEntryWriter(android::BigBuffer* entries_buffer)
+ explicit SequentialResEntryWriter(BigBuffer* entries_buffer)
: ResEntryWriter(entries_buffer) {
}
~SequentialResEntryWriter() override = default;
- int32_t WriteItem(const FlatEntry* entry) override;
+ int32_t WriteItem(const FlatEntry* entry) override {
+ auto result = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
+ return result.first;
+ }
- int32_t WriteMap(const FlatEntry* entry) override;
+ int32_t WriteMap(const FlatEntry* entry) override {
+ return WriteMapToBuffer(entry, entries_buffer_);
+ }
private:
DISALLOW_COPY_AND_ASSIGN(SequentialResEntryWriter);
@@ -111,25 +141,44 @@
// ResEntryWriter that writes only unique entry and value pairs into entries_buffer.
// Next entry is written into buffer only if there is no entry with the same bytes representation
// in memory written before. Otherwise returns offset of already written entry.
+template <bool compact_entry = false>
class DeduplicateItemsResEntryWriter : public ResEntryWriter {
public:
- explicit DeduplicateItemsResEntryWriter(android::BigBuffer* entries_buffer)
+ explicit DeduplicateItemsResEntryWriter(BigBuffer* entries_buffer)
: ResEntryWriter(entries_buffer) {
}
~DeduplicateItemsResEntryWriter() override = default;
- int32_t WriteItem(const FlatEntry* entry) override;
+ int32_t WriteItem(const FlatEntry* entry) override {
+ const auto& [offset, out_entry] = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
- int32_t WriteMap(const FlatEntry* entry) override;
+ auto [it, inserted] = entry_offsets.insert({Ref{*out_entry}, offset});
+ if (inserted) {
+ // If inserted just return a new offset as this is a first time we store
+ // this entry
+ return offset;
+ }
+
+ // If not inserted this means that this is a duplicate, backup allocated block to the buffer
+ // and return offset of previously stored entry
+ entries_buffer_->BackUp(sizeof(*out_entry));
+ return it->second;
+ }
+
+ int32_t WriteMap(const FlatEntry* entry) override {
+ return WriteMapToBuffer(entry, entries_buffer_);
+ }
private:
DISALLOW_COPY_AND_ASSIGN(DeduplicateItemsResEntryWriter);
- std::unordered_map<ResEntryValuePairRef, int32_t, ResEntryValuePairContentHasher,
- ResEntryValuePairContentEqualTo>
- entry_offsets;
+ using Ref = ResEntryValueRef<compact_entry>;
+ using Map = std::unordered_map<Ref, int32_t,
+ ResEntryValueContentHasher,
+ ResEntryValueContentEqualTo>;
+ Map entry_offsets;
};
} // namespace aapt
-#endif
\ No newline at end of file
+#endif
diff --git a/tools/aapt2/format/binary/ResEntryWriter_test.cpp b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
index 56ca133..4cb17c3 100644
--- a/tools/aapt2/format/binary/ResEntryWriter_test.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
@@ -56,14 +56,28 @@
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
- BigBuffer out(512);
- SequentialResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ SequentialResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
- 2 * sizeof(ResEntryValuePair)};
- EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
+ 2 * sizeof(ResEntryValuePair)};
+ EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* expect a compact entry to only take sizeof(ResTable_entry) */
+ BigBuffer out(512);
+ SequentialResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry),
+ 2 * sizeof(ResTable_entry)};
+ EXPECT_EQ(out.size(), 3 * sizeof(ResTable_entry));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -83,13 +97,26 @@
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
- BigBuffer out(512);
- SequentialResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ SequentialResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
- EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* compact_entry should have no impact to map items */
+ BigBuffer out(512);
+ SequentialResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) {
@@ -100,13 +127,26 @@
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
- BigBuffer out(512);
- DeduplicateItemsResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, 0, 0};
- EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, 0, 0};
+ EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* expect a compact entry to only take sizeof(ResTable_entry) */
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, 0, 0};
+ EXPECT_EQ(out.size(), sizeof(ResTable_entry));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -126,13 +166,26 @@
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
- BigBuffer out(512);
- DeduplicateItemsResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
- EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
- EXPECT_EQ(offsets, expected_offsets);
-};
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* compact_entry should have no impact to map items */
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+ };
} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 318b8b6..c431730 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -67,7 +67,9 @@
public:
PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
const std::map<size_t, std::string>* shared_libs,
- SparseEntriesMode sparse_entries, bool collapse_key_stringpool,
+ SparseEntriesMode sparse_entries,
+ bool compact_entries,
+ bool collapse_key_stringpool,
const std::set<ResourceName>& name_collapse_exemptions,
bool deduplicate_entry_values)
: context_(context),
@@ -75,6 +77,7 @@
package_(package),
shared_libs_(shared_libs),
sparse_entries_(sparse_entries),
+ compact_entries_(compact_entries),
collapse_key_stringpool_(collapse_key_stringpool),
name_collapse_exemptions_(name_collapse_exemptions),
deduplicate_entry_values_(deduplicate_entry_values) {
@@ -135,6 +138,33 @@
private:
DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
+ // Use compact entries only if
+ // 1) it is enabled, and that
+ // 2) the entries will be accessed on platforms U+, and
+ // 3) all entry keys can be encoded in 16 bits
+ bool UseCompactEntries(const ConfigDescription& config, std::vector<FlatEntry>* entries) const {
+ return compact_entries_ &&
+ (context_->GetMinSdkVersion() > SDK_TIRAMISU || config.sdkVersion > SDK_TIRAMISU) &&
+ std::none_of(entries->cbegin(), entries->cend(),
+ [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
+ }
+
+ std::unique_ptr<ResEntryWriter> GetResEntryWriter(bool dedup, bool compact, BigBuffer* buffer) {
+ if (dedup) {
+ if (compact) {
+ return std::make_unique<DeduplicateItemsResEntryWriter<true>>(buffer);
+ } else {
+ return std::make_unique<DeduplicateItemsResEntryWriter<false>>(buffer);
+ }
+ } else {
+ if (compact) {
+ return std::make_unique<SequentialResEntryWriter<true>>(buffer);
+ } else {
+ return std::make_unique<SequentialResEntryWriter<false>>(buffer);
+ }
+ }
+ }
+
bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config,
const size_t num_total_entries, std::vector<FlatEntry>* entries,
BigBuffer* buffer) {
@@ -150,15 +180,11 @@
std::vector<uint32_t> offsets;
offsets.resize(num_total_entries, 0xffffffffu);
+ bool compact_entry = UseCompactEntries(config, entries);
+
android::BigBuffer values_buffer(512);
- std::variant<std::monostate, DeduplicateItemsResEntryWriter, SequentialResEntryWriter>
- writer_variant;
- ResEntryWriter* res_entry_writer;
- if (deduplicate_entry_values_) {
- res_entry_writer = &writer_variant.emplace<DeduplicateItemsResEntryWriter>(&values_buffer);
- } else {
- res_entry_writer = &writer_variant.emplace<SequentialResEntryWriter>(&values_buffer);
- }
+ auto res_entry_writer = GetResEntryWriter(deduplicate_entry_values_,
+ compact_entry, &values_buffer);
for (FlatEntry& flat_entry : *entries) {
CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
@@ -512,6 +538,7 @@
const ResourceTablePackageView package_;
const std::map<size_t, std::string>* shared_libs_;
SparseEntriesMode sparse_entries_;
+ bool compact_entries_;
android::StringPool type_pool_;
android::StringPool key_pool_;
bool collapse_key_stringpool_;
@@ -568,7 +595,9 @@
}
PackageFlattener flattener(context, package, &table->included_packages_,
- options_.sparse_entries, options_.collapse_key_stringpool,
+ options_.sparse_entries,
+ options_.use_compact_entries,
+ options_.collapse_key_stringpool,
options_.name_collapse_exemptions,
options_.deduplicate_entry_values);
if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 6151b7e..35254ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -44,6 +44,9 @@
// as a sparse map of entry ID and offset to actual data.
SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled;
+ // When true, use compact entries for simple data
+ bool use_compact_entries = false;
+
// When true, the key string pool in the final ResTable
// is collapsed to a single entry. All resource entries
// have name indices that point to this single value