背景

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

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

  • 创建RealApplication,维护正常的生命周期,并进行回调。
  • 对应用中屏蔽掉ProxyApplication,对于下层无感知。在Activity等调用getApplicationContext之后,应该返回RealApplication。
  • 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 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的实际类全名。
Android动态替换Application实现_第1张图片
之后,反射调用系统的makeApplication.
Android动态替换Application实现_第2张图片

这样,在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的源代码实现:
Android动态替换Application实现_第3张图片

其中mContext被赋值的有两个地方,一个在构造方法,一个是attchInfo的时候。继续追踪源代码中使用构造方法初始化,或者调用attachInfo的地方,结果在android.app.ActivityThread中找到installProvider方法中存在着调用关系。
Android动态替换Application实现_第4张图片
可以看出,使用反射调用ContentProvider无参构造方法创建实例,然后调用了attachInfo,传递的Context为installProvider方法中的参数,而installProvider的参数是在installContentProviders内部在初始化中传递的。
Android动态替换Application实现_第5张图片

可以明确,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解决问题的思路,可以借鉴一下。

更多相关文章

  1. Android设置TextView显示指定个数字符,超过部分显示...(省略号)的
  2. 极光推送 使用实例 (一)服务端
  3. Android 文件操作详解及简单实例
  4. Android使用自定义字体的方法
  5. Android 环信官方Demo3.3.2详细配置方法
  6. Android中经常用到的方法--SDcard下文件的操作
  7. Android之实例界面设计
  8. Android Intent调用方法总结
  9. Android 应用程序退出后不在运行列表中显示的方法

随机推荐

  1. 使用系统key文件生成keystore
  2. android studio 混淆编译
  3. android获取有线网的Mac地址
  4. android activity向fragment通信,获取frag
  5. 转:Android设计模式之观察者模式
  6. Android(安卓)RoboGuice2 使用指南(3): I
  7. ViewModel+LiveData
  8. Activity切换导致的onCreate重复执行[转]
  9. Qt Android(安卓)QScreen 屏幕旋转功能
  10. Android(安卓)属性动画(Property Animatio