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 = null;

  String appClass = mApplicationInfo.className;
  if (forceDefaultAppClass || (appClass == null)) {
   appClass = "android.app.Application";
  }

  try {
   java.lang.ClassLoader cl = getClassLoader();
   if (!mPackageName.equals("android")) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
      "initializeJavaContextClassLoader");
    initializeJavaContextClassLoader();
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
   }
   ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
   app = mActivityThread.mInstrumentation.newApplication(
     cl, appClass, appContext);
   appContext.setOuterContext(app);
  } catch (Exception e) {
   if (!mActivityThread.mInstrumentation.onException(app, e)) {
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    throw new RuntimeException(
     "Unable to instantiate application " + appClass
     + ": " + e.toString(), e);
   }
  }
  mActivityThread.mAllApplications.add(app);
  mApplication = app;

  if (instrumentation != null) {
   try {
    instrumentation.callApplicationOnCreate(app);
   } catch (Exception e) {
    if (!instrumentation.onException(app, e)) {
     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     throw new RuntimeException(
      "Unable to create application " + app.getClass().getName()
      + ": " + e.toString(), e);
    }
   }
  }

  // Rewrite the R 'constants' for all library apks.
  SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
    .getAssignedPackageIdentifiers();
  final int N = packageIdentifiers.size();
  for (int i = 0; i < N; i++) {
   final int id = packageIdentifiers.keyAt(i);
   if (id == 0x01 || id == 0x7f) {
    continue;
   }

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

  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

  return app;
 }

通过上面的代码可以发现,如果缓存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的attchBaseContext的回调。在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被赋值的有两个地方,一个在构造方法,一个是attchInfo的时候。继续追踪源代码中使用构造方法初始化,或者调用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解决问题的思路,可以借鉴一下。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android ApplicationInfo 应用程序信息的详解

    Android ApplicationInfo 应用程序信息 1.简述 1 ApplicationInfo是android.content.pm包下的一个实体类,用于封装应用的信息,flags是其中的一个成员变量public int flags = 0;用于保存应用的标志信息. 2 ApplicationInfo 通过它可以得到一个应用基本信息. 这些信息是从AndroidManifest.xml的< application >标签获取的 3 ApplicationInfo对象里保存的信息都是

  • 详细解读Android系统中的application标签

    < application /> :应用的声明. 这个元素包含了子元素,这些子元素声明了应用的组件,元素的属性将会影响应用下的所有组件.很多属性为组件设置了默认值,有些属性设置了全局值并且不能被组件修改. <application>的子节点描述了应用所包含的组件,它的属性会影响到它所有的子节点组件.icon/lable/permission 等 属性是给子节点组件设置一个默认值,可以被复写.而 debuggable/enabled 等 属性是作为整个application的全局属性

  • Android编程之Application设置全局变量及传值用法实例分析

    本文实例讲述了Android编程之Application设置全局变量及传值用法.分享给大家供大家参考,具体如下: /** * 重写Application,主要重写里面的onCreate方法,就是创建的时候, * 我们让它初始化一些值,前段时间在javaeye里面看到过一个例子,与此相似, * 我做了些改进.听说外国开发者习惯用此初始化一些全局变量,好像在Activity * 一些类里面初始化全局变量的化,会遇到一些空指针的异常,当然,我没有遇到过. * 如果用此方法初始化的话,那么就可以避免那些

  • 详解Android中Application设置全局变量以及传值

    Application设置全局变量以及传值 /** * 重写Application,主要重写里面的onCreate方法,就是创建的时候, * 我们让它初始化一些值,前段时间在javaeye里面看到过一个例子,与此相似, * 我做了些改进.听说外国开发者习惯用此初始化一些全局变量,好像在Activity * 一些类里面初始化全局变量的化,会遇到一些空指针的异常,当然,我没有遇到过. * 如果用此方法初始化的话,那么就可以避免那些有可能出现的错误. * * 启动Application,他就会创建一个

  • Android application捕获崩溃异常怎么办

    Android application捕获崩溃异常怎么办? 通用 application 1.收集所有 avtivity 用于彻底退出应用 2.捕获崩溃异常,保存错误日志,并重启应用 public class HKBaseApplication extends Application { // activity对象列表,用于activity统一管理 private List<Activity> activityList; // 异常捕获 protected boolean isNeedCaug

  • Android Application存取公共数据的实例详解

    Android Application存取公共数据的实例详解 Android系统在运行每一个程序应用的时候,都会创建一个Application对象,用于存储与整个应用相关的公共变量. 一个Android应用只会生成一个Application对象,在不同的Activity中获取的Application对象是一样的,所以Application对象是一个单例(SingleTon). Application对象非常适合用于存储一些与整个应用相关数据,例如应用版本,应用登录账户,数据缓存等. 利用Appl

  • Android Framework Application Framework层简单介绍

    引言 Android的四层架构相比大家都很清楚,老生常谈的说一下分别为:Linux2.6内核层,核心库层,应用框架层,应用层.我今天重点介绍一下应用框架层Framework,其实也是我自己的学习心得. Framework层为我们开发应用程序提供了非常多的API,我们通过调用特殊的API构造我们的APP,满足我们业务上的需求.写APP的人都知道,学习Android开发的第一步就是去学习各种各样的API,什么Activity,Service,Notification等.这些都是framework提供

  • Android Application类的详细介绍

    Android Application类详解: Android中Application类的详细解释: 我们在平时的开发中,有时候可能会需要一些全局数据,来让应用中的所有Activity和View都能访问到,大家在遇到这种情况时,可能首先会想到自己定义一个类,然后创建很多静态成员. 但是这种方法不符合Android的框架架构,不过andorid已经为我们提供了这种情况的解决方案:在Android中,有一个名为Application的类,我们可以在Activity中使用getApplication(

  • Android动态替换Application实现

    背景 虽然热更新和Hook技术都被大家聊烂了,但是还是想和大家聊一下这方面的内容.最近做一些Android方面的优化工作,大家知道Android的ClassLoader在加载dex文件的过程中,而AndroidManifest的Application类就在dex文件中,Application通常会做一些全局的初始化工作,在加载dex之前,我们需要替换原有的Application为ProxyApplication.使其应用启动时加载ProxyApplication,然后在其中实现加载dex等一些流

  • Android底部导航栏的动态替换方案

    Android底部导航栏的动态替换方案,供大家参考,具体内容如下 1.通常来说,一般情况下,我们的app的BottomTab会有下面几种实现方式. 1).自定义view,然后自己写逻辑去实现互斥. 2).使用RadioGroup+RadioButton去实现底部的Tab. 自由度比极高,如果想实现搞复杂度的话可以重写 RadioButton. 3).使用google design包里面的 TabLayout去实现. 可上.可下.可以滑动 偷懒的话可以根据已有api来设置一些资源,也可以 setC

  • Android动态修改应用图标与名称的方法实例

    遇到的坑 这里我把做这个功能中遇到的一些问题写在前面,是为了大家能先了解有什么问题存在,遇到这些问题的时候就不慌了,这里我把应用图标和名称先统一使用icon代替进行说明. 1.动态替换icon,只能替换内置的icon,无法从服务器端获取来更新icon: 2.动态替换icon以后,应用内更新的时候必须要切换到原始icon),否则可能导致更新安装失败(AS上表现为adb运行会失败),或者升级后应用图标出现多个甚至应用图标都不显示的情况(这些问题都可以通过下面我推荐的开发规则解决掉,所以这是一个坑点,

  • Android 动态注册监听网络变化实例详解

    Android 动态注册监听网络变化实例详解 新建一个BroadcastTest项目,然后修改MainActivity中的代码,如下: public class MainActivity extends AppCompatActivity { private IntentFilter intentFilter; private NetworkChangeReceiver networkChangeReceiver; @Override protected void onCreate(Bundle

  • 使用springboot logback动态获取application的配置项

    目录 springboot logback动态获取application的配置项 logback自身支持以下方式配置 因此只能使用spring提供的标签来对此参数进行配置 springboot logback配置及动态配置log目录 子节点一 appender 控制台输出appender 文件输入appender root节点-启用配置 动态配置log目录 获取jar目录路径 springboot logback动态获取application的配置项 在多环境的情况下,logback的日志路径需

  • Android动态更换应用图标详情

    目录 一.背景 二.技术实现 一.背景 近日,微博官方发布了一项新功能,即可以在App设置中动态更换微博的显示图标样式.根据微博官方的说法,除了最原始的图标外,微博还推出了另外10种不同的样式,既有3D微博.炫彩微博等保留了眼睛造型的新样式,也有奶酪甜馨.巧克力等以食物命名的“新口味”,还有梦幻紫.幻想星空等抽象派新造型,给了微博用户多种选择的自由. 不过需要注意的是,这一功能并不是面对所有人开放的,只有微博年费会员才能享受.此外,iOS 10.3及以上和Android 10及以上系统版本支持该

  • 如何动态替换Spring容器中的Bean

    目录 动态替换Spring容器中的Bean 原因 方案 实现 Spring中bean替换问题 动态替换Spring容器中的Bean 原因 最近在编写单测时,发现使用 Mock 工具预定义 Service 中方法的行为特别难用,而且无法精细化的实现自定义的行为,因此想要在 Spring 容器运行过程中使用自定义 Mock 对象,该对象能够代替实际的 Bean 的给定方法. 方案 创建一个 Mock 注解,并且在 Spring 容器注册完所有的 Bean 之后,解析 classpath 下所有引入该

  • Android 动态加载 so实现示例详解

    目录 背景 so动态加载介绍 从一个例子出发 so库检索与删除 动态加载so 结束了吗? ELF文件 扩展 总结 背景 对于一个普通的android应用来说,so库的占比通常都是巨高不下的,因为我们无可避免的在开发中遇到各种各样需要用到native的需求,所以so库的动态化可以减少极大的包体积,自从2020腾讯的bugly团队发部关于动态化so的相关文章后,已经过去两年了,相关文章,经过两年的考验,实际上so动态加载也是非常成熟的一项技术了. 但是很遗憾,许多公司都还没有这方面的涉略又或者说不知

  • Android动态自定义圆形进度条

    效果图: A.绘制圆环,圆弧,文本 //1.画圆环 //原点坐标 float circleX = width / 2; float circleY = width / 2; //半径 float radius = width / 2 - roundWidth / 2; //设置画笔的属性 paint.setColor(roundColor); paint.setStrokeWidth(roundWidth); paint.setStyle(Paint.Style.STROKE); canvas.

  • spring hibernate实现动态替换表名(分表)的方法

    1.概述 其实最简单的办法就是使用原生sql,如 session.createSQLQuery("sql"),或者使用jdbcTemplate.但是项目中已经使用了hql的方式查询,修改起来又累,风险又大!所以,必须找到一种比较好的解决方案,实在不行再改写吧!经过3天的时间的研究,终于找到一种不错的方法,下面讲述之. 2.步骤 2.1 新建hibernate interceptor类 /** * Created by hdwang on 2017/8/7. * * hibernate拦

随机推荐