Android中动态替换Application的实现

1.背景

最近一直在做优化相关事情,需要在启动时干预加载dex文件的过程,而AndroidManifest设定的Application类已经在dex文件中,在加载dex之前,不能找到这个Application类。所以我们需要替换原有的Application为ProxyApplication。使其应用启动时加载ProxyApplication,我们在其中实现加载dex等一些流程处理。而后要替换回原有的Application(以下为RealApplication),确保应用正常运行,并且要保持生命周期、初始化顺序不变,屏蔽对于应用中getContext,getApplicationContext的影响。

2.需要处理的问题

在我们替换ProxyApplication之后,在初始化时创建RealApplication,还需要满意以下几个条件:

创建RealApplication,维护正常的生命周期,并进行回调。

对应用中屏蔽掉ProxyApplication,对于下层无感知。在Activity等调用getApplicationContext之后,应该返回RealApplication。

ContentProvider创建时机比较特殊,在满足正常的初始化顺序之后,也要屏蔽ProxyApplication的存在。

3.方案具体实现

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

3.1 创建RealApplication

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

我们知道,Java层初始化可以认为是从android.app.ActivityThread开始,所以从ActivityThread开始查看。ActivityThread中存在静态方法currentActivityThread返回实例。

image

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

image

上面去掉不相关代码之后,可以明显看出:

如果缓存mApplication不为空,则直接返回。

mApplication为空时,则创建RealApplication,并且执行相关的回调。创建RealApplication时,类名是从mApplicationInfo.className中获取。

添加新创建RealApplication到mActivityThread.mAllApplications。

赋值给缓存mApplication。

所以我们在调用makeApplication之前,需要将mApplication置为null,否则会直接返回ProxyApplication的实例。

首先,通过android.app.ActivityThread中静态方法获取实例

image

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

image

之后,反射调用系统的makeApplication

image

最终,使用上面三个方法组合起来,完成我们的逻辑。

image

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

3.2 ContentProvider中getContext问题

通过阅读系统的源代码(或者自己打log),可以很容易的知道,Application和ContentProvider的初始化顺序是:Application.attachBaseContext -> ContentProvider.onCreate -> Application.onCreate

在保持正常Application生命周期的情况下,也要保持对ContentProvider中无感知。因为ContentProvider中也存在getContext方法,看ContentProvider的源代码实现:

image

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

image

以上源代码中,省略了不相关的部分代码。可以看出,使用反射调用ContentProvider无参构造方法创建实例,然后调用了attachInfo,传递的Context为installProvider方法中的参数。那这个参数哪传递过来的呢?带着疑问从源码中继续追踪调用关系,发现installContentProviders内部在初始化每个ContentProvider时,分别调用了一次installProvider。看installContentProviders的主要逻辑:

image

可以明确,installContentProviders中调用installProvider时传递的Context,也是由方法调用时传递的参数。继续向上追踪发现ActivityThread.handleBindApplication在初始化ContentProvider时调用了installContentProviders,看源代码中关键部分。

image

通过这部分的源代码,我们明确知道,最终通过attachInfo设置给ContentProvider中的Context的实际类型是Application。其中返回Application这行语句中,data的类型是AppBindData,info的类型是LoadedApk,所以makeApplication的具体实现就是章节3.1中列举的源代码。

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

如果在attachBaseContext中设置mInitialApplication的值,在初始化ContentProvider之前,又被重新设置为ProxyApplication。这样在初始化ContentProvider时传递的Context依然是ProxyApplication,在ContentProvider的onCreate调用getContext返回的依然不是RealApplication,这种情况也是不能满足对下层无感知的需求。

如果在onCreate中设置mInitialApplication,也是不能起作用的,因为此时已经完成了ContentProvider的初始化过程。

仔细阅读源代码,发现初始化ContentProvider是有一系列条件控制,那么我们可以提前修改data.providers为null,让系统不执行ContentProvider的初始化。我们在ProxyApplication.attachBaseContext主动调用installContentProviders,传递RealApplication给ContentProvider。在ProxyApplication.onCreate中恢复data.providers的数据,减小对其它逻辑可能带来的影响。具体的实现分两部分,在attachBaseContext中调用我们实现的installContentProviders,源代码如下:

image

在onCreate中调用的restoreProviderInfo,恢复AppBindData中的List<ProviderInfo> providers原来的值,具体源代码如下:

image

4. 总结

以上大致讲解了从源代码的实现进行分析,了解系统基本的实现原理之后,进行相关的反射修改内部的值,达到我们替换Application的目的。这种方案,接入成本比较低,但是新系统出现之后,可能出现兼容性的问题,需要每次发布新系统之后进行相关的适配。但是这种解决问题的思路,可以借鉴一下,从别的地方进行Java Hook,实现一些黑科技的东西。而且替换Application这个方式,不只是可以应用在安装速度优化上面,同时也可以应用在其他一些场景上。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容