Merge changes from topic "art-enable-apex-libs"

* changes:
  Revert^2 "Switch from version script to APEX stubs for libdexfile_external and add it to the Runtime APEX."
  Add remaining libnative* libs to the Runtime APEX.
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index d7ca168..f7ed826 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -89,6 +89,11 @@
 art_tools_device_binaries = art_tools_common_binaries + art_tools_device_only_binaries
 art_tools_host_binaries = art_tools_common_binaries + art_tools_host_only_binaries
 
+// Libraries needed to use com.android.runtime.host for zipapex run-tests
+art_runtime_host_run_test_libs = [
+    "libartd-disassembler"
+]
+
 // Libcore native libraries.
 libcore_native_shared_libs = [
     "libjavacore",
@@ -107,6 +112,14 @@
     "apache-xml",
 ]
 
+// Temporary library includes for b/123591866 as all libraries are moved into the main art-apex.
+art_runtime_libraries_zipapex = [
+    "libnativebridge",
+    "libnativeloader",
+    "libnativehelper",
+    "libcutils",
+]
+
 apex_key {
     name: "com.android.runtime.key",
     public_key: "com.android.runtime.avbpubkey",
@@ -192,7 +205,7 @@
 // because binaries have different multilib classes and 'multilib: {}' isn't
 // supported by target: { ... }.
 // See b/120617876 for more information.
-art_apex {
+art_apex_test {
     name: "com.android.runtime.host",
     compile_multilib: "both",
     payload_type: "zip",
@@ -204,7 +217,9 @@
     native_shared_libs: art_runtime_base_native_shared_libs
         + art_runtime_debug_native_shared_libs
         + libcore_native_shared_libs
-        + libcore_debug_native_shared_libs,
+        + libcore_debug_native_shared_libs
+        + art_runtime_libraries_zipapex
+        + art_runtime_host_run_test_libs,
     multilib: {
         both: {
             // TODO: Add logic to create a `dalvikvm` symlink to `dalvikvm32` or `dalvikvm64`
diff --git a/build/art.go b/build/art.go
index 5236e31..4b63829 100644
--- a/build/art.go
+++ b/build/art.go
@@ -298,11 +298,11 @@
 	// changes this to 'prefer32' on all host binaries. Since HOST_PREFER_32_BIT is
 	// only used for testing we can just disable the module.
 	// See b/120617876 for more information.
-	android.RegisterModuleType("art_apex", artApexBundleFactory)
+	android.RegisterModuleType("art_apex_test", artTestApexBundleFactory)
 }
 
-func artApexBundleFactory() android.Module {
-	module := apex.ApexBundleFactory()
+func artTestApexBundleFactory() android.Module {
+	module := apex.ApexBundleFactory( /*testApex*/ true)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
 		if envTrue(ctx, "HOST_PREFER_32_BIT") {
 			type props struct {
@@ -356,7 +356,7 @@
 func libartStaticDefaultsFactory() android.Module {
 	c := &codegenProperties{}
 	module := cc.DefaultsFactory(c)
-	android.AddLoadHook(module, func(ctx android.LoadHookContext) {	codegen(ctx, c, true) })
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) { codegen(ctx, c, true) })
 
 	return module
 }
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index c70674b..4670b3f 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -2355,10 +2355,6 @@
   // 2) Their inputs are identical.
   bool Equals(const HInstruction* other) const;
 
-  // TODO: Remove this indirection when the [[pure]] attribute proposal (n3744)
-  // is adopted and implemented by our C++ compiler(s). Fow now, we need to hide
-  // the virtual function because the __attribute__((__pure__)) doesn't really
-  // apply the strong requirement for virtual functions, preventing optimizations.
   InstructionKind GetKind() const { return GetPackedField<InstructionKindField>(); }
 
   virtual size_t ComputeHashCode() const {
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 92850f7..988c612 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -15,14 +15,16 @@
  */
 
 #include <string>
+#include <string_view>
 
-#include "base/logging.h"  // For InitLogging.
-#include "base/mutex.h"
-#include "base/os.h"
-#include "base/utils.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "base/file_utils.h"
+#include "base/logging.h"  // For InitLogging.
+#include "base/mutex.h"
+#include "base/os.h"
+#include "base/string_view_cpp20.h"
+#include "base/utils.h"
 #include "compiler_filter.h"
 #include "class_loader_context.h"
 #include "dex/dex_file.h"
@@ -155,56 +157,57 @@
     }
 
     for (int i = 0; i < argc; ++i) {
-      const StringPiece option(argv[i]);
+      const char* raw_option = argv[i];
+      const std::string_view option(raw_option);
       if (option == "--assume-profile-changed") {
         assume_profile_changed_ = true;
-      } else if (option.starts_with("--dex-file=")) {
-        dex_file_ = option.substr(strlen("--dex-file=")).ToString();
-      } else if (option.starts_with("--compiler-filter=")) {
-        std::string filter_str = option.substr(strlen("--compiler-filter=")).ToString();
-        if (!CompilerFilter::ParseCompilerFilter(filter_str.c_str(), &compiler_filter_)) {
-          Usage("Invalid compiler filter '%s'", option.data());
+      } else if (StartsWith(option, "--dex-file=")) {
+        dex_file_ = std::string(option.substr(strlen("--dex-file=")));
+      } else if (StartsWith(option, "--compiler-filter=")) {
+        const char* filter_str = raw_option + strlen("--compiler-filter=");
+        if (!CompilerFilter::ParseCompilerFilter(filter_str, &compiler_filter_)) {
+          Usage("Invalid compiler filter '%s'", raw_option);
         }
-      } else if (option.starts_with("--isa=")) {
-        std::string isa_str = option.substr(strlen("--isa=")).ToString();
-        isa_ = GetInstructionSetFromString(isa_str.c_str());
+      } else if (StartsWith(option, "--isa=")) {
+        const char* isa_str = raw_option + strlen("--isa=");
+        isa_ = GetInstructionSetFromString(isa_str);
         if (isa_ == InstructionSet::kNone) {
-          Usage("Invalid isa '%s'", option.data());
+          Usage("Invalid isa '%s'", raw_option);
         }
-      } else if (option.starts_with("--image=")) {
-        image_ = option.substr(strlen("--image=")).ToString();
+      } else if (StartsWith(option, "--image=")) {
+        image_ = std::string(option.substr(strlen("--image=")));
       } else if (option == "--runtime-arg") {
         if (i + 1 == argc) {
           Usage("Missing argument for --runtime-arg\n");
         }
         ++i;
         runtime_args_.push_back(argv[i]);
-      } else if (option.starts_with("--android-data=")) {
+      } else if (StartsWith(option, "--android-data=")) {
         // Overwrite android-data if needed (oat file assistant relies on a valid directory to
         // compute dalvik-cache folder). This is mostly used in tests.
-        std::string new_android_data = option.substr(strlen("--android-data=")).ToString();
-        setenv("ANDROID_DATA", new_android_data.c_str(), 1);
-      } else if (option.starts_with("--downgrade")) {
+        const char* new_android_data = raw_option + strlen("--android-data=");
+        setenv("ANDROID_DATA", new_android_data, 1);
+      } else if (option == "--downgrade") {
         downgrade_ = true;
-      } else if (option.starts_with("--oat-fd")) {
-        oat_fd_ = std::stoi(option.substr(strlen("--oat-fd=")).ToString(), nullptr, 0);
+      } else if (StartsWith(option, "--oat-fd=")) {
+        oat_fd_ = std::stoi(std::string(option.substr(strlen("--oat-fd="))), nullptr, 0);
         if (oat_fd_ < 0) {
           Usage("Invalid --oat-fd %d", oat_fd_);
         }
-      } else if (option.starts_with("--vdex-fd")) {
-        vdex_fd_ = std::stoi(option.substr(strlen("--vdex-fd=")).ToString(), nullptr, 0);
+      } else if (StartsWith(option, "--vdex-fd=")) {
+        vdex_fd_ = std::stoi(std::string(option.substr(strlen("--vdex-fd="))), nullptr, 0);
         if (vdex_fd_ < 0) {
           Usage("Invalid --vdex-fd %d", vdex_fd_);
         }
-      } else if (option.starts_with("--zip-fd")) {
-          zip_fd_ = std::stoi(option.substr(strlen("--zip-fd=")).ToString(), nullptr, 0);
-          if (zip_fd_ < 0) {
-            Usage("Invalid --zip-fd %d", zip_fd_);
-          }
-      } else if (option.starts_with("--class-loader-context=")) {
-        context_str_ = option.substr(strlen("--class-loader-context=")).ToString();
+      } else if (StartsWith(option, "--zip-fd=")) {
+        zip_fd_ = std::stoi(std::string(option.substr(strlen("--zip-fd="))), nullptr, 0);
+        if (zip_fd_ < 0) {
+          Usage("Invalid --zip-fd %d", zip_fd_);
+        }
+      } else if (StartsWith(option, "--class-loader-context=")) {
+        context_str_ = std::string(option.substr(strlen("--class-loader-context=")));
       } else {
-        Usage("Unknown argument '%s'", option.data());
+        Usage("Unknown argument '%s'", raw_option);
       }
     }
 
diff --git a/libartbase/base/utils.cc b/libartbase/base/utils.cc
index b989d9e..30423a4 100644
--- a/libartbase/base/utils.cc
+++ b/libartbase/base/utils.cc
@@ -190,45 +190,6 @@
 #endif
 }
 
-static void ParseStringAfterChar(const std::string& s,
-                                 char c,
-                                 std::string* parsed_value,
-                                 UsageFn Usage) {
-  std::string::size_type colon = s.find(c);
-  if (colon == std::string::npos) {
-    Usage("Missing char %c in option %s\n", c, s.c_str());
-  }
-  // Add one to remove the char we were trimming until.
-  *parsed_value = s.substr(colon + 1);
-}
-
-void ParseDouble(const std::string& option,
-                 char after_char,
-                 double min,
-                 double max,
-                 double* parsed_value,
-                 UsageFn Usage) {
-  std::string substring;
-  ParseStringAfterChar(option, after_char, &substring, Usage);
-  bool sane_val = true;
-  double value;
-  if ((false)) {
-    // TODO: this doesn't seem to work on the emulator.  b/15114595
-    std::stringstream iss(substring);
-    iss >> value;
-    // Ensure that we have a value, there was no cruft after it and it satisfies a sensible range.
-    sane_val = iss.eof() && (value >= min) && (value <= max);
-  } else {
-    char* end = nullptr;
-    value = strtod(substring.c_str(), &end);
-    sane_val = *end == '\0' && value >= min && value <= max;
-  }
-  if (!sane_val) {
-    Usage("Invalid double value %s for option %s\n", substring.c_str(), option.c_str());
-  }
-  *parsed_value = value;
-}
-
 void SleepForever() {
   while (true) {
     usleep(1000000);
diff --git a/libartbase/base/utils.h b/libartbase/base/utils.h
index 11472a8..9284950 100644
--- a/libartbase/base/utils.h
+++ b/libartbase/base/utils.h
@@ -30,7 +30,6 @@
 #include "enums.h"
 #include "globals.h"
 #include "macros.h"
-#include "stringpiece.h"
 
 namespace art {
 
@@ -91,44 +90,6 @@
   return reinterpret_cast<const void*>(code);
 }
 
-using UsageFn = void (*)(const char*, ...);
-
-template <typename T>
-static void ParseIntOption(const StringPiece& option,
-                            const std::string& option_name,
-                            T* out,
-                            UsageFn usage,
-                            bool is_long_option = true) {
-  std::string option_prefix = option_name + (is_long_option ? "=" : "");
-  DCHECK(option.starts_with(option_prefix)) << option << " " << option_prefix;
-  const char* value_string = option.substr(option_prefix.size()).data();
-  int64_t parsed_integer_value = 0;
-  if (!android::base::ParseInt(value_string, &parsed_integer_value)) {
-    usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value_string);
-  }
-  *out = dchecked_integral_cast<T>(parsed_integer_value);
-}
-
-template <typename T>
-static void ParseUintOption(const StringPiece& option,
-                            const std::string& option_name,
-                            T* out,
-                            UsageFn usage,
-                            bool is_long_option = true) {
-  ParseIntOption(option, option_name, out, usage, is_long_option);
-  if (*out < 0) {
-    usage("%s passed a negative value %d", option_name.c_str(), *out);
-    *out = 0;
-  }
-}
-
-void ParseDouble(const std::string& option,
-                 char after_char,
-                 double min,
-                 double max,
-                 double* parsed_value,
-                 UsageFn Usage);
-
 #if defined(__BIONIC__)
 struct Arc4RandomGenerator {
   typedef uint32_t result_type;
diff --git a/profman/profman.cc b/profman/profman.cc
index 82d9df0..b29e743 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -25,6 +25,7 @@
 #include <iostream>
 #include <set>
 #include <string>
+#include <string_view>
 #include <unordered_set>
 #include <vector>
 
@@ -36,7 +37,7 @@
 #include "base/mem_map.h"
 #include "base/scoped_flock.h"
 #include "base/stl_util.h"
-#include "base/stringpiece.h"
+#include "base/string_view_cpp20.h"
 #include "base/time_utils.h"
 #include "base/unix_file/fd_file.h"
 #include "base/utils.h"
@@ -188,6 +189,33 @@
   exit(1);
 }
 
+template <typename T>
+static void ParseUintOption(const char* raw_option,
+                            std::string_view option_prefix,
+                            T* out) {
+  DCHECK(EndsWith(option_prefix, "="));
+  DCHECK(StartsWith(raw_option, option_prefix)) << raw_option << " " << option_prefix;
+  const char* value_string = raw_option + option_prefix.size();
+  int64_t parsed_integer_value = 0;
+  if (!android::base::ParseInt(value_string, &parsed_integer_value)) {
+    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
+    Usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value_string);
+  }
+  if (parsed_integer_value < 0) {
+    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
+    Usage("%s passed a negative value %" PRId64, option_name.c_str(), parsed_integer_value);
+  }
+  if (static_cast<uint64_t>(parsed_integer_value) >
+      static_cast<std::make_unsigned_t<T>>(std::numeric_limits<T>::max())) {
+    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
+    Usage("%s passed a value %" PRIu64 " above max (%" PRIu64 ")",
+          option_name.c_str(),
+          static_cast<uint64_t>(parsed_integer_value),
+          static_cast<uint64_t>(std::numeric_limits<T>::max()));
+  }
+  *out = dchecked_integral_cast<T>(parsed_integer_value);
+}
+
 // TODO(calin): This class has grown too much from its initial design. Split the functionality
 // into smaller, more contained pieces.
 class ProfMan final {
@@ -226,7 +254,8 @@
     }
 
     for (int i = 0; i < argc; ++i) {
-      const StringPiece option(argv[i]);
+      const char* raw_option = argv[i];
+      const std::string_view option(raw_option);
       const bool log_options = false;
       if (log_options) {
         LOG(INFO) << "profman: option[" << i << "]=" << argv[i];
@@ -235,66 +264,60 @@
         dump_only_ = true;
       } else if (option == "--dump-classes-and-methods") {
         dump_classes_and_methods_ = true;
-      } else if (option.starts_with("--create-profile-from=")) {
-        create_profile_from_file_ = option.substr(strlen("--create-profile-from=")).ToString();
-      } else if (option.starts_with("--dump-output-to-fd=")) {
-        ParseUintOption(option, "--dump-output-to-fd", &dump_output_to_fd_, Usage);
+      } else if (StartsWith(option, "--create-profile-from=")) {
+        create_profile_from_file_ = std::string(option.substr(strlen("--create-profile-from=")));
+      } else if (StartsWith(option, "--dump-output-to-fd=")) {
+        ParseUintOption(raw_option, "--dump-output-to-fd=", &dump_output_to_fd_);
       } else if (option == "--generate-boot-image-profile") {
         generate_boot_image_profile_ = true;
-      } else if (option.starts_with("--boot-image-class-threshold=")) {
-        ParseUintOption(option,
-                        "--boot-image-class-threshold",
-                        &boot_image_options_.image_class_theshold,
-                        Usage);
-      } else if (option.starts_with("--boot-image-clean-class-threshold=")) {
-        ParseUintOption(option,
-                        "--boot-image-clean-class-threshold",
-                        &boot_image_options_.image_class_clean_theshold,
-                        Usage);
-      } else if (option.starts_with("--boot-image-sampled-method-threshold=")) {
-        ParseUintOption(option,
-                        "--boot-image-sampled-method-threshold",
-                        &boot_image_options_.compiled_method_threshold,
-                        Usage);
-      } else if (option.starts_with("--profile-file=")) {
-        profile_files_.push_back(option.substr(strlen("--profile-file=")).ToString());
-      } else if (option.starts_with("--profile-file-fd=")) {
-        ParseFdForCollection(option, "--profile-file-fd", &profile_files_fd_);
-      } else if (option.starts_with("--reference-profile-file=")) {
-        reference_profile_file_ = option.substr(strlen("--reference-profile-file=")).ToString();
-      } else if (option.starts_with("--reference-profile-file-fd=")) {
-        ParseUintOption(option, "--reference-profile-file-fd", &reference_profile_file_fd_, Usage);
-      } else if (option.starts_with("--dex-location=")) {
-        dex_locations_.push_back(option.substr(strlen("--dex-location=")).ToString());
-      } else if (option.starts_with("--apk-fd=")) {
-        ParseFdForCollection(option, "--apk-fd", &apks_fd_);
-      } else if (option.starts_with("--apk=")) {
-        apk_files_.push_back(option.substr(strlen("--apk=")).ToString());
-      } else if (option.starts_with("--generate-test-profile=")) {
-        test_profile_ = option.substr(strlen("--generate-test-profile=")).ToString();
-      } else if (option.starts_with("--generate-test-profile-num-dex=")) {
-        ParseUintOption(option,
-                        "--generate-test-profile-num-dex",
-                        &test_profile_num_dex_,
-                        Usage);
-      } else if (option.starts_with("--generate-test-profile-method-percentage")) {
-        ParseUintOption(option,
-                        "--generate-test-profile-method-percentage",
-                        &test_profile_method_percerntage_,
-                        Usage);
-      } else if (option.starts_with("--generate-test-profile-class-percentage")) {
-        ParseUintOption(option,
-                        "--generate-test-profile-class-percentage",
-                        &test_profile_class_percentage_,
-                        Usage);
-      } else if (option.starts_with("--generate-test-profile-seed=")) {
-        ParseUintOption(option, "--generate-test-profile-seed", &test_profile_seed_, Usage);
-      } else if (option.starts_with("--copy-and-update-profile-key")) {
+      } else if (StartsWith(option, "--boot-image-class-threshold=")) {
+        ParseUintOption(raw_option,
+                        "--boot-image-class-threshold=",
+                        &boot_image_options_.image_class_theshold);
+      } else if (StartsWith(option, "--boot-image-clean-class-threshold=")) {
+        ParseUintOption(raw_option,
+                        "--boot-image-clean-class-threshold=",
+                        &boot_image_options_.image_class_clean_theshold);
+      } else if (StartsWith(option, "--boot-image-sampled-method-threshold=")) {
+        ParseUintOption(raw_option,
+                        "--boot-image-sampled-method-threshold=",
+                        &boot_image_options_.compiled_method_threshold);
+      } else if (StartsWith(option, "--profile-file=")) {
+        profile_files_.push_back(std::string(option.substr(strlen("--profile-file="))));
+      } else if (StartsWith(option, "--profile-file-fd=")) {
+        ParseFdForCollection(raw_option, "--profile-file-fd=", &profile_files_fd_);
+      } else if (StartsWith(option, "--reference-profile-file=")) {
+        reference_profile_file_ = std::string(option.substr(strlen("--reference-profile-file=")));
+      } else if (StartsWith(option, "--reference-profile-file-fd=")) {
+        ParseUintOption(raw_option, "--reference-profile-file-fd=", &reference_profile_file_fd_);
+      } else if (StartsWith(option, "--dex-location=")) {
+        dex_locations_.push_back(std::string(option.substr(strlen("--dex-location="))));
+      } else if (StartsWith(option, "--apk-fd=")) {
+        ParseFdForCollection(raw_option, "--apk-fd=", &apks_fd_);
+      } else if (StartsWith(option, "--apk=")) {
+        apk_files_.push_back(std::string(option.substr(strlen("--apk="))));
+      } else if (StartsWith(option, "--generate-test-profile=")) {
+        test_profile_ = std::string(option.substr(strlen("--generate-test-profile=")));
+      } else if (StartsWith(option, "--generate-test-profile-num-dex=")) {
+        ParseUintOption(raw_option,
+                        "--generate-test-profile-num-dex=",
+                        &test_profile_num_dex_);
+      } else if (StartsWith(option, "--generate-test-profile-method-percentage=")) {
+        ParseUintOption(raw_option,
+                        "--generate-test-profile-method-percentage=",
+                        &test_profile_method_percerntage_);
+      } else if (StartsWith(option, "--generate-test-profile-class-percentage=")) {
+        ParseUintOption(raw_option,
+                        "--generate-test-profile-class-percentage=",
+                        &test_profile_class_percentage_);
+      } else if (StartsWith(option, "--generate-test-profile-seed=")) {
+        ParseUintOption(raw_option, "--generate-test-profile-seed=", &test_profile_seed_);
+      } else if (option == "--copy-and-update-profile-key") {
         copy_and_update_profile_key_ = true;
-      } else if (option.starts_with("--store-aggregation-counters")) {
+      } else if (option == "--store-aggregation-counters") {
         store_aggregation_counters_ = true;
       } else {
-        Usage("Unknown argument '%s'", option.data());
+        Usage("Unknown argument '%s'", raw_option);
       }
     }
 
@@ -1265,11 +1288,11 @@
   }
 
  private:
-  static void ParseFdForCollection(const StringPiece& option,
-                                   const char* arg_name,
+  static void ParseFdForCollection(const char* raw_option,
+                                   std::string_view option_prefix,
                                    std::vector<int>* fds) {
     int fd;
-    ParseUintOption(option, arg_name, &fd, Usage);
+    ParseUintOption(raw_option, option_prefix, &fd);
     fds->push_back(fd);
   }
 
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 2e1f364..148fdba 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1449,6 +1449,7 @@
 
   static void UpdateInternStrings(
       gc::space::ImageSpace* space,
+      bool use_preresolved_strings,
       const SafeMap<mirror::String*, mirror::String*>& intern_remap)
       REQUIRES_SHARED(Locks::mutator_lock_);
 };
@@ -1464,8 +1465,10 @@
   ScopedTrace app_image_timing("AppImage:Updating");
 
   Thread* const self = Thread::Current();
-  gc::Heap* const heap = Runtime::Current()->GetHeap();
+  Runtime* const runtime = Runtime::Current();
+  gc::Heap* const heap = runtime->GetHeap();
   const ImageHeader& header = space->GetImageHeader();
+  bool load_app_image_startup_cache = runtime->LoadAppImageStartupCache();
   {
     // Register dex caches with the class loader.
     WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
@@ -1479,6 +1482,10 @@
         class_linker->RegisterDexFileLocked(*dex_file, dex_cache, class_loader.Get());
       }
 
+      if (!load_app_image_startup_cache) {
+        dex_cache->ClearPreResolvedStrings();
+      }
+
       if (kIsDebugBuild) {
         CHECK(new_class_set != nullptr);
         mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes();
@@ -1545,11 +1552,13 @@
 
 void AppImageLoadingHelper::UpdateInternStrings(
     gc::space::ImageSpace* space,
+    bool use_preresolved_strings,
     const SafeMap<mirror::String*, mirror::String*>& intern_remap) {
   const uint8_t* target_base = space->Begin();
   const ImageSection& sro_section =
       space->GetImageHeader().GetImageStringReferenceOffsetsSection();
   const size_t num_string_offsets = sro_section.Size() / sizeof(AppImageReferenceOffsetInfo);
+  InternTable* const intern_table = Runtime::Current()->GetInternTable();
 
   VLOG(image)
       << "ClassLinker:AppImage:InternStrings:imageStringReferenceOffsetCount = "
@@ -1582,24 +1591,29 @@
         WriteBarrier::ForEveryFieldWrite(dex_cache);
         dex_cache->GetStrings()[string_index].store(
             mirror::StringDexCachePair(it->second, source.index));
+      } else if (!use_preresolved_strings) {
+        dex_cache->GetStrings()[string_index].store(
+            mirror::StringDexCachePair(intern_table->InternStrong(referred_string), source.index));
       }
     } else if (HasDexCachePreResolvedStringNativeRefTag(base_offset)) {
-      base_offset = ClearDexCacheNativeRefTags(base_offset);
-      DCHECK_ALIGNED(base_offset, 2);
+      if (use_preresolved_strings) {
+        base_offset = ClearDexCacheNativeRefTags(base_offset);
+        DCHECK_ALIGNED(base_offset, 2);
 
-      ObjPtr<mirror::DexCache> dex_cache =
-          reinterpret_cast<mirror::DexCache*>(space->Begin() + base_offset);
-      uint32_t string_index = sro_base[offset_index].second;
+        ObjPtr<mirror::DexCache> dex_cache =
+            reinterpret_cast<mirror::DexCache*>(space->Begin() + base_offset);
+        uint32_t string_index = sro_base[offset_index].second;
 
-      ObjPtr<mirror::String> referred_string =
-          dex_cache->GetPreResolvedStrings()[string_index].Read();
-      DCHECK(referred_string != nullptr);
+        ObjPtr<mirror::String> referred_string =
+            dex_cache->GetPreResolvedStrings()[string_index].Read();
+        DCHECK(referred_string != nullptr);
 
-      auto it = intern_remap.find(referred_string.Ptr());
-      if (it != intern_remap.end()) {
-        // Because we are not using a helper function we need to mark the GC card manually.
-        WriteBarrier::ForEveryFieldWrite(dex_cache);
-        dex_cache->GetPreResolvedStrings()[string_index] = GcRoot<mirror::String>(it->second);
+        auto it = intern_remap.find(referred_string.Ptr());
+        if (it != intern_remap.end()) {
+          // Because we are not using a helper function we need to mark the GC card manually.
+          WriteBarrier::ForEveryFieldWrite(dex_cache);
+          dex_cache->GetPreResolvedStrings()[string_index] = GcRoot<mirror::String>(it->second);
+        }
       }
     } else {
       uint32_t raw_member_offset = sro_base[offset_index].second;
@@ -1622,6 +1636,13 @@
                                 /* kCheckTransaction= */ false,
                                 kVerifyNone,
                                 /* kIsVolatile= */ false>(member_offset, it->second);
+      } else if (!use_preresolved_strings) {
+        obj_ptr->SetFieldObject</* kTransactionActive= */ false,
+                                /* kCheckTransaction= */ false,
+                                kVerifyNone,
+                                /* kIsVolatile= */ false>(
+            member_offset,
+            intern_table->InternStrong(referred_string));
       }
     }
   }
@@ -1632,13 +1653,16 @@
   // the strings they point to.
   ScopedTrace timing("AppImage:InternString");
 
-  InternTable* const intern_table = Runtime::Current()->GetInternTable();
+  Runtime* const runtime = Runtime::Current();
+  InternTable* const intern_table = runtime->GetInternTable();
+
+  const bool load_startup_cache = runtime->LoadAppImageStartupCache();
 
   // Add the intern table, removing any conflicts. For conflicts, store the new address in a map
   // for faster lookup.
   // TODO: Optimize with a bitmap or bloom filter
   SafeMap<mirror::String*, mirror::String*> intern_remap;
-  intern_table->AddImageStringsToTable(space, [&](InternTable::UnorderedSet& interns)
+  auto func = [&](InternTable::UnorderedSet& interns)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(Locks::intern_table_lock_) {
     const size_t non_boot_image_strings = intern_table->CountInterns(
@@ -1681,14 +1705,23 @@
         CHECK(intern_table->LookupStrongLocked(string) == nullptr) << string->ToModifiedUtf8();
       }
     }
-  });
+  };
 
-  VLOG(image) << "AppImage:conflictingInternStrings = " << intern_remap.size();
+  bool update_intern_strings;
+  if (load_startup_cache) {
+    // Only add the intern table if we are using the startup cache. Otherwise,
+    // UpdateInternStrings adds the strings to the intern table.
+    intern_table->AddImageStringsToTable(space, func);
+    update_intern_strings = kIsDebugBuild || !intern_remap.empty();
+    VLOG(image) << "AppImage:conflictingInternStrings = " << intern_remap.size();
+  } else {
+    update_intern_strings = true;
+  }
 
   // For debug builds, always run the code below to get coverage.
-  if (kIsDebugBuild || !intern_remap.empty()) {
+  if (update_intern_strings) {
     // Slow path case is when there are conflicting intern strings to fix up.
-    UpdateInternStrings(space, intern_remap);
+    UpdateInternStrings(space, /*use_preresolved_strings=*/ load_startup_cache, intern_remap);
   }
 }
 
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index c9bdbda..91cea79 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -409,7 +409,7 @@
 
   void AssertDexFileClass(ObjPtr<mirror::ClassLoader> class_loader, const std::string& descriptor)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    ASSERT_TRUE(descriptor != nullptr);
+    ASSERT_FALSE(descriptor.empty());
     Thread* self = Thread::Current();
     StackHandleScope<1> hs(self);
     Handle<mirror::Class> klass(
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 47b621a..f0ad931 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -110,8 +110,7 @@
   WriteBarrier::ForEveryFieldWrite(this);
 }
 
-inline void DexCache::SetPreResolvedString(dex::StringIndex string_idx,
-                                           ObjPtr<String> resolved) {
+inline void DexCache::SetPreResolvedString(dex::StringIndex string_idx, ObjPtr<String> resolved) {
   DCHECK(resolved != nullptr);
   DCHECK_LT(string_idx.index_, GetDexFile()->NumStringIds());
   GetPreResolvedStrings()[string_idx.index_] = GcRoot<mirror::String>(resolved);
@@ -122,6 +121,17 @@
   WriteBarrier::ForEveryFieldWrite(this);
 }
 
+inline void DexCache::ClearPreResolvedStrings() {
+  SetFieldPtr64</*kTransactionActive=*/false,
+                /*kCheckTransaction=*/false,
+                kVerifyNone,
+                GcRoot<mirror::String>*>(PreResolvedStringsOffset(), nullptr);
+  SetField32</*kTransactionActive=*/false,
+             /*bool kCheckTransaction=*/false,
+             kVerifyNone,
+             /*kIsVolatile=*/false>(NumPreResolvedStringsOffset(), 0);
+}
+
 inline void DexCache::ClearString(dex::StringIndex string_idx) {
   DCHECK(Runtime::Current()->IsAotCompiler());
   uint32_t slot_idx = StringSlotIndex(string_idx);
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index c742928..b5619f8 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -283,6 +283,11 @@
                             ObjPtr<mirror::String> resolved)
       ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Clear the preresolved string cache to prevent further usage. Not thread safe, so should only
+  // be called when the string cache is guaranteed to not be accessed.
+  void ClearPreResolvedStrings()
+      ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Clear a string for a string_idx, used to undo string intern transactions to make sure
   // the string isn't kept live.
   void ClearString(dex::StringIndex string_idx) REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index 26fc5e9..891ecef 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -149,6 +149,7 @@
   HIDDEN_API_ENFORCEMENT_POLICY_MASK = (1 << 12)
                                      | (1 << 13),
   PROFILE_SYSTEM_SERVER              = 1 << 14,
+  USE_APP_IMAGE_STARTUP_CACHE        = 1 << 16,
 
   // bits to shift (flags & HIDDEN_API_ENFORCEMENT_POLICY_MASK) by to get a value
   // corresponding to hiddenapi::EnforcementPolicy
@@ -298,6 +299,10 @@
   bool profile_system_server = (runtime_flags & PROFILE_SYSTEM_SERVER) == PROFILE_SYSTEM_SERVER;
   runtime_flags &= ~PROFILE_SYSTEM_SERVER;
 
+  Runtime::Current()->SetLoadAppImageStartupCacheEnabled(
+      (runtime_flags & USE_APP_IMAGE_STARTUP_CACHE) != 0u);
+  runtime_flags &= ~USE_APP_IMAGE_STARTUP_CACHE;
+
   if (runtime_flags != 0) {
     LOG(ERROR) << StringPrintf("Unknown bits set in runtime_flags: %#x", runtime_flags);
   }
diff --git a/runtime/runtime.h b/runtime/runtime.h
index ee2c514..81c17a5 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -823,6 +823,14 @@
     ThreadPool* const thread_pool_;
   };
 
+  bool LoadAppImageStartupCache() const {
+    return load_app_image_startup_cache_;
+  }
+
+  void SetLoadAppImageStartupCacheEnabled(bool enabled) {
+    load_app_image_startup_cache_ = enabled;
+  }
+
  private:
   static void InitPlatformSignalHandlers();
 
@@ -1145,6 +1153,8 @@
 
   uint32_t verifier_logging_threshold_ms_;
 
+  bool load_app_image_startup_cache_ = false;
+
   // Note: See comments on GetFaultMessage.
   friend std::string GetFaultMessageForAbortLogging();
   friend class ScopedThreadPoolUsage;
diff --git a/runtime/verifier/method_verifier_test.cc b/runtime/verifier/method_verifier_test.cc
index 36890a6..09171a4 100644
--- a/runtime/verifier/method_verifier_test.cc
+++ b/runtime/verifier/method_verifier_test.cc
@@ -35,7 +35,7 @@
  protected:
   void VerifyClass(const std::string& descriptor)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    ASSERT_TRUE(descriptor != nullptr);
+    ASSERT_FALSE(descriptor.empty());
     Thread* self = Thread::Current();
     ObjPtr<mirror::Class> klass = class_linker_->FindSystemClass(self, descriptor.c_str());
 
diff --git a/test/005-annotations/expected.txt b/test/005-annotations/expected.txt
index ee5b0c7..b537c8f 100644
--- a/test/005-annotations/expected.txt
+++ b/test/005-annotations/expected.txt
@@ -109,4 +109,4 @@
 
 Get annotation with missing class should not throw
 Got expected TypeNotPresentException
-Got expected NoSuchFieldError
+Got expected Error for renamed enum
diff --git a/test/005-annotations/src/android/test/anno/TestAnnotations.java b/test/005-annotations/src/android/test/anno/TestAnnotations.java
index 8ea8e8e..a3e32f9 100644
--- a/test/005-annotations/src/android/test/anno/TestAnnotations.java
+++ b/test/005-annotations/src/android/test/anno/TestAnnotations.java
@@ -17,6 +17,7 @@
 package android.test.anno;
 
 import java.lang.annotation.Annotation;
+import java.lang.annotation.AnnotationFormatError;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -241,8 +242,8 @@
                 Annotation[] annos = m.getDeclaredAnnotations();
                 System.out.println("  annotations on METH " + m + ":");
             }
-        } catch (NoSuchFieldError expected) {
-            System.out.println("Got expected NoSuchFieldError");
+        } catch (Error expected) {
+            System.out.println("Got expected Error for renamed enum");
         }
 
         // Test if annotations marked VISIBILITY_BUILD are visible to runtime in M and earlier.
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 660c971..2910920 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -1005,12 +1005,14 @@
     export ANDROID_DATA="$DEX_LOCATION"
     export ANDROID_ROOT="${ANDROID_ROOT}"
     export ANDROID_RUNTIME_ROOT="${ANDROID_RUNTIME_ROOT}"
-    export LD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
     if [ "$USE_ZIPAPEX" = "y" ]; then
       # Put the zipapex files in front of the ld-library-path
-      export LD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${LD_LIBRARY_PATH}"
+      export LD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
+      export DYLD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
+    else
+      export LD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
+      export DYLD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
     fi
-    export DYLD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
     export PATH="$PATH:$BIN_DIR"
 
     # Temporarily disable address space layout randomization (ASLR).
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index 2692f68..141dd22 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -18,6 +18,8 @@
 #include <iostream>
 #include <map>
 #include <set>
+#include <string>
+#include <string_view>
 
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -27,6 +29,7 @@
 #include "base/mem_map.h"
 #include "base/os.h"
 #include "base/stl_util.h"
+#include "base/string_view_cpp20.h"
 #include "base/unix_file/fd_file.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/class_accessor-inl.h"
@@ -887,45 +890,48 @@
     argc--;
 
     if (argc > 0) {
-      const StringPiece command(argv[0]);
+      const char* raw_command = argv[0];
+      const std::string_view command(raw_command);
       if (command == "encode") {
         for (int i = 1; i < argc; ++i) {
-          const StringPiece option(argv[i]);
-          if (option.starts_with("--input-dex=")) {
-            boot_dex_paths_.push_back(option.substr(strlen("--input-dex=")).ToString());
-          } else if (option.starts_with("--output-dex=")) {
-            output_dex_paths_.push_back(option.substr(strlen("--output-dex=")).ToString());
-          } else if (option.starts_with("--api-flags=")) {
-            api_flags_path_ = option.substr(strlen("--api-flags=")).ToString();
+          const char* raw_option = argv[i];
+          const std::string_view option(raw_option);
+          if (StartsWith(option, "--input-dex=")) {
+            boot_dex_paths_.push_back(std::string(option.substr(strlen("--input-dex="))));
+          } else if (StartsWith(option, "--output-dex=")) {
+            output_dex_paths_.push_back(std::string(option.substr(strlen("--output-dex="))));
+          } else if (StartsWith(option, "--api-flags=")) {
+            api_flags_path_ = std::string(option.substr(strlen("--api-flags=")));
           } else if (option == "--no-force-assign-all") {
             force_assign_all_ = false;
           } else {
-            Usage("Unknown argument '%s'", option.data());
+            Usage("Unknown argument '%s'", raw_option);
           }
         }
         return Command::kEncode;
       } else if (command == "list") {
         for (int i = 1; i < argc; ++i) {
-          const StringPiece option(argv[i]);
-          if (option.starts_with("--boot-dex=")) {
-            boot_dex_paths_.push_back(option.substr(strlen("--boot-dex=")).ToString());
-          } else if (option.starts_with("--public-stub-classpath=")) {
+          const char* raw_option = argv[i];
+          const std::string_view option(raw_option);
+          if (StartsWith(option, "--boot-dex=")) {
+            boot_dex_paths_.push_back(std::string(option.substr(strlen("--boot-dex="))));
+          } else if (StartsWith(option, "--public-stub-classpath=")) {
             stub_classpaths_.push_back(std::make_pair(
-                option.substr(strlen("--public-stub-classpath=")).ToString(),
+                std::string(option.substr(strlen("--public-stub-classpath="))),
                 ApiList::Whitelist()));
-          } else if (option.starts_with("--core-platform-stub-classpath=")) {
+          } else if (StartsWith(option, "--core-platform-stub-classpath=")) {
             stub_classpaths_.push_back(std::make_pair(
-                option.substr(strlen("--core-platform-stub-classpath=")).ToString(),
+                std::string(option.substr(strlen("--core-platform-stub-classpath="))),
                 ApiList::CorePlatformApi()));
-          } else if (option.starts_with("--out-api-flags=")) {
-            api_flags_path_ = option.substr(strlen("--out-api-flags=")).ToString();
+          } else if (StartsWith(option, "--out-api-flags=")) {
+            api_flags_path_ = std::string(option.substr(strlen("--out-api-flags=")));
           } else {
-            Usage("Unknown argument '%s'", option.data());
+            Usage("Unknown argument '%s'", raw_option);
           }
         }
         return Command::kList;
       } else {
-        Usage("Unknown command '%s'", command.data());
+        Usage("Unknown command '%s'", raw_command);
       }
     } else {
       Usage("No command specified");