当前位置: 首页 > news >正文

App冷启动阶段Open Dexfiles实现原理【ART虚拟机系列2】

目录

一、完整的代码时序

二、Open Dexfiles源码分析

2.1 主要功能

2.2 源码分析

三、可优化方向


平台:Android14

备注:以下代码AOSP开源代码

一、完整的代码时序

app启动起点到Open Dexfiles(主要加载dex文件到内存)的代码时序:

app冷启动阶段,主线程执行bindApplication方法中基于applicationInfo去加载dex file到内存(app启动依赖这些文件数据)。

二、Open Dexfiles源码分析

2.1 主要功能

1)从apk文件中加载dex file存入out_dex_files数组。

2)madvise系统调用提示内核异步地预读内存区域的dex与oat文件数据到页面缓存(page cache)。

2.2 源码分析

  • attach

// frameworks/base/core/java/android/app/ActivityThread.java
private void attach(boolean system, long startSeq) {...// 通过Binder接口调用AMS的attachApplication方法IActivityManager mgr = ActivityManager.getService();mgr.attachApplication(mAppThread, startSeq);...
}
  • attachApplication

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public final void attachApplication(IApplicationThread thread, long startSeq) {...attachApplicationLocked(thread, callingPid, callingUid, startSeq);...
}private void attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) {...// Invoke the bindApplication interface in ActivityThreadthread.bindApplication(...);...
}

  • bindApplication

// frameworks/base/core/java/android/app/ActivityThread.javapublic final void bindApplication(...) {...// 发送BIND_APPLICATION消息sendMessage(H.BIND_APPLICATION, data);...
}public void handleMessage(Message msg) {switch (msg.what) {// 处理BIND_APPLICATION消息case BIND_APPLICATION:AppBindData data = (AppBindData)msg.obj;handleBindApplication(data);break;...}
}private void handleBindApplication(AppBindData data) {...data.info.makeApplicationInner(data.restrictedBackupMode, null);...
}// frameworks/base/core/java/android/app/LoadedApk.java
private Application makeApplicationInner(boolean forceDefaultAppClass, Instrumentation instrumentation,boolean allowDuplicateInstances) {...getClassLoader();...
}

  • getClassLoader

获取ClassLoader类加载器,循环遍历dex files,并进行逐个加载:

// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, ClassLoader[] sharedLibraryLoadersAfter, boolean isTrusted) {...this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);...
}// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
DexPathList(...) {makeDexElements(...);
}private static Element[] makeDexElements(...) {...loadDexFile(file, optimizedDirectory, loader, elements);...
}private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException {...for (File file : files) {DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);}
}// libcore/dalvik/src/main/java/dalvik/system/DexFile.java
static DexFile loadDex(String sourcePathName, String outputPathName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
}DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {mCookie = openDexFile(fileName, null, 0, loader, elements);
}
  • openDexFile

java层通过JNI调用,进入native层接口,如下:

// libcore/dalvik/src/main/java/dalvik/system/DexFile.java
/*** @param sourceName DEX/APK文件路径 (Java String)* @param outputName 历史遗留参数(通常为null)* @param flags 控制DEX文件加载和验证行为的标志位(应用启动时通常传0)* @param loader 加载Dex的类加载器* @param elements 类加载器的Dex路径列表*/
private static Object openDexFile(String sourceName, String outputName, int flags,ClassLoader loader, DexPathList.Element[] elements) throws IOException {// 使用绝对路径以支持在主机测试时使用相对路径return openDexFileNative(new File(sourceName).getAbsolutePath(),(outputName == null) ? null : new File(outputName).getAbsolutePath(),flags,loader,elements);
}
// art/runtime/native/dalvik_system_DexFile.cc
static jobject DexFile_openDexFileNative(JNIEnv* env,jclass,jstring javaSourceName,jstring javaOutputName ATTRIBUTE_UNUSED,jint flags ATTRIBUTE_UNUSED,jobject class_loader,jobjectArray dex_elements) {...const OatFile* oat_file = nullptr;std::vector<std::unique_ptr<const DexFile>> dex_files =Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),class_loader,dex_elements,/*out*/ &oat_file,/*out*/ &error_msgs);return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}
  • OpenDexFilesFromOat

核心函数OpenDexFilesFromOat:

// 核心功能:从OAT文件加载DEX
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(const char* dex_location,    // DEX文件路径(如APK路径)jobject class_loader,        // Java类加载器对象jobjectArray dex_elements,   // 类加载器的DEX元素数组const OatFile** out_oat_file, // 输出参数:加载的OAT文件指针std::vector<std::string>* error_msgs) {Runtime* const runtime = Runtime::Current();std::vector<std::unique_ptr<const DexFile>> dex_files;if (class_loader == nullptr) {LOG(WARNING) << "尝试在没有类加载器的情况下打开oat文件,"<< "是否使用了已弃用的DexFile API?";} else if (context != nullptr) {// 创建OAT文件助手(优化决策核心)auto oat_file_assistant = std::make_unique<OatFileAssistant>(dex_location,kRuntimeISA,             // 当前运行架构(如arm64)context.get(),          // 类加载器上下文runtime->GetOatFilesExecutable(), // 是否加载可执行代码only_use_system_oat_files_);// 获取优化状态信息std::string odex_location;     // OAT文件路径std::string compilation_filter; // 编译模式std::string compilation_reason; // 编译原因std::string odex_status;       // 状态OatFileAssistant::Location ignored_location;oat_file_assistant->GetOptimizationStatus(&odex_location, &compilation_filter,&compilation_reason,&odex_status,&ignored_location);// 判断是否启用madvise预读:// 1) 检查运行时是否已完成应用信息注册const bool has_registered_app_info = Runtime::Current()->GetAppInfo()->HasRegisteredAppInfo();// 2) 获取DEX文件对应的代码类型const AppInfo::CodeType code_type = Runtime::Current()->GetAppInfo()->GetRegisteredCodeType(dex_location);bool should_madvise = !has_registered_app_info || code_type == AppInfo::CodeType::kPrimaryApk || code_type == AppInfo::CodeType::kSplitApk;// 获取最佳OAT文件(核心决策点)std::unique_ptr<const OatFile> oat_file(oat_file_assistant->GetBestOatFile().release());if (oat_file != nullptr) {bool compilation_enabled = CompilerFilter::IsAotCompilationEnabled(oat_file->GetCompilerFilter());bool added_image_space = false;// 内存优化:madvise预读OAT文件if (should_madvise) {VLOG(oat) << "对oat文件执行madvise: " << oat_file->GetLocation();size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex();Runtime::MadviseFileForRange(madvise_size_limit,oat_file->Size(),oat_file->Begin(),oat_file->End(),oat_file->GetLocation());}std::unique_ptr<gc::space::ImageSpace> image_space;// 尝试加载AppImage(预初始化的类对象)if (ShouldLoadAppImage()) {if (oat_file->IsExecutable()) {image_space = oat_file_assistant->OpenImageSpace(oat_file.get());}if (kEnableRuntimeAppImage && image_space == nullptr && !compilation_enabled) {std::string art_file = RuntimeImage::GetRuntimeImagePath(dex_location);std::string error_msg;ScopedObjectAccess soa(self);image_space = gc::space::ImageSpace::CreateFromAppImage(art_file.c_str(),oat_file.get(),&error_msg);if (image_space == nullptr) {(OS::FileExists(art_file.c_str()) ? LOG_STREAM(INFO) : VLOG_STREAM(image))<< "无法加载运行时生成的app image: " << error_msg;}}}if (image_space != nullptr) {ScopedObjectAccess soa(self);StackHandleScope<1> hs(self);Handle<mirror::ClassLoader> h_loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));if (h_loader != nullptr) {oat_file->SetAppImageBegin(image_space->Begin()); // 关联OAT和Imagestd::string temp_error_msg;// 临界区操作:暂停所有线程添加ImageSpace{ScopedThreadSuspension sts(self, ThreadState::kSuspended);gc::ScopedGCCriticalSection gcs(self,gc::kGcCauseAddRemoveAppImageSpace,gc::kCollectorTypeAddRemoveAppImageSpace);ScopedSuspendAll ssa("添加image space");runtime->GetHeap()->AddSpace(image_space.get());}// 将ImageSpace注册到类链接器{ScopedTrace image_space_timing("添加image space");gc::space::ImageSpace* space_ptr = image_space.get();added_image_space = runtime->GetClassLinker()->AddImageSpaces(ArrayRef<gc::space::ImageSpace*>(&space_ptr, /*size=*/1),h_loader,context.get(),/*out*/ &dex_files,/*out*/ &temp_error_msg);}if (added_image_space) {image_space.release(); // 释放所有权(由运行时管理)// 注册DEX文件跟踪(用于性能分析)for (const auto& dex_file : dex_files) {dex::tracking::RegisterDexFile(dex_file.get());}} else {oat_file->SetAppImageBegin(nullptr);dex_files.clear();// 回滚:移除ImageSpace{ScopedThreadSuspension sts(self, ThreadState::kSuspended);gc::ScopedGCCriticalSection gcs(self,gc::kGcCauseAddRemoveAppImageSpace,gc::kCollectorTypeAddRemoveAppImageSpace);ScopedSuspendAll ssa("移除image space");runtime->GetHeap()->RemoveSpace(image_space.get());}}}}// AppImage加载失败的备选路径if (!added_image_space) {DCHECK(dex_files.empty());if (oat_file->RequiresImage()) {// 回退:尝试加载非可执行OATauto nonexecutable_oat_file_assistant = std::make_unique<OatFileAssistant>(dex_location,kRuntimeISA,context.get(),/*load_executable=*/false,only_use_system_oat_files_);oat_file.reset(nonexecutable_oat_file_assistant->GetBestOatFile().release());

从OAT文件中加载所有相关的DEX文件(包括主DEX和副DEX文件),并将结果存储到out_dex_files数组中,如下:

std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles(const OatFile& oat_file, const char* dex_location) {std::vector<std::unique_ptr<const DexFile>> dex_files;if (LoadDexFiles(oat_file, dex_location, &dex_files)) {return dex_files;}return {};
}bool OatFileAssistant::LoadDexFiles(const OatFile& oat_file,const std::string& dex_location,std::vector<std::unique_ptr<const DexFile>>* out_dex_files) {// 1. 加载主dex文件(class.dex)std::string error_msg;const OatDexFile* oat_dex_file = oat_file.GetOatDexFile(dex_location.c_str(), nullptr, &error_msg);out_dex_files->push_back(oat_dex_file->OpenDexFile(&error_msg));// 2. 加载其他副dex文件(class1.dex, class2.dex等)for (size_t i = 1; ; ++i) {std::string multidex_dex_location = DexFileLoader::GetMultiDexLocation(i, dex_location.c_str());oat_dex_file = oat_file.GetOatDexFile(multidex_dex_location.c_str(), nullptr);out_dex_files->push_back(oat_dex_file->OpenDexFile(&error_msg));}return true;
}

执行madvise系统调用进行内存预加载:

void Runtime::MadviseFileForRange(size_t madvise_size_limit_bytes, size_t map_size_bytes,const uint8_t* map_begin,const uint8_t* map_end,const std::string& file_name) {...for (const uint8_t* madvise_start = map_begin; madvise_start < target_pos; madvise_start += kIdealIoTransferSizeBytes) {void* madvise_addr = const_cast<void*>(static_cast<const void*>(madvise_start));size_t madvise_length = std::min(kIdealIoTransferSizeBytes, static_cast<size_t>(target_pos - madvise_start));int status = madvise(madvise_addr, madvise_length, MADV_WILLNEED);}...
}

madvise提示内核,示内核异步地预读内存区域的dex与oat文件数据到页面缓存(page cache),从而减少后续实际访问时的缺页(page fault)延迟。

  • Native层Open Dexfiles代码时序

三、可优化方向

1.目前Open Dexfiles操作在主线程的bindApplication方法中去执行,可提前挪到子线程执行bindApplication之前去完成,提升app冷启动速度。

2.dex文件加载到内存操可以提前去做,等到Open Dexfiles阶段可减少耗时。

http://www.dtcms.com/a/332039.html

相关文章:

  • docker nginx 定时脚本保存30天日志信息
  • MFC的使用——使用ChartCtrl绘制曲线
  • 2025.8.13~14 实习总结
  • 计算机网络技术学习-day1《网络乾坤:从比特洪流到协议星河的奇幻之旅》​
  • MCU中的LTDC(LCD-TFT Display Controller)
  • 网卡聚合teamdctl
  • 大模型技术栈全景
  • Java 图片像素碰撞检测
  • Linux软件编程-进程(1)
  • 【嵌入式C语言】四
  • 【PCB设计经验】3D模型在线预览!效率便捷!
  • pycharm远程连接服务器跑实验详细操作
  • ClickHouse 日常运维命令总结
  • 并发编程原理与实战(二十三)StampedLock应用实战与其他锁性能对比分析
  • CentOS7系统负载异常飙高全链路分析详细指南
  • Kaggle赛题分析1:Elo用户忠诚度评分预测(2)-特征工程与模型训练
  • 解决Python环境混乱问题
  • 【159页PPT】智慧方案企业数字化转型流程体系建设与运营方案(附下载方式)
  • 鸿蒙应用开发实战:模块内页面路由与Navigation导航详解
  • 深入理解提示词工程:从入门到精通的AI对话艺术
  • 零基础-动手学深度学习-10.3. 注意力评分函数
  • [vibe coding-lovable]lovable是不是ai界的复制忍者卡卡西?
  • 《零基础入门AI:深度学习中的视觉处理(卷积神经网络(CNN)进阶)》
  • 光猫 SK-D840N 获取管理员密码和登录Telnet简记
  • 如何永久修改开发板的时间(重启开发板也不会失效)
  • 深度学习-卷积神经网络CNN-膨胀卷积、可分离卷积(空间可分离、深度可分离)、分组卷积
  • 从红警中来学习工厂方法 vs. 抽象工厂
  • C# 异步编程:提高应用程序效率的关键
  • A股大盘数据-20250814 分析
  • mysql如何降级