Add ResTable_sparseTypeEntry support

Benchmarks on bullhead-userdebug show that there is a negligent
performance impact when using sparse entries on a 30% loaded
sparse type of 1000 resources.

Benchmark                                             Time           CPU Iterations
-----------------------------------------------------------------------------------
BM_SparseEntryGetResourceSparseLarge                255 ns        254 ns    2751408
BM_SparseEntryGetResourceNotSparseLarge             254 ns        254 ns    2756534

Bug: 27381711
Test: make libandroidfw_tests aapt2_tests
Change-Id: I051ea22f2f6b2bc3696e446adc9e2a34be18009f
This commit is contained in:
Adam Lesinski
2017-02-08 07:03:50 -08:00
parent c535d122c6
commit c8f71aa67e
30 changed files with 2125 additions and 182 deletions

View File

@@ -6074,6 +6074,10 @@ bool ResTable::getResourceFlags(uint32_t resID, uint32_t* outFlags) const {
return true;
}
static bool keyCompare(const ResTable_sparseTypeEntry& entry , uint16_t entryIdx) {
return dtohs(entry.idx) < entryIdx;
}
status_t ResTable::getEntry(
const PackageGroup* packageGroup, int typeIndex, int entryIndex,
const ResTable_config* config,
@@ -6115,6 +6119,9 @@ status_t ResTable::getEntry(
currentTypeIsOverlay = true;
}
// Check that the entry idx is within range of the declared entry count (ResTable_typeSpec).
// Particular types (ResTable_type) may be encoded with sparse entries, and so their
// entryCount do not need to match.
if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) {
ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)",
Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex),
@@ -6169,11 +6176,37 @@ status_t ResTable::getEntry(
continue;
}
// Check if there is the desired entry in this type.
const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize));
uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
uint32_t thisOffset;
// Check if there is the desired entry in this type.
if (thisType->flags & ResTable_type::FLAG_SPARSE) {
// This is encoded as a sparse map, so perform a binary search.
const ResTable_sparseTypeEntry* sparseIndices =
reinterpret_cast<const ResTable_sparseTypeEntry*>(eindex);
const ResTable_sparseTypeEntry* result = std::lower_bound(
sparseIndices, sparseIndices + dtohl(thisType->entryCount), realEntryIndex,
keyCompare);
if (result == sparseIndices + dtohl(thisType->entryCount)
|| dtohs(result->idx) != realEntryIndex) {
// No entry found.
continue;
}
// Extract the offset from the entry. Each offset must be a multiple of 4
// so we store it as the real offset divided by 4.
thisOffset = dtohs(result->offset) * 4u;
} else {
if (static_cast<uint32_t>(realEntryIndex) >= dtohl(thisType->entryCount)) {
// Entry does not exist.
continue;
}
thisOffset = dtohl(eindex[realEntryIndex]);
}
if (thisOffset == ResTable_type::NO_ENTRY) {
// There is no entry for this index and configuration.
continue;
@@ -6480,12 +6513,6 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
}
Type* t = typeList.editItemAt(typeList.size() - 1);
if (newEntryCount != t->entryCount) {
ALOGE("ResTable_type entry count inconsistent: given %d, previously %d",
(int)newEntryCount, (int)t->entryCount);
return (mError=BAD_TYPE);
}
if (t->package != package) {
ALOGE("No TypeSpec for type %d", type->id);
return (mError=BAD_TYPE);
@@ -7096,8 +7123,17 @@ void ResTable::print(bool inclValues) const
thisConfig.copyFromDtoH(type->config);
String8 configStr = thisConfig.toString();
printf(" config %s:\n", configStr.size() > 0
printf(" config %s", configStr.size() > 0
? configStr.string() : "(default)");
if (type->flags != 0u) {
printf(" flags=0x%02x", type->flags);
if (type->flags & ResTable_type::FLAG_SPARSE) {
printf(" [sparse]");
}
}
printf(":\n");
size_t entryCount = dtohl(type->entryCount);
uint32_t entriesStart = dtohl(type->entriesStart);
if ((entriesStart&0x3) != 0) {
@@ -7109,18 +7145,30 @@ void ResTable::print(bool inclValues) const
printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize);
continue;
}
for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
const uint32_t* const eindex = (const uint32_t*)
(((const uint8_t*)type) + dtohs(type->header.headerSize));
uint32_t thisOffset = dtohl(eindex[entryIndex]);
if (thisOffset == ResTable_type::NO_ENTRY) {
continue;
const uint32_t* const eindex = (const uint32_t*)
(((const uint8_t*)type) + dtohs(type->header.headerSize));
for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
size_t entryId;
uint32_t thisOffset;
if (type->flags & ResTable_type::FLAG_SPARSE) {
const ResTable_sparseTypeEntry* entry =
reinterpret_cast<const ResTable_sparseTypeEntry*>(
eindex + entryIndex);
entryId = dtohs(entry->idx);
// Offsets are encoded as divided by 4.
thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u;
} else {
entryId = entryIndex;
thisOffset = dtohl(eindex[entryIndex]);
if (thisOffset == ResTable_type::NO_ENTRY) {
continue;
}
}
uint32_t resID = (0xff000000 & ((packageId)<<24))
| (0x00ff0000 & ((typeIndex+1)<<16))
| (0x0000ffff & (entryIndex));
| (0x0000ffff & (entryId));
if (packageId == 0) {
pg->dynamicRefTable.lookupResourceId(&resID);
}

View File

@@ -16,23 +16,45 @@
#include <androidfw/TypeWrappers.h>
#include <algorithm>
namespace android {
TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl(data->entryCount)) {
if (data->flags & ResTable_type::FLAG_SPARSE) {
const uint32_t entryCount = dtohl(data->entryCount);
const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(data) + dtohl(data->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
reinterpret_cast<uintptr_t>(data) + dtohs(data->header.headerSize));
if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount)
> containerEnd) {
ALOGE("Type's entry indices extend beyond its boundaries");
mLength = 0;
} else {
mLength = ResTable_sparseTypeEntry{entryIndices[entryCount - 1]}.idx + 1;
}
}
}
TypeVariant::iterator& TypeVariant::iterator::operator++() {
mIndex++;
if (mIndex > dtohl(mTypeVariant->data->entryCount)) {
mIndex = dtohl(mTypeVariant->data->entryCount);
if (mIndex > mTypeVariant->mLength) {
mIndex = mTypeVariant->mLength;
}
return *this;
}
static bool keyCompare(uint32_t entry, uint16_t index) {
return dtohs(ResTable_sparseTypeEntry{entry}.idx) < index;
}
const ResTable_entry* TypeVariant::iterator::operator*() const {
const ResTable_type* type = mTypeVariant->data;
const uint32_t entryCount = dtohl(type->entryCount);
if (mIndex >= entryCount) {
if (mIndex >= mTypeVariant->mLength) {
return NULL;
}
const uint32_t entryCount = dtohl(mTypeVariant->data->entryCount);
const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(type)
+ dtohl(type->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
@@ -42,7 +64,19 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
return NULL;
}
const uint32_t entryOffset = dtohl(entryIndices[mIndex]);
uint32_t entryOffset;
if (type->flags & ResTable_type::FLAG_SPARSE) {
auto iter = std::lower_bound(entryIndices, entryIndices + entryCount, mIndex, keyCompare);
if (iter == entryIndices + entryCount
|| dtohs(ResTable_sparseTypeEntry{*iter}.idx) != mIndex) {
return NULL;
}
entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u;
} else {
entryOffset = dtohl(entryIndices[mIndex]);
}
if (entryOffset == ResTable_type::NO_ENTRY) {
return NULL;
}

View File

@@ -1339,12 +1339,21 @@ struct ResTable_typeSpec
/**
* A collection of resource entries for a particular resource data
* type. Followed by an array of uint32_t defining the resource
* type.
*
* If the flag FLAG_SPARSE is not set in `flags`, then this struct is
* followed by an array of uint32_t defining the resource
* values, corresponding to the array of type strings in the
* ResTable_package::typeStrings string block. Each of these hold an
* index from entriesStart; a value of NO_ENTRY means that entry is
* not defined.
*
* If the flag FLAG_SPARSE is set in `flags`, then this struct is followed
* by an array of ResTable_sparseTypeEntry defining only the entries that
* have values for this type. Each entry is sorted by their entry ID such
* that a binary search can be performed over the entries. The ID and offset
* are encoded in a uint32_t. See ResTabe_sparseTypeEntry.
*
* There may be multiple of these chunks for a particular resource type,
* supply different configuration variations for the resource values of
* that type.
@@ -1365,10 +1374,17 @@ struct ResTable_type
// 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.
uint8_t res0;
// Must be 0.
uint16_t res1;
uint16_t reserved;
// Number of uint32_t entry indices that follow.
uint32_t entryCount;
@@ -1380,6 +1396,24 @@ struct ResTable_type
ResTable_config config;
};
/**
* An entry in a ResTable_type with the flag `FLAG_SPARSE` set.
*/
union ResTable_sparseTypeEntry {
// Holds the raw uint32_t encoded value. Do not read this.
uint32_t entry;
struct {
// The index of the entry.
uint16_t idx;
// The offset from ResTable_type::entriesStart, divided by 4.
uint16_t offset;
};
};
static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
"ResTable_sparseTypeEntry must be 4 bytes in size");
/**
* This is the beginning of information about an entry in the resource
* table. It holds the reference to the name of this entry, and is

View File

@@ -23,8 +23,7 @@
namespace android {
struct TypeVariant {
TypeVariant(const ResTable_type* data)
: data(data) {}
TypeVariant(const ResTable_type* data);
class iterator {
public:
@@ -72,10 +71,13 @@ struct TypeVariant {
}
iterator endEntries() const {
return iterator(this, dtohl(data->entryCount));
return iterator(this, mLength);
}
const ResTable_type* data;
private:
size_t mLength;
};
} // namespace android

View File

@@ -45,6 +45,8 @@ testFiles := \
benchmarkFiles := \
AssetManager2_bench.cpp \
BenchMain.cpp \
BenchmarkHelpers.cpp \
SparseEntry_bench.cpp \
TestHelpers.cpp \
Theme_bench.cpp

View File

@@ -22,6 +22,7 @@
#include "androidfw/AssetManager2.h"
#include "androidfw/ResourceTypes.h"
#include "BenchmarkHelpers.h"
#include "TestHelpers.h"
#include "data/basic/R.h"
#include "data/libclient/R.h"
@@ -112,34 +113,6 @@ static void GetResourceBenchmark(const std::vector<std::string>& paths,
}
}
static void GetResourceBenchmarkOld(const std::vector<std::string>& paths,
const ResTable_config* config, uint32_t resid,
benchmark::State& state) {
AssetManager assetmanager;
for (const std::string& path : paths) {
if (!assetmanager.addAssetPath(String8(path.c_str()), nullptr /* cookie */,
false /* appAsLib */, false /* isSystemAssets */)) {
state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
return;
}
}
if (config != nullptr) {
assetmanager.setConfiguration(*config);
}
const ResTable& table = assetmanager.getResources(true);
Res_value value;
ResTable_config selected_config;
uint32_t flags;
while (state.KeepRunning()) {
table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
&selected_config);
}
}
static void BM_AssetManagerGetResource(benchmark::State& state) {
GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
basic::R::integer::number1, state);

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BenchmarkHelpers.h"
#include "android-base/stringprintf.h"
#include "androidfw/AssetManager.h"
namespace android {
void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTable_config* config,
uint32_t resid, benchmark::State& state) {
AssetManager assetmanager;
for (const std::string& path : paths) {
if (!assetmanager.addAssetPath(String8(path.c_str()), nullptr /* cookie */,
false /* appAsLib */, false /* isSystemAssets */)) {
state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
return;
}
}
if (config != nullptr) {
assetmanager.setConfiguration(*config);
}
const ResTable& table = assetmanager.getResources(true);
Res_value value;
ResTable_config selected_config;
uint32_t flags;
while (state.KeepRunning()) {
table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
&selected_config);
}
}
} // namespace android

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TESTS_BENCHMARKHELPERS_H_
#define TESTS_BENCHMARKHELPERS_H_
#include <string>
#include <vector>
#include "benchmark/benchmark.h"
#include "androidfw/ResourceTypes.h"
namespace android {
void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTable_config* config,
uint32_t resid, benchmark::State& state);
} // namespace android
#endif /* TESTS_BENCHMARKHELPERS_H_ */

View File

@@ -41,6 +41,34 @@ TEST(ResTableTest, ShouldLoadSuccessfully) {
ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
}
TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
&contents));
ResTable table;
ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
table.setParameters(&config);
String16 name(u"com.android.sparse:integer/foo_9");
uint32_t flags;
uint32_t resid =
table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
ASSERT_NE(0u, resid);
Res_value val;
ResTable_config selected_config;
ASSERT_GE(
table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
0);
EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
EXPECT_EQ(900u, val.data);
}
TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "androidfw/AssetManager.h"
#include "androidfw/ResourceTypes.h"
#include "BenchmarkHelpers.h"
#include "TestHelpers.h"
#include "data/sparse/R.h"
namespace sparse = com::android::sparse;
namespace android {
static void BM_SparseEntryGetResourceSparseSmall(benchmark::State& state) {
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config,
sparse::R::integer::foo_9, state);
}
BENCHMARK(BM_SparseEntryGetResourceSparseSmall);
static void BM_SparseEntryGetResourceNotSparseSmall(benchmark::State& state) {
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config,
sparse::R::integer::foo_9, state);
}
BENCHMARK(BM_SparseEntryGetResourceNotSparseSmall);
static void BM_SparseEntryGetResourceSparseLarge(benchmark::State& state) {
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config,
sparse::R::string::foo_999, state);
}
BENCHMARK(BM_SparseEntryGetResourceSparseLarge);
static void BM_SparseEntryGetResourceNotSparseLarge(benchmark::State& state) {
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config,
sparse::R::string::foo_999, state);
}
BENCHMARK(BM_SparseEntryGetResourceNotSparseLarge);
} // namespace android

View File

@@ -19,6 +19,7 @@
#include <ostream>
#include <string>
#include <vector>
#include "androidfw/ResourceTypes.h"
#include "gtest/gtest.h"

View File

@@ -0,0 +1 @@
*.flata

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.sparse">
<application />
</manifest>

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TESTS_DATA_SPARSE_R_H_
#define TESTS_DATA_SPARSE_R_H_
#include <cstdint>
namespace com {
namespace android {
namespace sparse {
struct R {
struct integer {
enum : uint32_t {
foo_0 = 0x7f010000,
foo_1 = 0x7f010000,
foo_2 = 0x7f010000,
foo_3 = 0x7f010000,
foo_4 = 0x7f010000,
foo_5 = 0x7f010000,
foo_6 = 0x7f010000,
foo_7 = 0x7f010000,
foo_8 = 0x7f010000,
foo_9 = 0x7f010000,
};
};
struct string {
enum : uint32_t {
foo_999 = 0x7f0203e7,
};
};
};
} // namespace sparse
} // namespace android
} // namespace com
#endif /* TESTS_DATA_SPARSE_R_H_ */

View File

@@ -0,0 +1,24 @@
#!/bin/bash
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -e
PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
aapt2 compile --dir res -o compiled.flata
aapt2 link --manifest AndroidManifest.xml -I $PATH_TO_FRAMEWORK_RES -o sparse.apk --enable-sparse-encoding compiled.flata
aapt2 link --manifest AndroidManifest.xml -I $PATH_TO_FRAMEWORK_RES -o not_sparse.apk compiled.flata

View File

@@ -0,0 +1,18 @@
#!/bin/bash
OUTPUT_default=res/values/strings.xml
OUTPUT_v26=res/values-v26/strings.xml
echo "<resources>" > $OUTPUT_default
echo "<resources>" > $OUTPUT_v26
for i in {0..999}
do
echo " <string name=\"foo_$i\">$i</string>" >> $OUTPUT_default
if [ "$(($i % 3))" -eq "0" ]
then
echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_v26
fi
done
echo "</resources>" >> $OUTPUT_default
echo "</resources>" >> $OUTPUT_v26

Binary file not shown.

View File

@@ -0,0 +1,336 @@
<resources>
<string name="foo_0">0</string>
<string name="foo_3">30</string>
<string name="foo_6">60</string>
<string name="foo_9">90</string>
<string name="foo_12">120</string>
<string name="foo_15">150</string>
<string name="foo_18">180</string>
<string name="foo_21">210</string>
<string name="foo_24">240</string>
<string name="foo_27">270</string>
<string name="foo_30">300</string>
<string name="foo_33">330</string>
<string name="foo_36">360</string>
<string name="foo_39">390</string>
<string name="foo_42">420</string>
<string name="foo_45">450</string>
<string name="foo_48">480</string>
<string name="foo_51">510</string>
<string name="foo_54">540</string>
<string name="foo_57">570</string>
<string name="foo_60">600</string>
<string name="foo_63">630</string>
<string name="foo_66">660</string>
<string name="foo_69">690</string>
<string name="foo_72">720</string>
<string name="foo_75">750</string>
<string name="foo_78">780</string>
<string name="foo_81">810</string>
<string name="foo_84">840</string>
<string name="foo_87">870</string>
<string name="foo_90">900</string>
<string name="foo_93">930</string>
<string name="foo_96">960</string>
<string name="foo_99">990</string>
<string name="foo_102">1020</string>
<string name="foo_105">1050</string>
<string name="foo_108">1080</string>
<string name="foo_111">1110</string>
<string name="foo_114">1140</string>
<string name="foo_117">1170</string>
<string name="foo_120">1200</string>
<string name="foo_123">1230</string>
<string name="foo_126">1260</string>
<string name="foo_129">1290</string>
<string name="foo_132">1320</string>
<string name="foo_135">1350</string>
<string name="foo_138">1380</string>
<string name="foo_141">1410</string>
<string name="foo_144">1440</string>
<string name="foo_147">1470</string>
<string name="foo_150">1500</string>
<string name="foo_153">1530</string>
<string name="foo_156">1560</string>
<string name="foo_159">1590</string>
<string name="foo_162">1620</string>
<string name="foo_165">1650</string>
<string name="foo_168">1680</string>
<string name="foo_171">1710</string>
<string name="foo_174">1740</string>
<string name="foo_177">1770</string>
<string name="foo_180">1800</string>
<string name="foo_183">1830</string>
<string name="foo_186">1860</string>
<string name="foo_189">1890</string>
<string name="foo_192">1920</string>
<string name="foo_195">1950</string>
<string name="foo_198">1980</string>
<string name="foo_201">2010</string>
<string name="foo_204">2040</string>
<string name="foo_207">2070</string>
<string name="foo_210">2100</string>
<string name="foo_213">2130</string>
<string name="foo_216">2160</string>
<string name="foo_219">2190</string>
<string name="foo_222">2220</string>
<string name="foo_225">2250</string>
<string name="foo_228">2280</string>
<string name="foo_231">2310</string>
<string name="foo_234">2340</string>
<string name="foo_237">2370</string>
<string name="foo_240">2400</string>
<string name="foo_243">2430</string>
<string name="foo_246">2460</string>
<string name="foo_249">2490</string>
<string name="foo_252">2520</string>
<string name="foo_255">2550</string>
<string name="foo_258">2580</string>
<string name="foo_261">2610</string>
<string name="foo_264">2640</string>
<string name="foo_267">2670</string>
<string name="foo_270">2700</string>
<string name="foo_273">2730</string>
<string name="foo_276">2760</string>
<string name="foo_279">2790</string>
<string name="foo_282">2820</string>
<string name="foo_285">2850</string>
<string name="foo_288">2880</string>
<string name="foo_291">2910</string>
<string name="foo_294">2940</string>
<string name="foo_297">2970</string>
<string name="foo_300">3000</string>
<string name="foo_303">3030</string>
<string name="foo_306">3060</string>
<string name="foo_309">3090</string>
<string name="foo_312">3120</string>
<string name="foo_315">3150</string>
<string name="foo_318">3180</string>
<string name="foo_321">3210</string>
<string name="foo_324">3240</string>
<string name="foo_327">3270</string>
<string name="foo_330">3300</string>
<string name="foo_333">3330</string>
<string name="foo_336">3360</string>
<string name="foo_339">3390</string>
<string name="foo_342">3420</string>
<string name="foo_345">3450</string>
<string name="foo_348">3480</string>
<string name="foo_351">3510</string>
<string name="foo_354">3540</string>
<string name="foo_357">3570</string>
<string name="foo_360">3600</string>
<string name="foo_363">3630</string>
<string name="foo_366">3660</string>
<string name="foo_369">3690</string>
<string name="foo_372">3720</string>
<string name="foo_375">3750</string>
<string name="foo_378">3780</string>
<string name="foo_381">3810</string>
<string name="foo_384">3840</string>
<string name="foo_387">3870</string>
<string name="foo_390">3900</string>
<string name="foo_393">3930</string>
<string name="foo_396">3960</string>
<string name="foo_399">3990</string>
<string name="foo_402">4020</string>
<string name="foo_405">4050</string>
<string name="foo_408">4080</string>
<string name="foo_411">4110</string>
<string name="foo_414">4140</string>
<string name="foo_417">4170</string>
<string name="foo_420">4200</string>
<string name="foo_423">4230</string>
<string name="foo_426">4260</string>
<string name="foo_429">4290</string>
<string name="foo_432">4320</string>
<string name="foo_435">4350</string>
<string name="foo_438">4380</string>
<string name="foo_441">4410</string>
<string name="foo_444">4440</string>
<string name="foo_447">4470</string>
<string name="foo_450">4500</string>
<string name="foo_453">4530</string>
<string name="foo_456">4560</string>
<string name="foo_459">4590</string>
<string name="foo_462">4620</string>
<string name="foo_465">4650</string>
<string name="foo_468">4680</string>
<string name="foo_471">4710</string>
<string name="foo_474">4740</string>
<string name="foo_477">4770</string>
<string name="foo_480">4800</string>
<string name="foo_483">4830</string>
<string name="foo_486">4860</string>
<string name="foo_489">4890</string>
<string name="foo_492">4920</string>
<string name="foo_495">4950</string>
<string name="foo_498">4980</string>
<string name="foo_501">5010</string>
<string name="foo_504">5040</string>
<string name="foo_507">5070</string>
<string name="foo_510">5100</string>
<string name="foo_513">5130</string>
<string name="foo_516">5160</string>
<string name="foo_519">5190</string>
<string name="foo_522">5220</string>
<string name="foo_525">5250</string>
<string name="foo_528">5280</string>
<string name="foo_531">5310</string>
<string name="foo_534">5340</string>
<string name="foo_537">5370</string>
<string name="foo_540">5400</string>
<string name="foo_543">5430</string>
<string name="foo_546">5460</string>
<string name="foo_549">5490</string>
<string name="foo_552">5520</string>
<string name="foo_555">5550</string>
<string name="foo_558">5580</string>
<string name="foo_561">5610</string>
<string name="foo_564">5640</string>
<string name="foo_567">5670</string>
<string name="foo_570">5700</string>
<string name="foo_573">5730</string>
<string name="foo_576">5760</string>
<string name="foo_579">5790</string>
<string name="foo_582">5820</string>
<string name="foo_585">5850</string>
<string name="foo_588">5880</string>
<string name="foo_591">5910</string>
<string name="foo_594">5940</string>
<string name="foo_597">5970</string>
<string name="foo_600">6000</string>
<string name="foo_603">6030</string>
<string name="foo_606">6060</string>
<string name="foo_609">6090</string>
<string name="foo_612">6120</string>
<string name="foo_615">6150</string>
<string name="foo_618">6180</string>
<string name="foo_621">6210</string>
<string name="foo_624">6240</string>
<string name="foo_627">6270</string>
<string name="foo_630">6300</string>
<string name="foo_633">6330</string>
<string name="foo_636">6360</string>
<string name="foo_639">6390</string>
<string name="foo_642">6420</string>
<string name="foo_645">6450</string>
<string name="foo_648">6480</string>
<string name="foo_651">6510</string>
<string name="foo_654">6540</string>
<string name="foo_657">6570</string>
<string name="foo_660">6600</string>
<string name="foo_663">6630</string>
<string name="foo_666">6660</string>
<string name="foo_669">6690</string>
<string name="foo_672">6720</string>
<string name="foo_675">6750</string>
<string name="foo_678">6780</string>
<string name="foo_681">6810</string>
<string name="foo_684">6840</string>
<string name="foo_687">6870</string>
<string name="foo_690">6900</string>
<string name="foo_693">6930</string>
<string name="foo_696">6960</string>
<string name="foo_699">6990</string>
<string name="foo_702">7020</string>
<string name="foo_705">7050</string>
<string name="foo_708">7080</string>
<string name="foo_711">7110</string>
<string name="foo_714">7140</string>
<string name="foo_717">7170</string>
<string name="foo_720">7200</string>
<string name="foo_723">7230</string>
<string name="foo_726">7260</string>
<string name="foo_729">7290</string>
<string name="foo_732">7320</string>
<string name="foo_735">7350</string>
<string name="foo_738">7380</string>
<string name="foo_741">7410</string>
<string name="foo_744">7440</string>
<string name="foo_747">7470</string>
<string name="foo_750">7500</string>
<string name="foo_753">7530</string>
<string name="foo_756">7560</string>
<string name="foo_759">7590</string>
<string name="foo_762">7620</string>
<string name="foo_765">7650</string>
<string name="foo_768">7680</string>
<string name="foo_771">7710</string>
<string name="foo_774">7740</string>
<string name="foo_777">7770</string>
<string name="foo_780">7800</string>
<string name="foo_783">7830</string>
<string name="foo_786">7860</string>
<string name="foo_789">7890</string>
<string name="foo_792">7920</string>
<string name="foo_795">7950</string>
<string name="foo_798">7980</string>
<string name="foo_801">8010</string>
<string name="foo_804">8040</string>
<string name="foo_807">8070</string>
<string name="foo_810">8100</string>
<string name="foo_813">8130</string>
<string name="foo_816">8160</string>
<string name="foo_819">8190</string>
<string name="foo_822">8220</string>
<string name="foo_825">8250</string>
<string name="foo_828">8280</string>
<string name="foo_831">8310</string>
<string name="foo_834">8340</string>
<string name="foo_837">8370</string>
<string name="foo_840">8400</string>
<string name="foo_843">8430</string>
<string name="foo_846">8460</string>
<string name="foo_849">8490</string>
<string name="foo_852">8520</string>
<string name="foo_855">8550</string>
<string name="foo_858">8580</string>
<string name="foo_861">8610</string>
<string name="foo_864">8640</string>
<string name="foo_867">8670</string>
<string name="foo_870">8700</string>
<string name="foo_873">8730</string>
<string name="foo_876">8760</string>
<string name="foo_879">8790</string>
<string name="foo_882">8820</string>
<string name="foo_885">8850</string>
<string name="foo_888">8880</string>
<string name="foo_891">8910</string>
<string name="foo_894">8940</string>
<string name="foo_897">8970</string>
<string name="foo_900">9000</string>
<string name="foo_903">9030</string>
<string name="foo_906">9060</string>
<string name="foo_909">9090</string>
<string name="foo_912">9120</string>
<string name="foo_915">9150</string>
<string name="foo_918">9180</string>
<string name="foo_921">9210</string>
<string name="foo_924">9240</string>
<string name="foo_927">9270</string>
<string name="foo_930">9300</string>
<string name="foo_933">9330</string>
<string name="foo_936">9360</string>
<string name="foo_939">9390</string>
<string name="foo_942">9420</string>
<string name="foo_945">9450</string>
<string name="foo_948">9480</string>
<string name="foo_951">9510</string>
<string name="foo_954">9540</string>
<string name="foo_957">9570</string>
<string name="foo_960">9600</string>
<string name="foo_963">9630</string>
<string name="foo_966">9660</string>
<string name="foo_969">9690</string>
<string name="foo_972">9720</string>
<string name="foo_975">9750</string>
<string name="foo_978">9780</string>
<string name="foo_981">9810</string>
<string name="foo_984">9840</string>
<string name="foo_987">9870</string>
<string name="foo_990">9900</string>
<string name="foo_993">9930</string>
<string name="foo_996">9960</string>
<string name="foo_999">9990</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<integer name="foo_0">0</integer>
<integer name="foo_4">400</integer>
<integer name="foo_5">500</integer>
<integer name="foo_9">900</integer>
</resources>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<integer name="foo_0">0</integer>
<integer name="foo_1">1</integer>
<integer name="foo_2">2</integer>
<integer name="foo_3">3</integer>
<integer name="foo_4">4</integer>
<integer name="foo_5">5</integer>
<integer name="foo_6">6</integer>
<integer name="foo_7">7</integer>
<integer name="foo_8">8</integer>
<integer name="foo_9">9</integer>
</resources>

Binary file not shown.

View File

@@ -94,7 +94,9 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, IArchiveWriter* writer) {
// The resource table needs to be reserialized since it might have changed.
if (path == "resources.arsc") {
BigBuffer buffer = BigBuffer(1024);
TableFlattener flattener(&buffer);
// TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
// with sparse entries) b/35389232.
TableFlattener flattener({}, &buffer);
if (!flattener.Consume(context, table_.get())) {
return false;
}

View File

@@ -25,7 +25,7 @@
namespace aapt {
enum {
enum : int {
SDK_CUPCAKE = 3,
SDK_DONUT = 4,
SDK_ECLAIR = 5,
@@ -49,7 +49,7 @@ enum {
SDK_MARSHMALLOW = 23,
SDK_NOUGAT = 24,
SDK_NOUGAT_MR1 = 25,
SDK_O = 26, // STOPSHIP Replace with real version
SDK_O = 26, // STOPSHIP Replace with real version
};
size_t FindAttributeSdkLevel(const ResourceId& id);

View File

@@ -26,6 +26,7 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "SdkConstants.h"
#include "ValueVisitor.h"
#include "flatten/ChunkWriter.h"
#include "flatten/ResourceTypeExtensions.h"
@@ -216,8 +217,11 @@ class MapFlattenVisitor : public RawValueVisitor {
class PackageFlattener {
public:
PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package)
: diag_(diag), package_(package) {}
PackageFlattener(IAaptContext* context, ResourceTablePackage* package, bool use_sparse_entries)
: context_(context),
diag_(context->GetDiagnostics()),
package_(package),
use_sparse_entries_(use_sparse_entries) {}
bool FlattenPackage(BigBuffer* buffer) {
ChunkWriter pkg_writer(buffer);
@@ -298,9 +302,12 @@ class PackageFlattener {
return true;
}
bool FlattenConfig(const ResourceTableType* type,
const ConfigDescription& config,
std::vector<FlatEntry>* entries, BigBuffer* buffer) {
bool FlattenConfig(const ResourceTableType* type, const ConfigDescription& config,
const size_t num_total_entries, std::vector<FlatEntry>* entries,
BigBuffer* buffer) {
CHECK(num_total_entries != 0);
CHECK(num_total_entries <= std::numeric_limits<uint16_t>::max());
ChunkWriter type_writer(buffer);
ResTable_type* type_header =
type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
@@ -308,39 +315,60 @@ class PackageFlattener {
type_header->config = config;
type_header->config.swapHtoD();
auto max_accum = [](uint32_t max,
const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
return std::max(max, (uint32_t)a->id.value());
};
std::vector<uint32_t> offsets;
offsets.resize(num_total_entries, 0xffffffffu);
// Find the largest entry ID. That is how many entries we will have.
const uint32_t entry_count =
std::accumulate(type->entries.begin(), type->entries.end(), 0,
max_accum) +
1;
type_header->entryCount = util::HostToDevice32(entry_count);
uint32_t* indices = type_writer.NextBlock<uint32_t>(entry_count);
CHECK((size_t)entry_count <= std::numeric_limits<uint16_t>::max());
memset(indices, 0xff, entry_count * sizeof(uint32_t));
type_header->entriesStart = util::HostToDevice32(type_writer.size());
const size_t entry_start = type_writer.buffer()->size();
BigBuffer values_buffer(512);
for (FlatEntry& flat_entry : *entries) {
CHECK(flat_entry.entry->id.value() < entry_count);
indices[flat_entry.entry->id.value()] =
util::HostToDevice32(type_writer.buffer()->size() - entry_start);
if (!FlattenValue(&flat_entry, type_writer.buffer())) {
CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
offsets[flat_entry.entry->id.value()] = values_buffer.size();
if (!FlattenValue(&flat_entry, &values_buffer)) {
diag_->Error(DiagMessage()
<< "failed to flatten resource '"
<< ResourceNameRef(package_->name, type->type,
flat_entry.entry->name)
<< ResourceNameRef(package_->name, type->type, flat_entry.entry->name)
<< "' for configuration '" << config << "'");
return false;
}
}
bool sparse_encode = use_sparse_entries_;
// Only sparse encode if the entries will be read on platforms O+.
sparse_encode =
sparse_encode && (context_->GetMinSdkVersion() >= SDK_O || config.sdkVersion >= SDK_O);
// Only sparse encode if the offsets are representable in 2 bytes.
sparse_encode =
sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max();
// Only sparse encode if the ratio of populated entries to total entries is below some
// threshold.
sparse_encode =
sparse_encode && ((100 * entries->size()) / num_total_entries) < kSparseEncodingThreshold;
if (sparse_encode) {
type_header->entryCount = util::HostToDevice32(entries->size());
type_header->flags |= ResTable_type::FLAG_SPARSE;
ResTable_sparseTypeEntry* indices =
type_writer.NextBlock<ResTable_sparseTypeEntry>(entries->size());
for (size_t i = 0; i < num_total_entries; i++) {
if (offsets[i] != ResTable_type::NO_ENTRY) {
CHECK((offsets[i] & 0x03) == 0);
indices->idx = util::HostToDevice16(i);
indices->offset = util::HostToDevice16(offsets[i] / 4u);
indices++;
}
}
} else {
type_header->entryCount = util::HostToDevice32(num_total_entries);
uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
for (size_t i = 0; i < num_total_entries; i++) {
indices[i] = util::HostToDevice32(offsets[i]);
}
}
type_header->entriesStart = util::HostToDevice32(type_writer.size());
type_writer.buffer()->AppendBuffer(std::move(values_buffer));
type_writer.Finish();
return true;
}
@@ -370,8 +398,7 @@ class PackageFlattener {
CHECK(bool(entry->id)) << "entry must have an ID set";
sorted_entries.push_back(entry.get());
}
std::sort(sorted_entries.begin(), sorted_entries.end(),
cmp_ids<ResourceEntry>);
std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_ids<ResourceEntry>);
return sorted_entries;
}
@@ -443,22 +470,22 @@ class PackageFlattener {
type_pool_.MakeRef(ToString(type->type));
std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
if (!FlattenTypeSpec(type, &sorted_entries, buffer)) {
return false;
}
// Since the entries are sorted by ID, the last ID will be the largest.
const size_t num_entries = sorted_entries.back()->id.value() + 1;
// The binary resource table lists resource entries for each
// configuration.
// We store them inverted, where a resource entry lists the values for
// each
// configuration available. Here we reverse this to match the binary
// table.
std::map<ConfigDescription, std::vector<FlatEntry>>
config_to_entry_list_map;
std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
for (ResourceEntry* entry : sorted_entries) {
const uint32_t key_index =
(uint32_t)key_pool_.MakeRef(entry->name).index();
const uint32_t key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
// Group values by configuration.
for (auto& config_value : entry->values) {
@@ -469,7 +496,7 @@ class PackageFlattener {
// Flatten a configuration value.
for (auto& entry : config_to_entry_list_map) {
if (!FlattenConfig(type, entry.first, &entry.second, buffer)) {
if (!FlattenConfig(type, entry.first, num_entries, &entry.second, buffer)) {
return false;
}
}
@@ -477,8 +504,10 @@ class PackageFlattener {
return true;
}
IAaptContext* context_;
IDiagnostics* diag_;
ResourceTablePackage* package_;
bool use_sparse_entries_;
StringPool type_pool_;
StringPool key_pool_;
};
@@ -513,7 +542,7 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
// Flatten each package.
for (auto& package : table->packages) {
PackageFlattener flattener(context->GetDiagnostics(), package.get());
PackageFlattener flattener(context, package.get(), options_.use_sparse_entries);
if (!flattener.FlattenPackage(&package_buffer)) {
return false;
}

View File

@@ -25,15 +25,29 @@
namespace aapt {
// The percentage of used entries for a type for which using a sparse encoding is
// preferred.
constexpr const size_t kSparseEncodingThreshold = 60;
struct TableFlattenerOptions {
// When true, types for configurations with a sparse set of entries are encoded
// as a sparse map of entry ID and offset to actual data.
// This is only available on platforms O+ and will only be respected when
// minSdk is O+.
bool use_sparse_entries = false;
};
class TableFlattener : public IResourceTableConsumer {
public:
explicit TableFlattener(BigBuffer* buffer) : buffer_(buffer) {}
explicit TableFlattener(const TableFlattenerOptions& options, BigBuffer* buffer)
: options_(options), buffer_(buffer) {}
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
DISALLOW_COPY_AND_ASSIGN(TableFlattener);
TableFlattenerOptions options_;
BigBuffer* buffer_;
};

View File

@@ -16,7 +16,10 @@
#include "flatten/TableFlattener.h"
#include "android-base/stringprintf.h"
#include "ResourceUtils.h"
#include "SdkConstants.h"
#include "test/Test.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Util.h"
@@ -34,32 +37,40 @@ class TableFlattenerTest : public ::testing::Test {
.Build();
}
::testing::AssertionResult Flatten(ResourceTable* table,
ResTable* out_table) {
::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
ResourceTable* table, std::string* out_content) {
BigBuffer buffer(1024);
TableFlattener flattener(&buffer);
if (!flattener.Consume(context_.get(), table)) {
TableFlattener flattener(options, &buffer);
if (!flattener.Consume(context, table)) {
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
}
*out_content = buffer.to_string();
return ::testing::AssertionSuccess();
}
std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
if (out_table->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
ResourceTable* table, ResTable* out_table) {
std::string content;
auto result = Flatten(context, options, table, &content);
if (!result) {
return result;
}
if (out_table->add(content.data(), content.size(), -1, true) != NO_ERROR) {
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult Flatten(ResourceTable* table,
ResourceTable* out_table) {
BigBuffer buffer(1024);
TableFlattener flattener(&buffer);
if (!flattener.Consume(context_.get(), table)) {
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions options,
ResourceTable* table, ResourceTable* out_table) {
std::string content;
auto result = Flatten(context, options, table, &content);
if (!result) {
return result;
}
std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
BinaryResourceParser parser(context_.get(), out_table, {}, data.get(),
buffer.size());
BinaryResourceParser parser(context, out_table, {}, content.data(), content.size());
if (!parser.Parse()) {
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
}
@@ -127,7 +138,7 @@ class TableFlattenerTest : public ::testing::Test {
return ::testing::AssertionSuccess();
}
private:
protected:
std::unique_ptr<IAaptContext> context_;
};
@@ -153,7 +164,7 @@ TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
.Build();
ResTable res_table;
ASSERT_TRUE(Flatten(table.get(), &res_table));
ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000),
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
@@ -200,7 +211,7 @@ TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
.Build();
ResTable res_table;
ASSERT_TRUE(Flatten(table.get(), &res_table));
ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001),
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
@@ -222,7 +233,7 @@ TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
.Build();
ResourceTable result;
ASSERT_TRUE(Flatten(table.get(), &result));
ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
Attribute* actualAttr =
test::GetValue<Attribute>(&result, "android:attr/foo");
@@ -233,4 +244,119 @@ TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
EXPECT_EQ(attr.max_int, actualAttr->max_int);
}
static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
IAaptContext* context, const ConfigDescription& sparse_config, float load) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId(context->GetCompilationPackage(), context->GetPackageId())
.Build();
// Add regular entries.
int stride = static_cast<int>(1.0f / load);
for (int i = 0; i < 100; i++) {
const ResourceName name = test::ParseNameOrDie(
base::StringPrintf("%s:string/foo_%d", context->GetCompilationPackage().data(), i));
const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
const auto value =
util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
CHECK(table->AddResource(name, resid, ConfigDescription::DefaultConfig(), "",
std::unique_ptr<Value>(value->Clone(nullptr)),
context->GetDiagnostics()));
// Every few entries, write out a sparse_config value. This will give us the desired load.
if (i % stride == 0) {
CHECK(table->AddResource(name, resid, sparse_config, "",
std::unique_ptr<Value>(value->Clone(nullptr)),
context->GetDiagnostics()));
}
}
return table;
}
TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
.SetMinSdkVersion(SDK_O)
.Build();
const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
TableFlattenerOptions options;
options.use_sparse_entries = true;
std::string no_sparse_contents;
ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
// Attempt to parse the sparse contents.
ResourceTable sparse_table;
BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"),
sparse_contents.data(), sparse_contents.size());
ASSERT_TRUE(parser.Parse());
auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
sparse_config);
ASSERT_NE(nullptr, value);
EXPECT_EQ(0u, value->value.data);
ASSERT_EQ(nullptr, test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
sparse_config));
value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
sparse_config);
ASSERT_NE(nullptr, value);
EXPECT_EQ(4u, value->value.data);
}
TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
.SetMinSdkVersion(SDK_LOLLIPOP)
.Build();
const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26");
auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
TableFlattenerOptions options;
options.use_sparse_entries = true;
std::string no_sparse_contents;
ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
}
TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
.SetMinSdkVersion(SDK_O)
.Build();
const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f);
TableFlattenerOptions options;
options.use_sparse_entries = true;
std::string no_sparse_contents;
ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
}
} // namespace aapt

View File

@@ -96,6 +96,9 @@ struct LinkOptions {
// Products to use/filter on.
std::unordered_set<std::string> products;
// Flattening options.
TableFlattenerOptions table_flattener_options;
// Split APK options.
TableSplitterOptions table_splitter_options;
std::vector<SplitConstraints> split_constraints;
@@ -874,7 +877,7 @@ class LinkCommand {
bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) {
BigBuffer buffer(1024);
TableFlattener flattener(&buffer);
TableFlattener flattener(options_.table_flattener_options, &buffer);
if (!flattener.Consume(context_, table)) {
return false;
}
@@ -1870,24 +1873,19 @@ int Link(const std::vector<StringPiece>& args) {
.RequiredFlag("-o", "Output path", &options.output_path)
.RequiredFlag("--manifest", "Path to the Android manifest to build",
&options.manifest_path)
.OptionalFlagList("-I", "Adds an Android APK to link against",
&options.include_paths)
.OptionalFlagList(
"-R",
"Compilation unit to link, using `overlay` semantics.\n"
"The last conflicting resource given takes precedence.",
&overlay_arg_list)
.OptionalFlagList("-I", "Adds an Android APK to link against", &options.include_paths)
.OptionalFlagList("-R",
"Compilation unit to link, using `overlay` semantics.\n"
"The last conflicting resource given takes precedence.",
&overlay_arg_list)
.OptionalFlag("--java", "Directory in which to generate R.java",
&options.generate_java_class_path)
.OptionalFlag("--proguard",
"Output file for generated Proguard rules",
.OptionalFlag("--proguard", "Output file for generated Proguard rules",
&options.generate_proguard_rules_path)
.OptionalFlag(
"--proguard-main-dex",
"Output file for generated Proguard rules for the main dex",
&options.generate_main_dex_proguard_rules_path)
.OptionalSwitch("--no-auto-version",
"Disables automatic style and layout SDK versioning",
.OptionalFlag("--proguard-main-dex",
"Output file for generated Proguard rules for the main dex",
&options.generate_main_dex_proguard_rules_path)
.OptionalSwitch("--no-auto-version", "Disables automatic style and layout SDK versioning",
&options.no_auto_version)
.OptionalSwitch("--no-version-vectors",
"Disables automatic versioning of vector drawables. "
@@ -1903,25 +1901,22 @@ int Link(const std::vector<StringPiece>& args) {
"Disables automatic deduping of resources with\n"
"identical values across compatible configurations.",
&options.no_resource_deduping)
.OptionalSwitch(
"-x",
"Legacy flag that specifies to use the package identifier 0x01",
&legacy_x_flag)
.OptionalSwitch("-z",
"Require localization of strings marked 'suggested'",
.OptionalSwitch("--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.",
&options.table_flattener_options.use_sparse_entries)
.OptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
&legacy_x_flag)
.OptionalSwitch("-z", "Require localization of strings marked 'suggested'",
&require_localization)
.OptionalFlag(
"-c",
"Comma separated list of configurations to include. The default\n"
"is all configurations",
&configs)
.OptionalFlag(
"--preferred-density",
"Selects the closest matching density and strips out all others.",
&preferred_density)
.OptionalFlag("--product",
"Comma separated list of product names to keep",
&product_list)
.OptionalFlag("-c",
"Comma separated list of configurations to include. The default\n"
"is all configurations",
&configs)
.OptionalFlag("--preferred-density",
"Selects the closest matching density and strips out all others.",
&preferred_density)
.OptionalFlag("--product", "Comma separated list of product names to keep", &product_list)
.OptionalSwitch("--output-to-dir",
"Outputs the APK contents to a directory specified "
"by -o",
@@ -1935,11 +1930,10 @@ int Link(const std::vector<StringPiece>& args) {
"Default minimum SDK version to use for "
"AndroidManifest.xml",
&options.manifest_fixer_options.min_sdk_version_default)
.OptionalFlag(
"--target-sdk-version",
"Default target SDK version to use for "
"AndroidManifest.xml",
&options.manifest_fixer_options.target_sdk_version_default)
.OptionalFlag("--target-sdk-version",
"Default target SDK version to use for "
"AndroidManifest.xml",
&options.manifest_fixer_options.target_sdk_version_default)
.OptionalFlag("--version-code",
"Version code (integer) to inject into the "
"AndroidManifest.xml if none is present",
@@ -1948,8 +1942,7 @@ int Link(const std::vector<StringPiece>& args) {
"Version name to inject into the AndroidManifest.xml "
"if none is present",
&options.manifest_fixer_options.version_name_default)
.OptionalSwitch("--static-lib", "Generate a static Android library",
&options.static_lib)
.OptionalSwitch("--static-lib", "Generate a static Android library", &options.static_lib)
.OptionalSwitch("--no-static-lib-packages",
"Merge all library resources under the app's package",
&options.no_static_lib_packages)
@@ -1957,14 +1950,12 @@ int Link(const std::vector<StringPiece>& args) {
"Generates R.java without the final modifier.\n"
"This is implied when --static-lib is specified.",
&options.generate_non_final_ids)
.OptionalFlag("--stable-ids",
"File containing a list of name to ID mapping.",
.OptionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
&stable_id_file_path)
.OptionalFlag(
"--emit-ids",
"Emit a file at the given path with a list of name to ID\n"
"mappings, suitable for use with --stable-ids.",
&options.resource_id_map_path)
.OptionalFlag("--emit-ids",
"Emit a file at the given path with a list of name to ID\n"
"mappings, suitable for use with --stable-ids.",
&options.resource_id_map_path)
.OptionalFlag("--private-symbols",
"Package name to use when generating R.java for "
"private symbols.\n"
@@ -1972,8 +1963,7 @@ int Link(const std::vector<StringPiece>& args) {
"the application's "
"package name",
&options.private_symbols)
.OptionalFlag("--custom-package",
"Custom Java package under which to generate R.java",
.OptionalFlag("--custom-package", "Custom Java package under which to generate R.java",
&options.custom_java_package)
.OptionalFlagList("--extra-packages",
"Generate the same R.java but with different "
@@ -1987,23 +1977,19 @@ int Link(const std::vector<StringPiece>& args) {
"Allows the addition of new resources in "
"overlays without <add-resource> tags",
&options.auto_add_overlay)
.OptionalFlag("--rename-manifest-package",
"Renames the package in AndroidManifest.xml",
.OptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
&options.manifest_fixer_options.rename_manifest_package)
.OptionalFlag(
"--rename-instrumentation-target-package",
"Changes the name of the target package for instrumentation. "
"Most useful "
"when used\nin conjunction with --rename-manifest-package",
&options.manifest_fixer_options
.rename_instrumentation_target_package)
.OptionalFlag("--rename-instrumentation-target-package",
"Changes the name of the target package for instrumentation. "
"Most useful "
"when used\nin conjunction with --rename-manifest-package",
&options.manifest_fixer_options.rename_instrumentation_target_package)
.OptionalFlagList("-0", "File extensions not to compress",
&options.extensions_to_not_compress)
.OptionalFlagList(
"--split",
"Split resources matching a set of configs out to a "
"Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
&split_args)
.OptionalFlagList("--split",
"Split resources matching a set of configs out to a "
"Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
&split_args)
.OptionalSwitch("-v", "Enables verbose logging", &verbose);
if (!flags.Parse("aapt2 link", args, &std::cerr)) {

View File

@@ -76,4 +76,12 @@ void* BigBuffer::NextBlock(size_t* out_size) {
return blocks_.back().buffer.get();
}
std::string BigBuffer::to_string() const {
std::string result;
for (const Block& block : blocks_) {
result.append(block.buffer.get(), block.buffer.get() + block.size);
}
return result;
}
} // namespace aapt

View File

@@ -19,6 +19,7 @@
#include <cstring>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
@@ -116,6 +117,8 @@ class BigBuffer {
const_iterator begin() const;
const_iterator end() const;
std::string to_string() const;
private:
DISALLOW_COPY_AND_ASSIGN(BigBuffer);