跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Android 动态替换 Application 实现

Android 动态替换 Application 技术通过反射 Hook ActivityThread 和 LoadedApk 实现。核心流程是在 ProxyApplication.attachBaseContext 中调用系统 makeApplication 方法创建 RealApplication,并替换 LoadedApk 中的 ApplicationInfo 类名。此方案需处理 mApplication 缓存清空及 mAllApplications 移除旧实例,确保生命周期回调正常。同时需注意 ContentProvider 初始化顺序,其 getContext 返回的 Context 类型也需保持一致。该方案接入成本低,但需随系统版本适配兼容性。

雾岛听风发布于 2025/2/7更新于 2026/7/2354 浏览
Android 动态替换 Application 实现

背景

虽然热更新和 Hook 技术都被大家聊烂了,但是还是想和大家聊一下这方面的内容。最近做一些 Android 方面的优化工作,大家知道 Android 的 ClassLoader 在加载 dex 文件的过程中,而 AndroidManifest 的 Application 类就在 dex 文件中,Application 通常会做一些全局的初始化工作,在加载 dex 之前,我们需要替换原有的 Application 为 ProxyApplication。使其应用启动时加载 ProxyApplication,然后在其中实现加载 dex 等一些流程处理。而后要替换回原有的 Application(以下称为 RealApplication),确保应用正常运行,并且要保持生命周期、初始化顺序不变,屏蔽对于应用中 getContext,getApplicationContext 的影响。

在替换 Application 的过程中,应该注意以下几点:

  1. 创建 RealApplication,维护正常的生命周期,并进行回调。
  2. 对应用中屏蔽掉 ProxyApplication,对于下层无感知。在 Activity 等调用 getApplicationContext 之后,应该返回 RealApplication。
  3. ContentProvider 创建时机比较特殊,在满足正常的初始化顺序之后,也要屏蔽 ProxyApplication 的存在。

方案实现

在 AndroidManifest.xml 文件中替换 Application 为 ProxyApplication,可以使用自动化方式,或者打包方式,关于实现的具体细节此处不讨论。这里主要叙述创建 RealApplication 的过程。替换了 ProxyApplication 之后,对于系统而言 ProxyApplication 就是应用初始化的入口,所有的回调均是在 ProxyApplication 中发生。我们主要关注 attachBaseContext 和 onCreate 的回调。

创建 RealApplication

创建 RealApplication,我们可以使用反射的方式 newInstance 创建对象,然后执行回调 attachBaseContext。但是对于不同的系统版本,内部执行的细节可能不同,或者有其它相关逻辑的处理,所以我们采用另一种方式进行处理。首先看系统源码的如何实现,这里选择 8.0.0 的系统源码进行分析,其它版本去 http://androidxref.com 查看。

我们知道,Android 初始化是从 android.app.ActivityThread 开始的,所以从 ActivityThread 开始查看,ActivityThread 中存在静态方法 currentActivityThread 返回实例。可以参考系统的 ActivityThread 类:

public static ActivityThread currentActivityThread() {
  return sCurrentActivityThread;
 }

ActivityThread 内部存在成员变量 AppBindData mBoundApplication。AppBindData 是一个静态内部类,其中包含成员变量 LoadedApk info。查看 android.app.LoadedApk 源代码,发现创建 Application 的 makeApplication 方法。

 public Application makeApplication(boolean forceDefaultAppClass,
   Instrumentation instrumentation) {
  if (mApplication != null) {
   return mApplication;
  }

  Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

  Application app = ;

     mApplicationInfo.className;
   (forceDefaultAppClass || (appClass == )) {
   appClass = ;
  }

   {
   java.lang.   getClassLoader();
    (!mPackageName.equals()) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
      );
    initializeJavaContextClassLoader();
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
   }
      ContextImpl.createAppContext(mActivityThread, );
   app = mActivityThread.mInstrumentation.newApplication(
     cl, appClass, appContext);
   appContext.setOuterContext(app);
  }  (Exception e) {
    (!mActivityThread.mInstrumentation.onException(app, e)) {
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      (
       + appClass
      +  + e.toString(), e);
   }
  }
  mActivityThread.mAllApplications.add(app);
  mApplication = app;

   (instrumentation != ) {
    {
    instrumentation.callApplicationOnCreate(app);
   }  (Exception e) {
     (!instrumentation.onException(app, e)) {
     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
       (
       + app.getClass().getName()
      +  + e.toString(), e);
    }
   }
  }

  
  SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
    .getAssignedPackageIdentifiers();
      packageIdentifiers.size();
   (   ; i < N; i++) {
       packageIdentifiers.keyAt(i);
    (id ==  || id == ) {
    ;
   }

   rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
  }

  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

   app;
 }
null
String
appClass
=
if
null
"android.app.Application"
try
ClassLoader
cl
=
if
"android"
"initializeJavaContextClassLoader"
ContextImpl
appContext
=
this
catch
if
throw
new
RuntimeException
"Unable to instantiate application "
": "
if
null
try
catch
if
throw
new
RuntimeException
"Unable to create application "
": "
// Rewrite the R 'constants' for all library apks.
final
int
N
=
for
int
i
=
0
final
int
id
=
if
0x01
0x7f
continue
return

通过上面的代码可以发现,如果缓存 mApplication 不为空,则直接返回。mApplication 为空时,则创建 RealApplication,并且执行相关的回调,创建 RealApplication 时,类名是从 mApplicationInfo.className 中获取。

添加新创建 RealApplication 到 mActivityThread.mAllApplications。赋值给缓存 mApplication。所以我们在调用 makeApplication 之前,需要将 mApplication 置为 null,否则会直接返回 ProxyApplication 的实例。

首先,通过 android.app.ActivityThread 中静态方法获取 ActivityThread 实例,然后,通过 ActivityThread 实例,获得 LoadedApk 实例。为了使 makeApplication 顺利执行,先设置 mApplication 为 null。移除 mAllApplications 中 ProxyApplication 的实例。LoadedApk 中 mApplicationInfo 和 AppBindData 中 appInfo 都是 ApplicationInfo 类型,需要分别替换 className 字段的值为 RealApplication 的实际类全名。

之后,反射调用系统的 makeApplication。

这样,在 ProxyApplication.attachBaseContext 中,调用 makeApplication 创建 RealApplication,并且内部已经完成对于 RealApplication 的 attachBaseContext 的回调。在 ProxyApplication.onCreate 中只需要回调 RealApplication 实例的 onCreate,即可完成对于 RealApplication 的创建,已经内部替换以及正常的生命周期的回调。而且在 Activity 中调用 getApplicationContext 返回的值,实际上也是 LoadedApk 中 mApplication 的值,同时也保证对于 Activity 等地方屏蔽 ProxyApplication 的目的。

ContentProvider 中 getContext

Application 和 ContentProvider 的初始化顺序是:Application.attachBaseContext – ContentProvider.onCreate – Application.onCreate。ContentProvider 中也存在 getContext 方法,看 ContentProvider 的源代码实现:

其中 mContext 被赋值的有两个地方,一个在构造方法,一个是 attachInfo 的时候。继续追踪源代码中使用构造方法初始化,或者调用 attachInfo 的地方,结果在 android.app.ActivityThread 中找到 installProvider 方法中存在着调用关系。

可以看出,使用反射调用 ContentProvider 无参构造方法创建实例,然后调用了 attachInfo,传递的 Context 为 installProvider 方法中的参数,而 installProvider 的参数是在 installContentProviders 内部在初始化中传递的。

可以明确,installContentProviders 中调用 installProvider 时传递的 Context,也是由方法调用时传递的参数。继续向上追踪发现 ActivityThread.handleBindApplication 在初始化 ContentProvider 时调用了 installContentProviders,最终通过 attachInfo 设置给 ContentProvider 中的 Context 的实际类型是 Application。

在 App 初始化时,系统调用 makeApplication 创建了 ProxyApplication 实例,同时回调了 attachBaseContext(Context context)。所以这个方法返回的就是 App 初始化时 ProxyApplication,调用发生 ProxyApplication.attachBaseContext 之后,ProxyApplication.onCreate 之前。所以我们没有办法在这两个方法生命周期内进行替换为 RealApplication。

这种方案,接入成本比较低,但是新系统出现之后,可能出现兼容性的问题,需要每次发布新系统之后进行相关的适配。但是这种 Hook 解决问题的思路,可以借鉴一下。

  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Linux 基础 IO(三):文件描述符与重定向
  • 前端面试核心八股文汇总:JavaScript、React、Vue 等考点整理
  • 大型语言模型的 11 种高效微调策略详解
  • TypeTale 字字动画:免费 AIGC 视频创作工具指南
  • Flutter pathfinding 库在 OpenHarmony 上的适配与实战
  • CycleGAN 详解与图像风格迁移实现
  • LLM 解码方式详解:贪心、束搜索与采样策略
  • 哈希表原理与 LeetCode 实战
  • Qwen-Image-Lightning 本地部署与实战指南
  • 基于 Web 的学校田径运动会管理系统开发与实现开题答辩问答
  • 文心一言开源版测评:能力、易用性与价值
  • 基于 FPGA 的北斗导航自适应抗干扰算法设计与实现
  • Qt Creator 配置 GitHub Copilot 插件指南
  • OpenClaw 对接 QQ 机器人教程:本地与云端部署方案
  • AI 驱动游戏:鸿蒙生态的机会在哪里?
  • KingbaseES 数据库 ksql 连接指南:本地连接与基础交互
  • AI 生成代码缺陷分类与缓解策略综述
  • 基于 vLLM 和 Open-WebUI 快速部署 Meta-Llama-3-8B-Instruct
  • Stable Diffusion 扩散模型原理与 PyTorch 实现
  • C++ STL string 容器基础用法与构造技巧

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online