1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include <memory> 18#include <vector> 19 20#include "androidfw/StringPiece.h" 21 22#include "Diagnostics.h" 23#include "Flags.h" 24#include "LoadedApk.h" 25#include "ResourceUtils.h" 26#include "SdkConstants.h" 27#include "ValueVisitor.h" 28#include "cmd/Util.h" 29#include "flatten/TableFlattener.h" 30#include "flatten/XmlFlattener.h" 31#include "io/BigBufferInputStream.h" 32#include "io/Util.h" 33#include "optimize/ResourceDeduper.h" 34#include "optimize/VersionCollapser.h" 35#include "split/TableSplitter.h" 36 37using android::StringPiece; 38 39namespace aapt { 40 41struct OptimizeOptions { 42 // Path to the output APK. 43 std::string output_path; 44 45 // Details of the app extracted from the AndroidManifest.xml 46 AppInfo app_info; 47 48 // Split APK options. 49 TableSplitterOptions table_splitter_options; 50 51 // List of output split paths. These are in the same order as `split_constraints`. 52 std::vector<std::string> split_paths; 53 54 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`. 55 std::vector<SplitConstraints> split_constraints; 56 57 TableFlattenerOptions table_flattener_options; 58}; 59 60class OptimizeContext : public IAaptContext { 61 public: 62 OptimizeContext() = default; 63 64 PackageType GetPackageType() override { 65 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to 66 // avoid. 67 return PackageType::kApp; 68 } 69 70 IDiagnostics* GetDiagnostics() override { 71 return &diagnostics_; 72 } 73 74 NameMangler* GetNameMangler() override { 75 UNIMPLEMENTED(FATAL); 76 return nullptr; 77 } 78 79 const std::string& GetCompilationPackage() override { 80 static std::string empty; 81 return empty; 82 } 83 84 uint8_t GetPackageId() override { 85 return 0; 86 } 87 88 SymbolTable* GetExternalSymbols() override { 89 UNIMPLEMENTED(FATAL); 90 return nullptr; 91 } 92 93 bool IsVerbose() override { 94 return verbose_; 95 } 96 97 void SetVerbose(bool val) { 98 verbose_ = val; 99 } 100 101 void SetMinSdkVersion(int sdk_version) { 102 sdk_version_ = sdk_version; 103 } 104 105 int GetMinSdkVersion() override { 106 return sdk_version_; 107 } 108 109 private: 110 DISALLOW_COPY_AND_ASSIGN(OptimizeContext); 111 112 StdErrDiagnostics diagnostics_; 113 bool verbose_ = false; 114 int sdk_version_ = 0; 115}; 116 117class OptimizeCommand { 118 public: 119 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options) 120 : options_(options), context_(context) { 121 } 122 123 int Run(std::unique_ptr<LoadedApk> apk) { 124 if (context_->IsVerbose()) { 125 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK..."); 126 } 127 128 VersionCollapser collapser; 129 if (!collapser.Consume(context_, apk->GetResourceTable())) { 130 return 1; 131 } 132 133 ResourceDeduper deduper; 134 if (!deduper.Consume(context_, apk->GetResourceTable())) { 135 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources"); 136 return 1; 137 } 138 139 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or 140 // equal to the minSdk. 141 options_.split_constraints = 142 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints); 143 144 // Stripping the APK using the TableSplitter. The resource table is modified in place in the 145 // LoadedApk. 146 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options); 147 if (!splitter.VerifySplitConstraints(context_)) { 148 return 1; 149 } 150 splitter.SplitTable(apk->GetResourceTable()); 151 152 auto path_iter = options_.split_paths.begin(); 153 auto split_constraints_iter = options_.split_constraints.begin(); 154 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) { 155 if (context_->IsVerbose()) { 156 context_->GetDiagnostics()->Note( 157 DiagMessage(*path_iter) << "generating split with configurations '" 158 << util::Joiner(split_constraints_iter->configs, ", ") << "'"); 159 } 160 161 // Generate an AndroidManifest.xml for each split. 162 std::unique_ptr<xml::XmlResource> split_manifest = 163 GenerateSplitManifest(options_.app_info, *split_constraints_iter); 164 std::unique_ptr<IArchiveWriter> split_writer = 165 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter); 166 if (!split_writer) { 167 return 1; 168 } 169 170 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) { 171 return 1; 172 } 173 174 ++path_iter; 175 ++split_constraints_iter; 176 } 177 178 std::unique_ptr<IArchiveWriter> writer = 179 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path); 180 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) { 181 return 1; 182 } 183 184 return 0; 185 } 186 187 private: 188 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) { 189 BigBuffer manifest_buffer(4096); 190 XmlFlattener xml_flattener(&manifest_buffer, {}); 191 if (!xml_flattener.Consume(context_, manifest)) { 192 return false; 193 } 194 195 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer); 196 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml", 197 ArchiveEntry::kCompress, writer)) { 198 return false; 199 } 200 201 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files; 202 for (auto& pkg : table->packages) { 203 for (auto& type : pkg->types) { 204 // Sort by config and name, so that we get better locality in the zip file. 205 config_sorted_files.clear(); 206 207 for (auto& entry : type->entries) { 208 for (auto& config_value : entry->values) { 209 FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); 210 if (file_ref == nullptr) { 211 continue; 212 } 213 214 if (file_ref->file == nullptr) { 215 ResourceNameRef name(pkg->name, type->type, entry->name); 216 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource()) 217 << "file for resource " << name << " with config '" 218 << config_value->config << "' not found"); 219 continue; 220 } 221 222 const StringPiece entry_name = entry->name; 223 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref; 224 } 225 } 226 227 for (auto& entry : config_sorted_files) { 228 FileReference* file_ref = entry.second; 229 uint32_t compression_flags = 230 file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u; 231 if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags, 232 writer)) { 233 return false; 234 } 235 } 236 } 237 } 238 239 BigBuffer table_buffer(4096); 240 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer); 241 if (!table_flattener.Consume(context_, table)) { 242 return false; 243 } 244 245 io::BigBufferInputStream table_buffer_in(&table_buffer); 246 if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", 247 ArchiveEntry::kAlign, writer)) { 248 return false; 249 } 250 return true; 251 } 252 253 OptimizeOptions options_; 254 OptimizeContext* context_; 255}; 256 257bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk, 258 OptimizeOptions* out_options) { 259 io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml"); 260 if (manifest_file == nullptr) { 261 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) 262 << "missing AndroidManifest.xml"); 263 return false; 264 } 265 266 std::unique_ptr<io::IData> data = manifest_file->OpenAsData(); 267 if (data == nullptr) { 268 context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource()) 269 << "failed to open file"); 270 return false; 271 } 272 273 std::unique_ptr<xml::XmlResource> manifest = xml::Inflate( 274 data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource()); 275 if (manifest == nullptr) { 276 context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml"); 277 return false; 278 } 279 280 Maybe<AppInfo> app_info = 281 ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics()); 282 if (!app_info) { 283 context->GetDiagnostics()->Error(DiagMessage() 284 << "failed to extract data from AndroidManifest.xml"); 285 return false; 286 } 287 288 out_options->app_info = std::move(app_info.value()); 289 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0)); 290 return true; 291} 292 293int Optimize(const std::vector<StringPiece>& args) { 294 OptimizeContext context; 295 OptimizeOptions options; 296 Maybe<std::string> target_densities; 297 std::vector<std::string> configs; 298 std::vector<std::string> split_args; 299 bool verbose = false; 300 Flags flags = 301 Flags() 302 .RequiredFlag("-o", "Path to the output APK.", &options.output_path) 303 .OptionalFlag( 304 "--target-densities", 305 "Comma separated list of the screen densities that the APK will be optimized for.\n" 306 "All the resources that would be unused on devices of the given densities will be \n" 307 "removed from the APK.", 308 &target_densities) 309 .OptionalFlagList("-c", 310 "Comma separated list of configurations to include. The default\n" 311 "is all configurations.", 312 &configs) 313 .OptionalFlagList("--split", 314 "Split resources matching a set of configs out to a " 315 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n" 316 "On Windows, use a semicolon ';' separator instead.", 317 &split_args) 318 .OptionalSwitch("--enable-sparse-encoding", 319 "Enables encoding sparse entries using a binary search tree.\n" 320 "This decreases APK size at the cost of resource retrieval performance.", 321 &options.table_flattener_options.use_sparse_entries) 322 .OptionalSwitch("-v", "Enables verbose logging", &verbose); 323 324 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) { 325 return 1; 326 } 327 328 if (flags.GetArgs().size() != 1u) { 329 std::cerr << "must have one APK as argument.\n\n"; 330 flags.Usage("aapt2 optimize", &std::cerr); 331 return 1; 332 } 333 334 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]); 335 if (!apk) { 336 return 1; 337 } 338 339 context.SetVerbose(verbose); 340 341 if (target_densities) { 342 // Parse the target screen densities. 343 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) { 344 Maybe<uint16_t> target_density = 345 ParseTargetDensityParameter(config_str, context.GetDiagnostics()); 346 if (!target_density) { 347 return 1; 348 } 349 options.table_splitter_options.preferred_densities.push_back(target_density.value()); 350 } 351 } 352 353 std::unique_ptr<IConfigFilter> filter; 354 if (!configs.empty()) { 355 filter = ParseConfigFilterParameters(configs, context.GetDiagnostics()); 356 if (filter == nullptr) { 357 return 1; 358 } 359 options.table_splitter_options.config_filter = filter.get(); 360 } 361 362 // Parse the split parameters. 363 for (const std::string& split_arg : split_args) { 364 options.split_paths.push_back({}); 365 options.split_constraints.push_back({}); 366 if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(), 367 &options.split_constraints.back())) { 368 return 1; 369 } 370 } 371 372 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) { 373 return 1; 374 } 375 376 OptimizeCommand cmd(&context, options); 377 return cmd.Run(std::move(apk)); 378} 379 380} // namespace aapt 381