Reduce multidex checksum to single scalar value.

Change GetMultiDexChecksums to return a single value rather than
a vector. The single checksum represents the whole multidex set,
and will change if any dex file within the multidex set changes.
Fundamentally, we need a value to represent a .zip/.jar archive,
so that we can check whether our build artifacts are up to date.
We previously used a vector of CRC32 values (one per dex file),
however, one combined checksum per archive is also sufficient.

This is necessary, since the future multidex format will have
single zip entry, so we need to adjust the code to expect just
one checksum per zip file. This separates the change to own CL.

The two sides of the checksum comparison are trivially reduced:
 * Zip files are reduced by XORing the individual CRC values.
 * Likewise, checksums of already open DexFiles are XORed.

As a consequence, ClassLoader path needs to be reduced to
print only single checksum per jar file as well.

Bug: 266950186
Test: ./art/test.py -b --host
Change-Id: I848aee1e4836e87945a5172c7594e266739451e9
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index be44207..56d14a7 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -19,6 +19,7 @@
 
 #include <algorithm>
 #include <iterator>
+#include <optional>
 #include <regex>
 #include <sstream>
 #include <string>
@@ -1089,9 +1090,11 @@
 TEST_F(Dex2oatClassLoaderContextTest, ContextWithOtherDexFiles) {
   std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Nested");
 
+  uint32_t expected_checksum = DexFileLoader::GetMultiDexChecksum(dex_files);
+
   std::string context = "PCL[" + dex_files[0]->GetLocation() + "]";
-  std::string expected_classpath_key = "PCL[" + dex_files[0]->GetLocation() + "*" +
-                                       std::to_string(dex_files[0]->GetLocationChecksum()) + "]";
+  std::string expected_classpath_key =
+      "PCL[" + dex_files[0]->GetLocation() + "*" + std::to_string(expected_checksum) + "]";
   RunTest(context.c_str(), expected_classpath_key.c_str(), true);
 }
 
@@ -2215,21 +2218,9 @@
   const std::string odex_location = out_dir + "/base.odex";
   const std::string valid_context = "PCL[" + dex_files[0]->GetLocation() + "]";
   const std::string stored_context = "PCL[/system/not_real_lib.jar]";
-  std::string expected_stored_context = "PCL[";
-  size_t index = 1;
-  for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    const bool is_first = index == 1u;
-    if (!is_first) {
-      expected_stored_context += ":";
-    }
-    expected_stored_context += "/system/not_real_lib.jar";
-    if (!is_first) {
-      expected_stored_context += "!classes" + std::to_string(index) + ".dex";
-    }
-    expected_stored_context += "*" + std::to_string(dex_file->GetLocationChecksum());
-    ++index;
-  }
-  expected_stored_context += "]";
+  uint32_t checksum = DexFileLoader::GetMultiDexChecksum(dex_files);
+  std::string expected_stored_context =
+      "PCL[/system/not_real_lib.jar*" + std::to_string(checksum) + "]";
   // The class path should not be valid and should fail being stored.
   EXPECT_TRUE(GenerateOdexForTest(GetTestDexFileName("ManyMethods"),
                                   odex_location,
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index db0e1c1..e83cc0d 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -582,13 +582,8 @@
 std::string CommonArtTestImpl::CreateClassPathWithChecksums(
     const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
   CHECK(!dex_files.empty());
-  std::string classpath = dex_files[0]->GetLocation() + "*" +
-      std::to_string(dex_files[0]->GetLocationChecksum());
-  for (size_t i = 1; i < dex_files.size(); i++) {
-    classpath += ":" + dex_files[i]->GetLocation() + "*" +
-        std::to_string(dex_files[i]->GetLocationChecksum());
-  }
-  return classpath;
+  uint32_t checksum = DexFileLoader::GetMultiDexChecksum(dex_files);
+  return dex_files[0]->GetLocation() + "*" + std::to_string(checksum);
 }
 
 CommonArtTestImpl::ForkAndExecResult CommonArtTestImpl::ForkAndExec(
diff --git a/libdexfile/dex/art_dex_file_loader.cc b/libdexfile/dex/art_dex_file_loader.cc
index ac52ca0..056d2fb 100644
--- a/libdexfile/dex/art_dex_file_loader.cc
+++ b/libdexfile/dex/art_dex_file_loader.cc
@@ -37,88 +37,6 @@
 
 namespace art {
 
-using android::base::StringPrintf;
-
-bool ArtDexFileLoader::GetMultiDexChecksums(const char* filename,
-                                            std::vector<uint32_t>* checksums,
-                                            std::vector<std::string>* dex_locations,
-                                            std::string* error_msg,
-                                            int zip_fd,
-                                            bool* zip_file_only_contains_uncompressed_dex) {
-  CHECK(checksums != nullptr);
-  uint32_t magic;
-
-  File fd;
-  if (zip_fd != -1) {
-     if (ReadMagicAndReset(zip_fd, &magic, error_msg)) {
-       fd = File(DupCloexec(zip_fd), /* check_usage= */ false);
-     }
-  } else {
-    fd = OpenAndReadMagic(filename, &magic, error_msg);
-  }
-  if (fd.Fd() == -1) {
-    DCHECK(!error_msg->empty());
-    return false;
-  }
-  if (IsZipMagic(magic)) {
-    std::unique_ptr<ZipArchive> zip_archive(
-        ZipArchive::OpenFromFd(fd.Release(), filename, error_msg));
-    if (zip_archive.get() == nullptr) {
-      *error_msg = StringPrintf("Failed to open zip archive '%s' (error msg: %s)", filename,
-                                error_msg->c_str());
-      return false;
-    }
-
-    if (zip_file_only_contains_uncompressed_dex != nullptr) {
-      // Start by assuming everything is uncompressed.
-      *zip_file_only_contains_uncompressed_dex = true;
-    }
-
-    uint32_t idx = 0;
-    std::string zip_entry_name = GetMultiDexClassesDexName(idx);
-    std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(zip_entry_name.c_str(), error_msg));
-    if (zip_entry.get() == nullptr) {
-      // A zip file with no dex code should be accepted. It's likely a config split APK, which we
-      // are currently passing from higher levels.
-      VLOG(dex) << StringPrintf("Zip archive '%s' doesn't contain %s (error msg: %s)",
-                                filename,
-                                zip_entry_name.c_str(),
-                                error_msg->c_str());
-      return true;
-    }
-
-    do {
-      if (zip_file_only_contains_uncompressed_dex != nullptr) {
-        if (!(zip_entry->IsUncompressed() && zip_entry->IsAlignedTo(alignof(DexFile::Header)))) {
-          *zip_file_only_contains_uncompressed_dex = false;
-        }
-      }
-      checksums->push_back(zip_entry->GetCrc32());
-      dex_locations->push_back(GetMultiDexLocation(idx, filename));
-      zip_entry_name = GetMultiDexClassesDexName(++idx);
-      zip_entry.reset(zip_archive->Find(zip_entry_name.c_str(), error_msg));
-    } while (zip_entry.get() != nullptr);
-    return true;
-  }
-  if (IsMagicValid(magic)) {
-    ArtDexFileLoader loader(fd.Release(), filename);
-    std::vector<std::unique_ptr<const DexFile>> dex_files;
-    if (!loader.Open(/* verify= */ false,
-                     /* verify_checksum= */ false,
-                     error_msg,
-                     &dex_files)) {
-      return false;
-    }
-    for (auto& dex_file : dex_files) {
-      checksums->push_back(dex_file->GetHeader().checksum_);
-      dex_locations->push_back(dex_file->GetLocation());
-    }
-    return true;
-  }
-  *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename);
-  return false;
-}
-
 std::unique_ptr<const DexFile> ArtDexFileLoader::Open(
     const uint8_t* base,
     size_t size,
diff --git a/libdexfile/dex/art_dex_file_loader.h b/libdexfile/dex/art_dex_file_loader.h
index 9f2ae82..2fbc2cb 100644
--- a/libdexfile/dex/art_dex_file_loader.h
+++ b/libdexfile/dex/art_dex_file_loader.h
@@ -36,31 +36,8 @@
 // Class that is used to open dex files and deal with corresponding multidex and location logic.
 class ArtDexFileLoader : public DexFileLoader {
  public:
-  using DexFileLoader::DexFileLoader;
-  virtual ~ArtDexFileLoader() { }
-
-  // Returns the checksums of a file for comparison with GetLocationChecksum().
-  // For .dex files, this is the single header checksum.
-  // For zip files, this is the zip entry CRC32 checksum for classes.dex and
-  // each additional multidex entry classes2.dex, classes3.dex, etc.
-  // If a valid zip_fd is provided the file content will be read directly from
-  // the descriptor and `filename` will be used as alias for error logging. If
-  // zip_fd is -1, the method will try to open the `filename` and read the
-  // content from it.
-  //
-  // The dex_locations vector will be populated with the corresponding multidex
-  // locations.
-  //
-  // Return true if the checksums could be found, false otherwise.
-  static bool GetMultiDexChecksums(const char* filename,
-                                   std::vector<uint32_t>* checksums,
-                                   std::vector<std::string>* dex_locations,
-                                   std::string* error_msg,
-                                   int zip_fd = -1,
-                                   bool* only_contains_uncompressed_dex = nullptr);
-
-  // Don't shadow overloads from base class.
-  using DexFileLoader::Open;
+  using DexFileLoader::DexFileLoader;  // Use constructors from base class.
+  using DexFileLoader::Open;           // Don't shadow overloads from base class.
 
   // Old signature preserved for app-compat.
   std::unique_ptr<const DexFile> Open(const uint8_t* base,
diff --git a/libdexfile/dex/art_dex_file_loader_test.cc b/libdexfile/dex/art_dex_file_loader_test.cc
index d8911ec..4663a02 100644
--- a/libdexfile/dex/art_dex_file_loader_test.cc
+++ b/libdexfile/dex/art_dex_file_loader_test.cc
@@ -19,6 +19,8 @@
 #include <sys/mman.h>
 
 #include <memory>
+#include <optional>
+#include <vector>
 
 #include "base/common_art_test.h"
 #include "base/mem_map.h"
@@ -29,8 +31,8 @@
 #include "dex/class_accessor-inl.h"
 #include "dex/code_item_accessors-inl.h"
 #include "dex/descriptors_names.h"
-#include "dex/dex_file.h"
 #include "dex/dex_file-inl.h"
+#include "dex/dex_file.h"
 #include "dex/dex_file_loader.h"
 
 namespace art {
@@ -95,65 +97,50 @@
 }
 
 TEST_F(ArtDexFileLoaderTest, GetChecksum) {
-  std::vector<uint32_t> checksums;
-  std::vector<std::string> dex_locations;
+  std::optional<uint32_t> checksum;
   std::string error_msg;
-  EXPECT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
-      GetLibCoreDexFileNames()[0].c_str(), &checksums, &dex_locations, &error_msg))
-      << error_msg;
-  ASSERT_EQ(1U, checksums.size());
-  ASSERT_EQ(1U, dex_locations.size());
-  EXPECT_EQ(java_lang_dex_file_->GetLocationChecksum(), checksums[0]);
-  EXPECT_EQ(java_lang_dex_file_->GetLocation(), dex_locations[0]);
+  ArtDexFileLoader dex_loader(GetLibCoreDexFileNames()[0]);
+  ASSERT_TRUE(dex_loader.GetMultiDexChecksum(&checksum, &error_msg)) << error_msg;
+  ASSERT_TRUE(checksum.has_value());
+
+  std::vector<const DexFile*> dex_files{java_lang_dex_file_};
+  uint32_t expected_checksum = DexFileLoader::GetMultiDexChecksum(dex_files);
+  EXPECT_EQ(expected_checksum, checksum.value());
 }
 
-TEST_F(ArtDexFileLoaderTest, GetMultiDexChecksums) {
+TEST_F(ArtDexFileLoaderTest, GetMultiDexChecksum) {
   std::string error_msg;
-  std::vector<uint32_t> checksums;
-  std::vector<std::string> dex_locations;
+  std::optional<uint32_t> checksum;
   std::string multidex_file = GetTestDexFileName("MultiDex");
-  EXPECT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
-      multidex_file.c_str(), &checksums, &dex_locations, &error_msg))
-      << error_msg;
+  ArtDexFileLoader dex_loader(multidex_file);
+  EXPECT_TRUE(dex_loader.GetMultiDexChecksum(&checksum, &error_msg)) << error_msg;
 
   std::vector<std::unique_ptr<const DexFile>> dexes = OpenTestDexFiles("MultiDex");
   ASSERT_EQ(2U, dexes.size());
-  ASSERT_EQ(2U, checksums.size());
-  ASSERT_EQ(2U, dex_locations.size());
+  ASSERT_TRUE(checksum.has_value());
 
+  uint32_t expected_checksum = DexFileLoader::GetMultiDexChecksum(dexes);
   EXPECT_EQ(dexes[0]->GetLocation(), DexFileLoader::GetMultiDexLocation(0, multidex_file.c_str()));
-  EXPECT_EQ(dexes[0]->GetLocation(), dex_locations[0]);
-  EXPECT_EQ(dexes[0]->GetLocationChecksum(), checksums[0]);
-
   EXPECT_EQ(dexes[1]->GetLocation(), DexFileLoader::GetMultiDexLocation(1, multidex_file.c_str()));
-  EXPECT_EQ(dexes[1]->GetLocation(), dex_locations[1]);
-  EXPECT_EQ(dexes[1]->GetLocationChecksum(), checksums[1]);
+  EXPECT_EQ(expected_checksum, checksum.value());
 }
 
 TEST_F(ArtDexFileLoaderTest, GetMultiDexChecksumsEmptyZip) {
   std::string error_msg;
-  std::vector<uint32_t> checksums;
-  std::vector<std::string> dex_locations;
+  std::optional<uint32_t> checksum;
   std::string multidex_file = GetTestDexFileName("MainEmptyUncompressed");
-  EXPECT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
-      multidex_file.c_str(), &checksums, &dex_locations, &error_msg))
-      << error_msg;
-
-  EXPECT_EQ(dex_locations.size(), 0);
-  EXPECT_EQ(checksums.size(), 0);
+  ArtDexFileLoader dex_loader(multidex_file);
+  EXPECT_TRUE(dex_loader.GetMultiDexChecksum(&checksum, &error_msg)) << error_msg;
+  EXPECT_FALSE(checksum.has_value());
 }
 
 TEST_F(ArtDexFileLoaderTest, GetMultiDexChecksumsDexFile) {
   std::string error_msg;
-  std::vector<uint32_t> checksums;
-  std::vector<std::string> dex_locations;
+  std::optional<uint32_t> checksum;
   std::string multidex_file = GetTestDexFileName("VerifierDeps");  // This is a .dex file.
-  EXPECT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
-      multidex_file.c_str(), &checksums, &dex_locations, &error_msg))
-      << error_msg;
-
-  EXPECT_EQ(dex_locations.size(), 1);
-  EXPECT_EQ(checksums.size(), 1);
+  DexFileLoader loader(multidex_file);
+  EXPECT_TRUE(loader.GetMultiDexChecksum(&checksum, &error_msg)) << error_msg;
+  EXPECT_TRUE(checksum.has_value());
 }
 
 TEST_F(ArtDexFileLoaderTest, ClassDefs) {
diff --git a/libdexfile/dex/dex_file.cc b/libdexfile/dex/dex_file.cc
index 080e381..8179559 100644
--- a/libdexfile/dex/dex_file.cc
+++ b/libdexfile/dex/dex_file.cc
@@ -23,12 +23,12 @@
 #include <zlib.h>
 
 #include <memory>
+#include <optional>
 #include <ostream>
 #include <sstream>
 #include <type_traits>
 
 #include "android-base/stringprintf.h"
-
 #include "base/enums.h"
 #include "base/hiddenapi_domain.h"
 #include "base/leb128.h"
diff --git a/libdexfile/dex/dex_file_loader.cc b/libdexfile/dex/dex_file_loader.cc
index d375aac..9b54c15 100644
--- a/libdexfile/dex/dex_file_loader.cc
+++ b/libdexfile/dex/dex_file_loader.cc
@@ -148,9 +148,72 @@
 }
 
 std::string DexFileLoader::GetMultiDexLocation(size_t index, const char* dex_location) {
-  return (index == 0)
-      ? dex_location
-      : StringPrintf("%s%cclasses%zu.dex", dex_location, kMultiDexSeparator, index + 1);
+  if (index == 0) {
+    return dex_location;
+  }
+  DCHECK(!IsMultiDexLocation(dex_location));
+  return StringPrintf("%s%cclasses%zu.dex", dex_location, kMultiDexSeparator, index + 1);
+}
+
+bool DexFileLoader::GetMultiDexChecksum(std::optional<uint32_t>* checksum,
+                                        std::string* error_msg,
+                                        bool* only_contains_uncompressed_dex) {
+  CHECK(checksum != nullptr);
+  checksum->reset();  // Return nullopt for an empty zip archive.
+
+  uint32_t magic;
+  if (!InitAndReadMagic(&magic, error_msg)) {
+    return false;
+  }
+
+  if (IsZipMagic(magic)) {
+    std::unique_ptr<ZipArchive> zip_archive(
+        file_.has_value() ?
+            ZipArchive::OpenFromOwnedFd(file_->Fd(), location_.c_str(), error_msg) :
+            ZipArchive::OpenFromMemory(
+                root_container_->Begin(), root_container_->Size(), location_.c_str(), error_msg));
+    if (zip_archive.get() == nullptr) {
+      DCHECK(!error_msg->empty());
+      return false;
+    }
+    if (only_contains_uncompressed_dex != nullptr) {
+      *only_contains_uncompressed_dex = true;
+    }
+    for (size_t i = 0;; ++i) {
+      std::string name = GetMultiDexClassesDexName(i);
+      std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(name.c_str(), error_msg));
+      if (zip_entry == nullptr) {
+        break;
+      }
+      if (only_contains_uncompressed_dex != nullptr) {
+        if (!(zip_entry->IsUncompressed() && zip_entry->IsAlignedTo(alignof(DexFile::Header)))) {
+          *only_contains_uncompressed_dex = false;
+        }
+      }
+      *checksum = checksum->value_or(kEmptyMultiDexChecksum) ^ zip_entry->GetCrc32();
+    }
+    return true;
+  }
+  if (!MapRootContainer(error_msg)) {
+    return false;
+  }
+  const uint8_t* begin = root_container_->Begin();
+  const uint8_t* end = root_container_->End();
+  for (const uint8_t* ptr = begin; ptr < end;) {
+    const auto* header = reinterpret_cast<const DexFile::Header*>(ptr);
+    size_t size = dchecked_integral_cast<size_t>(end - ptr);
+    if (size < sizeof(*header) || !IsMagicValid(ptr)) {
+      *error_msg = StringPrintf("Invalid dex header: '%s'", filename_.c_str());
+      return false;
+    }
+    if (size < header->file_size_) {
+      *error_msg = StringPrintf("Truncated dex file: '%s'", filename_.c_str());
+      return false;
+    }
+    *checksum = checksum->value_or(kEmptyMultiDexChecksum) ^ header->checksum_;
+    ptr += header->file_size_;
+  }
+  return true;
 }
 
 std::string DexFileLoader::GetDexCanonicalLocation(const char* dex_location) {
diff --git a/libdexfile/dex/dex_file_loader.h b/libdexfile/dex/dex_file_loader.h
index 532445a..14cbdad 100644
--- a/libdexfile/dex/dex_file_loader.h
+++ b/libdexfile/dex/dex_file_loader.h
@@ -32,7 +32,6 @@
 
 class MemMap;
 class OatDexFile;
-class ScopedTrace;
 class ZipArchive;
 
 enum class DexFileLoaderErrorCode {
@@ -72,6 +71,53 @@
   // index == 0, and dex_location + multi-dex-separator + GetMultiDexClassesDexName(index) else.
   static std::string GetMultiDexLocation(size_t index, const char* dex_location);
 
+  // Returns combined checksum of one or more dex files (one checksum for the whole multidex set).
+  //
+  // This uses the source path provided to DexFileLoader constructor.
+  //
+  // Returns false on error.  Sets *checksum to nullopt for an empty set.
+  bool GetMultiDexChecksum(/*out*/ std::optional<uint32_t>* checksum,
+                           /*out*/ std::string* error_msg,
+                           /*out*/ bool* only_contains_uncompressed_dex = nullptr);
+
+  // Returns combined checksum of one or more dex files (one checksum for the whole multidex set).
+  //
+  // This uses already open dex files.
+  //
+  // It starts iteration at index 'i', which must be a primary dex file,
+  // and it sets 'i' to the next primary dex file or to end of the array.
+  template <typename DexFilePtrVector>  // array|vector<unique_ptr|DexFile|OatDexFile*>.
+  static uint32_t GetMultiDexChecksum(const DexFilePtrVector& dex_files,
+                                      /*inout*/ size_t* i) {
+    CHECK_LT(*i, dex_files.size()) << "No dex files";
+    std::optional<uint32_t> checksum;
+    for (; *i < dex_files.size(); ++(*i)) {
+      const char* location = dex_files[*i]->GetLocation().c_str();
+      if (!checksum.has_value()) {                         // First dex file.
+        CHECK(!IsMultiDexLocation(location)) << location;  // Expect primary dex.
+      } else if (!IsMultiDexLocation(location)) {          // Later dex file.
+        break;  // Found another primary dex file, terminate iteration.
+      }
+      checksum = checksum.value_or(kEmptyMultiDexChecksum) ^ dex_files[*i]->GetLocationChecksum();
+    }
+    CHECK(checksum.has_value());
+    return checksum.value();
+  }
+
+  // Calculate checksum of dex files in a vector, starting at index 0.
+  // It will CHECK that the whole vector is consumed (i.e. there is just one primary dex file).
+  template <typename DexFilePtrVector>
+  static uint32_t GetMultiDexChecksum(const DexFilePtrVector& dex_files) {
+    size_t i = 0;
+    uint32_t checksum = GetMultiDexChecksum(dex_files, &i);
+    CHECK_EQ(i, dex_files.size());
+    return checksum;
+  }
+
+  // Non-zero initial value for multi-dex to catch bugs if multi-dex checksum is compared
+  // directly to DexFile::GetLocationChecksum without going through GetMultiDexChecksum.
+  static constexpr uint32_t kEmptyMultiDexChecksum = 1;
+
   // Returns the canonical form of the given dex location.
   //
   // There are different flavors of "dex locations" as follows:
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index 7bc1a04..e925f85 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -316,25 +316,18 @@
       return {};
     }
 
-    std::vector<uint32_t> checksums;
-    std::vector<std::string> dex_locations;
+    std::optional<uint32_t> checksum;
     std::string error_msg;
-    if (!ArtDexFileLoader::GetMultiDexChecksums(
-            actual_path.c_str(), &checksums, &dex_locations, &error_msg)) {
-      LOG(ERROR) << "Failed to get multi-dex checksums: " << error_msg;
+    ArtDexFileLoader dex_loader(actual_path);
+    if (!dex_loader.GetMultiDexChecksum(&checksum, &error_msg)) {
+      LOG(ERROR) << "Failed to get multi-dex checksum: " << error_msg;
       return {};
     }
 
-    std::ostringstream oss;
-    for (size_t i = 0; i < checksums.size(); ++i) {
-      if (i != 0) {
-        oss << ';';
-      }
-      oss << StringPrintf("%08x", checksums[i]);
-    }
-    const std::string checksum = oss.str();
+    const std::string checksum_str =
+        checksum.has_value() ? StringPrintf("%08x", checksum.value()) : std::string();
 
-    Result<T> component = custom_generator(path, static_cast<uint64_t>(sb.st_size), checksum);
+    Result<T> component = custom_generator(path, static_cast<uint64_t>(sb.st_size), checksum_str);
     if (!component.ok()) {
       LOG(ERROR) << "Failed to generate component: " << component.error();
       return {};
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index c0e5ade..07ea060 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -27,6 +27,7 @@
 #include "arch/instruction_set.h"
 #include "base/file_utils.h"
 #include "base/globals.h"
+#include "base/mem_map.h"
 #include "base/stl_util.h"
 #include "odr_common.h"
 #include "odr_compilation_log.h"
@@ -268,6 +269,7 @@
   if (!config.GetCompilationOsMode()) {
     android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
   }
+  art::MemMap::Init();  // Needed by DexFileLoader.
 
   argv += n;
   argc -= n;
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 2f34f04..1b14449 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -17,6 +17,7 @@
 #include "class_loader_context.h"
 
 #include <algorithm>
+#include <optional>
 
 #include "android-base/file.h"
 #include "android-base/parseint.h"
@@ -473,14 +474,12 @@
       }
 
       std::string error_msg;
+      ArtDexFileLoader dex_file_loader(fd, location);
+      std::optional<uint32_t> dex_checksum;
       if (only_read_checksums) {
         bool zip_file_only_contains_uncompress_dex;
-        if (!ArtDexFileLoader::GetMultiDexChecksums(location.c_str(),
-                                                    &dex_checksums,
-                                                    &dex_locations,
-                                                    &error_msg,
-                                                    fd,
-                                                    &zip_file_only_contains_uncompress_dex)) {
+        if (!dex_file_loader.GetMultiDexChecksum(
+                &dex_checksum, &error_msg, &zip_file_only_contains_uncompress_dex)) {
           LOG(WARNING) << "Could not get dex checksums for location " << location << ", fd=" << fd;
           dex_files_state_ = kDexFilesOpenFailed;
         }
@@ -489,8 +488,7 @@
         // contents. So pass true to verify_checksum.
         // We don't need to do structural dex file verification, we only need to
         // check the checksum, so pass false to verify.
-        size_t opened_dex_files_index = info->opened_dex_files.size();
-        ArtDexFileLoader dex_file_loader(location.c_str(), fd, location);
+        size_t i = info->opened_dex_files.size();
         if (!dex_file_loader.Open(/*verify=*/false,
                                   /*verify_checksum=*/true,
                                   &error_msg,
@@ -498,13 +496,14 @@
           LOG(WARNING) << "Could not open dex files for location " << location << ", fd=" << fd;
           dex_files_state_ = kDexFilesOpenFailed;
         } else {
-          for (size_t k = opened_dex_files_index; k < info->opened_dex_files.size(); k++) {
-            std::unique_ptr<const DexFile>& dex = info->opened_dex_files[k];
-            dex_locations.push_back(dex->GetLocation());
-            dex_checksums.push_back(dex->GetLocationChecksum());
-          }
+          dex_checksum = DexFileLoader::GetMultiDexChecksum(info->opened_dex_files, &i);
+          DCHECK_EQ(i, info->opened_dex_files.size());
         }
       }
+      if (dex_checksum.has_value()) {
+        dex_locations.push_back(location);
+        dex_checksums.push_back(dex_checksum.value());
+      }
     }
 
     // We finished opening the dex files from the classpath.
@@ -518,8 +517,8 @@
     // Note that this will also remove the paths that could not be opened.
     info->original_classpath = std::move(info->classpath);
     DCHECK(dex_locations.size() == dex_checksums.size());
-    info->classpath = dex_locations;
-    info->checksums = dex_checksums;
+    info->classpath = std::move(dex_locations);
+    info->checksums = std::move(dex_checksums);
     AddToWorkList(info, work_list);
   }
 
@@ -692,16 +691,16 @@
     }
   }
 
-  for (size_t k = 0; k < info.opened_dex_files.size(); k++) {
+  for (size_t k = 0; k < info.opened_dex_files.size();) {
     const std::unique_ptr<const DexFile>& dex_file = info.opened_dex_files[k];
+    uint32_t checksum = DexFileLoader::GetMultiDexChecksum(info.opened_dex_files, &k);
+
     if (for_dex2oat) {
       // dex2oat only needs the base location. It cannot accept multidex locations.
       // So ensure we only add each file once.
       bool new_insert =
           seen_locations.insert(DexFileLoader::GetBaseLocation(dex_file->GetLocation())).second;
-      if (!new_insert) {
-        continue;
-      }
+      CHECK(new_insert);
     }
 
     std::string location = dex_file->GetLocation();
@@ -716,7 +715,7 @@
 
     // dex2oat does not need the checksums.
     if (!for_dex2oat) {
-      checksums.push_back(dex_file->GetLocationChecksum());
+      checksums.push_back(checksum);
     }
   }
   EncodeClassPath(base_dir, locations, checksums, info.type, out);
@@ -1155,13 +1154,17 @@
   }
 
   // Now that `info` is in the chain, populate dex files.
-  for (const DexFile* dex_file : dex_files_loaded) {
+  for (size_t i = 0; i < dex_files_loaded.size();) {
+    const DexFile* dex_file = dex_files_loaded[i];
+    uint32_t checksum = DexFileLoader::GetMultiDexChecksum(dex_files_loaded, &i);
     // Dex location of dex files loaded with InMemoryDexClassLoader is always bogus.
     // Use a magic value for the classpath instead.
     info->classpath.push_back((type == kInMemoryDexClassLoader) ?
                                   kInMemoryDexClassLoaderDexLocationMagic :
                                   dex_file->GetLocation());
-    info->checksums.push_back(dex_file->GetLocationChecksum());
+    info->checksums.push_back(checksum);
+  }
+  for (auto* dex_file : dex_files_loaded) {
     info->opened_dex_files.emplace_back(dex_file);
   }
 
@@ -1477,8 +1480,10 @@
   // in the Android world) - and as such we decide not to warn on them.
   ClassLoaderInfo* info = class_loader_chain_.get();
   for (size_t k = 0; k < info->classpath.size(); k++) {
-    for (const DexFile* dex_file : dex_files_to_check) {
-      if (info->checksums[k] == dex_file->GetLocationChecksum() &&
+    for (size_t i = 0; i < dex_files_to_check.size();) {
+      const DexFile* dex_file = dex_files_to_check[i];
+      uint32_t checksum = DexFileLoader::GetMultiDexChecksum(dex_files_to_check, &i);
+      if (info->checksums[k] == checksum &&
           AreDexNameMatching(info->classpath[k], dex_file->GetLocation())) {
         result.insert(dex_file);
       }
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index ce9780a..98b8eed 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -20,6 +20,8 @@
 
 #include <filesystem>
 #include <fstream>
+#include <optional>
+#include <vector>
 
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -27,6 +29,7 @@
 #include "art_method-alloc-inl.h"
 #include "base/dchecked_vector.h"
 #include "base/stl_util.h"
+#include "base/string_view_cpp20.h"
 #include "class_linker.h"
 #include "class_root-inl.h"
 #include "common_runtime_test.h"
@@ -184,41 +187,62 @@
                 ClassLoaderContext::ContextDexFilesState::kDexFilesOpened);
     }
     ClassLoaderContext::ClassLoaderInfo& info = *context->GetParent(index);
-    ASSERT_EQ(all_dex_files->size(), info.classpath.size());
-    ASSERT_EQ(all_dex_files->size(), info.checksums.size());
+
+    std::vector<const DexFile*> primary_dex_files;
+    std::vector<std::optional<uint32_t>> primary_checksums;
+    for (size_t i = 0; i < all_dex_files->size();) {
+      primary_dex_files.push_back((*all_dex_files)[i].get());
+      primary_checksums.push_back(DexFileLoader::GetMultiDexChecksum(*all_dex_files, &i));
+    }
+    ASSERT_EQ(primary_dex_files.size(), info.classpath.size());
+    ASSERT_EQ(primary_dex_files.size(), info.checksums.size());
+
     if (only_read_checksums) {
       ASSERT_EQ(0u, info.opened_dex_files.size());
+      for (size_t k = 0; k < primary_dex_files.size(); k++) {
+        const std::string& opened_location = info.classpath[k];
+        uint32_t opened_checksum = info.checksums[k];
+
+        const DexFile* expected_dex_file = primary_dex_files[k];
+        std::string expected_location = expected_dex_file->GetLocation();
+
+        if (!IsAbsoluteLocation(opened_location)) {
+          // If the opened location is relative (it was open from a relative path without a
+          // classpath_dir) it might not match the expected location which is absolute in tests).
+          // So we compare the endings (the checksum will validate it's actually the same file).
+          ASSERT_TRUE(EndsWith(expected_location, opened_location))
+              << expected_location << " " << opened_location;
+        } else {
+          ASSERT_EQ(expected_location, opened_location);
+        }
+        ASSERT_EQ(primary_checksums[k], opened_checksum);
+        if (classpath_matches_dex_location) {
+          ASSERT_EQ(info.classpath[k], opened_location);
+        }
+      }
     } else {
       ASSERT_EQ(all_dex_files->size(), info.opened_dex_files.size());
-    }
 
-    for (size_t k = 0, cur_open_dex_index = 0;
-         k < all_dex_files->size();
-         k++, cur_open_dex_index++) {
-      const std::string& opened_location = only_read_checksums
-          ? info.classpath[cur_open_dex_index]
-          : info.opened_dex_files[cur_open_dex_index]->GetLocation();
-      uint32_t opened_checksum = only_read_checksums
-          ? info.checksums[cur_open_dex_index]
-          : info.opened_dex_files[cur_open_dex_index]->GetLocationChecksum();
+      for (size_t k = 0; k < all_dex_files->size(); k++) {
+        const std::string& opened_location = info.opened_dex_files[k]->GetLocation();
+        uint32_t opened_checksum = info.opened_dex_files[k]->GetLocationChecksum();
 
-      std::unique_ptr<const DexFile>& expected_dex_file = (*all_dex_files)[k];
-      std::string expected_location = expected_dex_file->GetLocation();
+        std::unique_ptr<const DexFile>& expected_dex_file = (*all_dex_files)[k];
+        std::string expected_location = expected_dex_file->GetLocation();
 
-      if (!IsAbsoluteLocation(opened_location)) {
-        // If the opened location is relative (it was open from a relative path without a
-        // classpath_dir) it might not match the expected location which is absolute in tests).
-        // So we compare the endings (the checksum will validate it's actually the same file).
-        ASSERT_EQ(0, expected_location.compare(
-            expected_location.length() - opened_location.length(),
-            opened_location.length(),
-            opened_location));
-      } else {
-        ASSERT_EQ(expected_location, opened_location);
-      }
-      ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_checksum);
-      if (classpath_matches_dex_location) {
-        ASSERT_EQ(info.classpath[k], opened_location);
+        if (!IsAbsoluteLocation(opened_location)) {
+          // If the opened location is relative (it was open from a relative path without a
+          // classpath_dir) it might not match the expected location which is absolute in tests).
+          // So we compare the endings (the checksum will validate it's actually the same file).
+          ASSERT_TRUE(EndsWith(expected_location, opened_location))
+              << expected_location << " " << opened_location;
+        } else {
+          ASSERT_EQ(expected_location, opened_location);
+        }
+        ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_checksum);
+        if (classpath_matches_dex_location) {
+          ASSERT_EQ(info.classpath[k], opened_location);
+        }
       }
     }
   }
@@ -1202,9 +1226,11 @@
   std::vector<std::unique_ptr<const DexFile>> dex2 = OpenTestDexFiles("MyClass");
   ASSERT_EQ(dex2.size(), 1u);
 
+  uint32_t expected_checksum = DexFileLoader::GetMultiDexChecksum(dex2);
+
   std::string encoding = context->EncodeContextForOatFile("");
-  std::string expected_encoding = "IMC[<unknown>*" + std::to_string(dex2[0]->GetLocationChecksum())
-      + "];PCL[" + CreateClassPathWithChecksums(dex1) + "]";
+  std::string expected_encoding = "IMC[<unknown>*" + std::to_string(expected_checksum) + "];PCL[" +
+                                  CreateClassPathWithChecksums(dex1) + "]";
   ASSERT_EQ(expected_encoding, context->EncodeContextForOatFile(""));
 }
 
diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h
index c7febcd..43e1bf8 100644
--- a/runtime/dex2oat_environment_test.h
+++ b/runtime/dex2oat_environment_test.h
@@ -18,6 +18,7 @@
 #define ART_RUNTIME_DEX2OAT_ENVIRONMENT_TEST_H_
 
 #include <fstream>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -98,8 +99,7 @@
     Dex2oatScratchDirs::SetUp(android_data_);
 
     // Verify the environment is as we expect
-    std::vector<uint32_t> checksums;
-    std::vector<std::string> dex_locations;
+    std::optional<uint32_t> checksum;
     std::string error_msg;
     ASSERT_TRUE(OS::FileExists(GetSystemImageFile().c_str()))
       << "Expected pre-compiled boot image to be at: " << GetSystemImageFile();
@@ -107,8 +107,8 @@
       << "Expected dex file to be at: " << GetDexSrc1();
     ASSERT_TRUE(OS::FileExists(GetResourceOnlySrc1().c_str()))
       << "Expected stripped dex file to be at: " << GetResourceOnlySrc1();
-    ASSERT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
-        GetResourceOnlySrc1().c_str(), &checksums, &dex_locations, &error_msg))
+    ArtDexFileLoader dex_file_loader0(GetResourceOnlySrc1());
+    ASSERT_TRUE(dex_file_loader0.GetMultiDexChecksum(&checksum, &error_msg))
         << "Expected stripped dex file to be stripped: " << GetResourceOnlySrc1();
     ASSERT_TRUE(OS::FileExists(GetDexSrc2().c_str()))
       << "Expected dex file to be at: " << GetDexSrc2();
@@ -128,6 +128,9 @@
         << error_msg;
     ASSERT_GT(multi2.size(), 1u);
 
+    ASSERT_EQ(multi1[0]->GetHeader().checksum_, multi2[0]->GetHeader().checksum_);
+    ASSERT_NE(multi1[1]->GetHeader().checksum_, multi2[1]->GetHeader().checksum_);
+
     ASSERT_EQ(multi1[0]->GetLocationChecksum(), multi2[0]->GetLocationChecksum());
     ASSERT_NE(multi1[1]->GetLocationChecksum(), multi2[1]->GetLocationChecksum());
   }
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index f4af50f..ebd6681 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -21,6 +21,7 @@
 #include <unistd.h>
 
 #include <memory>
+#include <optional>
 #include <random>
 #include <string>
 #include <vector>
@@ -3448,67 +3449,45 @@
     return false;
   }
 
-  size_t dex_file_index = 0;
-  for (const OatDexFile* oat_dex_file : oat_file.GetOatDexFiles()) {
-    // Skip multidex locations - These will be checked when we visit their
-    // corresponding primary non-multidex location.
-    if (DexFileLoader::IsMultiDexLocation(oat_dex_file->GetDexFileLocation().c_str())) {
-      continue;
-    }
-
+  size_t dex_file_index = 0;  // Counts only primary dex files.
+  const std::vector<const OatDexFile*>& oat_dex_files = oat_file.GetOatDexFiles();
+  for (size_t i = 0; i < oat_dex_files.size();) {
     DCHECK(dex_filenames.empty() || dex_file_index < dex_filenames.size());
-    const std::string& dex_file_location =
-        dex_filenames.empty() ? oat_dex_file->GetDexFileLocation() : dex_filenames[dex_file_index];
+    const std::string& dex_file_location = dex_filenames.empty() ?
+                                               oat_dex_files[i]->GetDexFileLocation() :
+                                               dex_filenames[dex_file_index];
     int dex_fd = dex_file_index < dex_fds.size() ? dex_fds[dex_file_index] : -1;
     dex_file_index++;
 
-    std::vector<uint32_t> checksums;
-    std::vector<std::string> dex_locations_ignored;
-    if (!ArtDexFileLoader::GetMultiDexChecksums(
-            dex_file_location.c_str(), &checksums, &dex_locations_ignored, error_msg, dex_fd)) {
-      *error_msg = StringPrintf("ValidateOatFile failed to get checksums of dex file '%s' "
-                                "referenced by oat file %s: %s",
-                                dex_file_location.c_str(),
-                                oat_file.GetLocation().c_str(),
-                                error_msg->c_str());
+    if (DexFileLoader::IsMultiDexLocation(oat_dex_files[i]->GetDexFileLocation().c_str())) {
+      return false;  // Expected primary dex file.
+    }
+    uint32_t oat_checksum = DexFileLoader::GetMultiDexChecksum(oat_dex_files, &i);
+
+    // Original checksum.
+    std::optional<uint32_t> dex_checksum;
+    ArtDexFileLoader dex_loader(dex_fd, dex_file_location);
+    if (!dex_loader.GetMultiDexChecksum(&dex_checksum, error_msg)) {
+      *error_msg = StringPrintf(
+          "ValidateOatFile failed to get checksum of dex file '%s' "
+          "referenced by oat file %s: %s",
+          dex_file_location.c_str(),
+          oat_file.GetLocation().c_str(),
+          error_msg->c_str());
       return false;
     }
-    CHECK(!checksums.empty());
-    if (checksums[0] != oat_dex_file->GetDexFileLocationChecksum()) {
-      *error_msg = StringPrintf("ValidateOatFile found checksum mismatch between oat file "
-                                "'%s' and dex file '%s' (0x%x != 0x%x)",
-                                oat_file.GetLocation().c_str(),
-                                dex_file_location.c_str(),
-                                oat_dex_file->GetDexFileLocationChecksum(),
-                                checksums[0]);
+    CHECK(dex_checksum.has_value());
+
+    if (oat_checksum != dex_checksum) {
+      *error_msg = StringPrintf(
+          "ValidateOatFile found checksum mismatch between oat file "
+          "'%s' and dex file '%s' (0x%x != 0x%x)",
+          oat_file.GetLocation().c_str(),
+          dex_file_location.c_str(),
+          oat_checksum,
+          dex_checksum.value());
       return false;
     }
-
-    // Verify checksums for any related multidex entries.
-    for (size_t i = 1; i < checksums.size(); i++) {
-      std::string multi_dex_location = DexFileLoader::GetMultiDexLocation(
-          i,
-          dex_file_location.c_str());
-      const OatDexFile* multi_dex = oat_file.GetOatDexFile(multi_dex_location.c_str(),
-                                                           nullptr,
-                                                           error_msg);
-      if (multi_dex == nullptr) {
-        *error_msg = StringPrintf("ValidateOatFile oat file '%s' is missing entry '%s'",
-                                  oat_file.GetLocation().c_str(),
-                                  multi_dex_location.c_str());
-        return false;
-      }
-
-      if (checksums[i] != multi_dex->GetDexFileLocationChecksum()) {
-        *error_msg = StringPrintf("ValidateOatFile found checksum mismatch between oat file "
-                                  "'%s' and dex file '%s' (0x%x != 0x%x)",
-                                  oat_file.GetLocation().c_str(),
-                                  multi_dex_location.c_str(),
-                                  multi_dex->GetDexFileLocationChecksum(),
-                                  checksums[i]);
-        return false;
-      }
-    }
   }
   return true;
 }
@@ -3556,14 +3535,13 @@
       ArrayRef<const DexFile* const>(boot_class_path).SubArray(bcp_pos);
   DCHECK(boot_class_path_tail.empty() ||
          !DexFileLoader::IsMultiDexLocation(boot_class_path_tail.front()->GetLocation().c_str()));
-  for (const DexFile* dex_file : boot_class_path_tail) {
-    if (!DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str())) {
-      if (!boot_image_checksum.empty()) {
-        boot_image_checksum += ':';
-      }
-      boot_image_checksum += kDexFileChecksumPrefix;
+  for (size_t i = 0; i < boot_class_path_tail.size();) {
+    uint32_t checksum = DexFileLoader::GetMultiDexChecksum(boot_class_path_tail, &i);
+    if (!boot_image_checksum.empty()) {
+      boot_image_checksum += ':';
     }
-    StringAppendF(&boot_image_checksum, "/%08x", dex_file->GetLocationChecksum());
+    boot_image_checksum += kDexFileChecksumPrefix;
+    StringAppendF(&boot_image_checksum, "/%08x", checksum);
   }
   return boot_image_checksum;
 }
diff --git a/runtime/oat.h b/runtime/oat.h
index e062baa..d65f19a 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -44,8 +44,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
-  // Last oat version changed reason: ARM64: Enable implicit suspend checks; compiled code check.
-  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '3', '0', '\0' } };
+  // Last oat version changed reason: Reduce multidex checksum to single scalar value.
+  static constexpr std::array<uint8_t, 4> kOatVersion{{'2', '3', '1', '\0'}};
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 68adb98..4fdbb75 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -514,6 +514,9 @@
     return dex_file_location_;
   }
 
+  // Returns original path of DexFile that was the source of this OatDexFile.
+  const std::string& GetLocation() const { return dex_file_location_; }
+
   // Returns the canonical location of DexFile that was the source of this OatDexFile.
   const std::string& GetCanonicalDexFileLocation() const {
     return canonical_dex_file_location_;
@@ -524,6 +527,9 @@
     return dex_file_location_checksum_;
   }
 
+  // Returns checksum of original DexFile that was the source of this OatDexFile;
+  uint32_t GetLocationChecksum() const { return dex_file_location_checksum_; }
+
   // Returns the OatClass for the class specified by the given DexFile class_def_index.
   OatFile::OatClass GetOatClass(uint16_t class_def_index) const;
 
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 9e16e49..cbbb31c 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -453,11 +453,11 @@
 
 std::optional<bool> OatFileAssistant::HasDexFiles(std::string* error_msg) {
   ScopedTrace trace("HasDexFiles");
-  const std::vector<std::uint32_t>* checksums = GetRequiredDexChecksums(error_msg);
-  if (checksums == nullptr) {
+  std::optional<std::uint32_t> checksum;
+  if (!GetRequiredDexChecksum(&checksum, error_msg)) {
     return std::nullopt;
   }
-  return !checksums->empty();
+  return checksum.has_value();
 }
 
 OatFileAssistant::OatStatus OatFileAssistant::OdexFileStatus() { return odex_.Status(); }
@@ -471,37 +471,35 @@
     return true;
   }
   ScopedTrace trace("DexChecksumUpToDate");
-  const std::vector<uint32_t>* required_dex_checksums = GetRequiredDexChecksums(error_msg);
-  if (required_dex_checksums == nullptr) {
+  std::optional<std::uint32_t> dex_checksum;
+  if (!GetRequiredDexChecksum(&dex_checksum, error_msg)) {
     return false;
   }
-  if (required_dex_checksums->empty()) {
+  if (!dex_checksum.has_value()) {
     LOG(WARNING) << "Required dex checksums not found. Assuming dex checksums are up to date.";
     return true;
   }
 
+  std::vector<const OatDexFile*> oat_dex_files;
   uint32_t number_of_dex_files = file.GetOatHeader().GetDexFileCount();
-  if (required_dex_checksums->size() != number_of_dex_files) {
-    *error_msg = StringPrintf(
-        "expected %zu dex files but found %u", required_dex_checksums->size(), number_of_dex_files);
-    return false;
-  }
-
   for (uint32_t i = 0; i < number_of_dex_files; i++) {
     std::string dex = DexFileLoader::GetMultiDexLocation(i, dex_location_.c_str());
-    uint32_t expected_checksum = (*required_dex_checksums)[i];
     const OatDexFile* oat_dex_file = file.GetOatDexFile(dex.c_str(), nullptr);
     if (oat_dex_file == nullptr) {
       *error_msg = StringPrintf("failed to find %s in %s", dex.c_str(), file.GetLocation().c_str());
       return false;
     }
-    uint32_t actual_checksum = oat_dex_file->GetDexFileLocationChecksum();
-    if (expected_checksum != actual_checksum) {
-      VLOG(oat) << "Dex checksum does not match for dex: " << dex
-                << ". Expected: " << expected_checksum << ", Actual: " << actual_checksum;
-      return false;
-    }
+    oat_dex_files.push_back(oat_dex_file);
   }
+  uint32_t oat_checksum = DexFileLoader::GetMultiDexChecksum(oat_dex_files);
+
+  CHECK(dex_checksum.has_value());
+  if (dex_checksum != oat_checksum) {
+    VLOG(oat) << "Checksum does not match: " << std::hex << file.GetLocation() << " ("
+              << oat_checksum << ") vs " << dex_location_ << " (" << *dex_checksum << ")";
+    return false;
+  }
+
   return true;
 }
 
@@ -718,33 +716,36 @@
   return GetDalvikCacheFilename(location.c_str(), dalvik_cache.c_str(), oat_filename, error_msg);
 }
 
-const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums(std::string* error_msg) {
+bool OatFileAssistant::GetRequiredDexChecksum(std::optional<uint32_t>* checksum,
+                                              std::string* error) {
   if (!required_dex_checksums_attempted_) {
     required_dex_checksums_attempted_ = true;
-    std::vector<uint32_t> checksums;
-    std::vector<std::string> dex_locations_ignored;
-    if (ArtDexFileLoader::GetMultiDexChecksums(dex_location_.c_str(),
-                                               &checksums,
-                                               &dex_locations_ignored,
-                                               &cached_required_dex_checksums_error_,
-                                               zip_fd_,
-                                               &zip_file_only_contains_uncompressed_dex_)) {
-      if (checksums.empty()) {
-        // The only valid case here is for APKs without dex files.
-        VLOG(oat) << "No dex file found in " << dex_location_;
-      }
 
-      cached_required_dex_checksums_ = std::move(checksums);
+    ArtDexFileLoader dex_loader(zip_fd_, dex_location_);
+    std::optional<uint32_t> checksum2;
+    std::string error2;
+    if (dex_loader.GetMultiDexChecksum(
+            &checksum2, &error2, &zip_file_only_contains_uncompressed_dex_)) {
+      cached_required_dex_checksums_ = checksum2;
+      cached_required_dex_checksums_error_ = std::nullopt;
+    } else {
+      cached_required_dex_checksums_ = std::nullopt;
+      cached_required_dex_checksums_error_ = error2;
     }
   }
 
-  if (cached_required_dex_checksums_.has_value()) {
-    return &cached_required_dex_checksums_.value();
-  } else {
-    *error_msg = cached_required_dex_checksums_error_;
-    DCHECK(!error_msg->empty());
-    return nullptr;
+  if (cached_required_dex_checksums_error_.has_value()) {
+    *error = cached_required_dex_checksums_error_.value();
+    DCHECK(!error->empty());
+    return false;
   }
+
+  if (!cached_required_dex_checksums_.has_value()) {
+    // The only valid case here is for APKs without dex files.
+    VLOG(oat) << "No dex file found in " << dex_location_;
+  }
+  *checksum = cached_required_dex_checksums_;
+  return true;
 }
 
 bool OatFileAssistant::ValidateBootClassPathChecksums(OatFileAssistantContext* ofa_context,
@@ -1380,8 +1381,9 @@
 
 bool OatFileAssistant::ZipFileOnlyContainsUncompressedDex() {
   // zip_file_only_contains_uncompressed_dex_ is only set during fetching the dex checksums.
+  std::optional<uint32_t> checksum;
   std::string error_msg;
-  if (GetRequiredDexChecksums(&error_msg) == nullptr) {
+  if (!GetRequiredDexChecksum(&checksum, &error_msg)) {
     LOG(ERROR) << error_msg;
   }
   return zip_file_only_contains_uncompressed_dex_;
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 54287eb..922dbf3 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -494,11 +494,9 @@
   // location.
   OatStatus GivenOatFileStatus(const OatFile& file);
 
-  // Gets the dex checksums required for an up-to-date oat file.
-  // Returns cached_required_dex_checksums if the required checksums were located. Returns an empty
-  // list if `dex_location_` refers to a zip and there is no dex file in it. Returns nullptr if an
-  // error occurred. The caller shouldn't clean up or free the returned pointer.
-  const std::vector<uint32_t>* GetRequiredDexChecksums(std::string* error_msg);
+  // Gets the dex checksum required for an up-to-date oat file.
+  // Returns cached result from GetMultiDexChecksum.
+  bool GetRequiredDexChecksum(std::optional<uint32_t>* checksum, std::string* error);
 
   // Returns whether there is at least one boot image usable.
   bool IsPrimaryBootImageUsable();
@@ -555,8 +553,8 @@
 
   // Cached value of the required dex checksums.
   // This should be accessed only by the GetRequiredDexChecksums() method.
-  std::optional<std::vector<uint32_t>> cached_required_dex_checksums_;
-  std::string cached_required_dex_checksums_error_;
+  std::optional<uint32_t> cached_required_dex_checksums_;
+  std::optional<std::string> cached_required_dex_checksums_error_;
   bool required_dex_checksums_attempted_ = false;
 
   // The AOT-compiled file of an app when the APK of the app is in /data.
diff --git a/runtime/oat_file_assistant_context.cc b/runtime/oat_file_assistant_context.cc
index c3fb73d..0f9860f 100644
--- a/runtime/oat_file_assistant_context.cc
+++ b/runtime/oat_file_assistant_context.cc
@@ -17,6 +17,7 @@
 #include "oat_file_assistant_context.h"
 
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -76,13 +77,13 @@
   // Fetch BCP checksums from the runtime.
   size_t bcp_index = 0;
   std::vector<std::string>* current_bcp_checksums = nullptr;
-  for (const DexFile* dex_file : runtime->GetClassLinker()->GetBootClassPath()) {
-    if (!DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str())) {
-      DCHECK_LT(bcp_index, runtime_options_->boot_class_path.size());
-      current_bcp_checksums = &bcp_checksums_by_index_[bcp_index++];
-    }
+  const std::vector<const DexFile*>& bcp_dex_files = runtime->GetClassLinker()->GetBootClassPath();
+  for (size_t i = 0; i < bcp_dex_files.size();) {
+    uint32_t checksum = DexFileLoader::GetMultiDexChecksum(bcp_dex_files, &i);
+    DCHECK_LT(bcp_index, runtime_options_->boot_class_path.size());
+    current_bcp_checksums = &bcp_checksums_by_index_[bcp_index++];
     DCHECK_NE(current_bcp_checksums, nullptr);
-    current_bcp_checksums->push_back(StringPrintf("/%08x", dex_file->GetLocationChecksum()));
+    current_bcp_checksums->push_back(StringPrintf("/%08x", checksum));
   }
   DCHECK_EQ(bcp_index, runtime_options_->boot_class_path.size());
 
@@ -156,23 +157,18 @@
     return &it->second;
   }
 
-  std::vector<uint32_t> checksums;
-  std::vector<std::string> dex_locations;
-  if (!ArtDexFileLoader::GetMultiDexChecksums(
-          runtime_options_->boot_class_path[bcp_index].c_str(),
-          &checksums,
-          &dex_locations,
-          error_msg,
-          runtime_options_->boot_class_path_fds != nullptr ?
-              (*runtime_options_->boot_class_path_fds)[bcp_index] :
-              -1)) {
+  const std::vector<int>* fds = runtime_options_->boot_class_path_fds;
+  ArtDexFileLoader dex_loader(fds != nullptr ? (*fds)[bcp_index] : -1,
+                              runtime_options_->boot_class_path[bcp_index]);
+  std::optional<uint32_t> checksum;
+  if (!dex_loader.GetMultiDexChecksum(&checksum, error_msg)) {
     return nullptr;
   }
 
-  DCHECK(!checksums.empty());
+  DCHECK(checksum.has_value());
   std::vector<std::string>& bcp_checksums = bcp_checksums_by_index_[bcp_index];
-  for (uint32_t checksum : checksums) {
-    bcp_checksums.push_back(StringPrintf("/%08x", checksum));
+  if (checksum.has_value()) {
+    bcp_checksums.push_back(StringPrintf("/%08x", checksum.value()));
   }
   return &bcp_checksums;
 }
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 36c8cde..f3bd4dd 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -16,6 +16,7 @@
 
 #include "runtime.h"
 
+#include <optional>
 #include <utility>
 
 #ifdef __linux__
@@ -2765,14 +2766,14 @@
   bool has_code = false;
   for (const std::string& path : code_paths) {
     std::string error_msg;
-    std::vector<uint32_t> checksums;
+    std::optional<uint32_t> checksum;
     std::vector<std::string> dex_locations;
-    if (!ArtDexFileLoader::GetMultiDexChecksums(
-            path.c_str(), &checksums, &dex_locations, &error_msg)) {
+    DexFileLoader loader(path);
+    if (!loader.GetMultiDexChecksum(&checksum, &error_msg)) {
       LOG(WARNING) << error_msg;
       continue;
     }
-    if (dex_locations.size() > 0) {
+    if (checksum.has_value()) {
       has_code = true;
       break;
     }