性能优化 (八) APK 加固之动态替换 Application

性能优化系列

APP 启动优化java

UI 绘制优化android

内存优化git

图片压缩github

长图优化面试

电量优化性能优化

Dex 加解密app

动态替换 Applicationide

APP 稳定性之热修复原理探索post

APP 持续运行之进程保活实现性能

ProGuard 对代码和资源压缩

APK 极限压缩

简介

上一篇讲了 dex 加密解密 尚未看过的能够先去了解下 dex 怎么加解密,这篇就来带你们完成剩下的工做,dex 解密完成以后须要把代理 ProxyApplication 给删除掉,而后把咱们本身的 Application 给添加到咱们程序中。想要替换 ProxyApplication 可不是一件简单的事儿,首先必须的对 Application 启动源码很熟悉才能对它进行操做,下面由我来带着你们一块儿进入源码的世界吧。

Application 绑定过程

APP 启动流程能够看我另一篇文章性能优化(一)启动优化,今天主要从 ActivityThread => main() 开始,下面以一个流程图来讲明一下:

XML 中如何解析咱们的 Application

  • ActivityThread.java

    mian() -> thread.attach() -> attachApplication() -> 接收 AMS 发过来的参数以后 sendMessage(H.BIND_APPLICATION)-> 处理 BIND_APPLICATION -> handleBindApplication() 在这里准备好 application - > Application app = data.info.makeApplication() - > mInitialApplication = app;

  • LoadedApk.java

    这个类就是 APK 在内存中的表示,能够获得如代码,资料,功能清单等信息

    1. 经过 mApplicationInfo.className 获得咱们注册的全类名
    2. app = mActivityThread.mInstrumentation.newApplication () 建立 application
    3. 接下来会使用 appContext.setOuterContext(app)
    4. mApplication = app

反射须要替换的内容

  • ContextImpl -> mOuterContext(app) 经过 Application 的 attachBaseContext 回调参数获取
  • ActivityThread -> mAllApplication(arrayList) 经过 ContextImpl 的 mMainThread 属性获取
  • LoadedApk -> mApplication 经过 ContextImpl 的 mPackageInfo 属性获取

反射开始替换 Application

boolean isBindReal;
    Application delegate;
    private void bindRealApplicatin() throws Exception {
        if (isBindReal) {
            return;
        }
        if (TextUtils.isEmpty(app_name)) {
            return;
        }
        //获得attachBaseContext(context) 传入的上下文 ContextImpl
        Context baseContext = getBaseContext();
        //建立用户真实的application (MyApplication)
        Class<?> delegateClass = Class.forName(app_name);
        delegate = (Application) delegateClass.newInstance();
        //获得attach()方法
        Method attach = Application.class.getDeclaredMethod("attach", Context.class);
        attach.setAccessible(true);
        attach.invoke(delegate, baseContext);


// ContextImpl---->mOuterContext(app) 经过Application的attachBaseContext回调参数获取
        Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
        //获取mOuterContext属性
        Field mOuterContextField = contextImplClass.getDeclaredField("mOuterContext");
        mOuterContextField.setAccessible(true);
        mOuterContextField.set(baseContext, delegate);

// ActivityThread--->mAllApplications(ArrayList) ContextImpl的mMainThread属性
        Field mMainThreadField = contextImplClass.getDeclaredField("mMainThread");
        mMainThreadField.setAccessible(true);
        Object mMainThread = mMainThreadField.get(baseContext);

// ActivityThread--->>mInitialApplication
        Class<?> activityThreadClass=Class.forName("android.app.ActivityThread");
        Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
        mInitialApplicationField.setAccessible(true);
        mInitialApplicationField.set(mMainThread,delegate);
// ActivityThread--->mAllApplications(ArrayList) ContextImpl的mMainThread属性
        Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications");
        mAllApplicationsField.setAccessible(true);
        ArrayList<Application> mAllApplications =(ArrayList<Application>) mAllApplicationsField.get(mMainThread);
        mAllApplications.remove(this);
        mAllApplications.add(delegate);

// LoadedApk------->mApplication ContextImpl的mPackageInfo属性
        Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");
        mPackageInfoField.setAccessible(true);
        Object mPackageInfo=mPackageInfoField.get(baseContext);

        Class<?> loadedApkClass=Class.forName("android.app.LoadedApk");
        Field mApplicationField = loadedApkClass.getDeclaredField("mApplication");
        mApplicationField.setAccessible(true);
        mApplicationField.set(mPackageInfo,delegate);

        //修改ApplicationInfo className LooadedApk
        Field mApplicationInfoField = loadedApkClass.getDeclaredField("mApplicationInfo");
        mApplicationInfoField.setAccessible(true);
        ApplicationInfo mApplicationInfo = (ApplicationInfo)mApplicationInfoField.get(mPackageInfo);
        mApplicationInfo.className=app_name;

        delegate.onCreate();
        isBindReal = true;
    }
复制代码

如今从新签名打包完成,启动咱们的 APK 看下 Log

2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication@1ec3c70
2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication@1ec3c70
2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication
2019-06-04 23:17:30.895 6064-6064/com.yk.dexdeapplication I/DevYK: MyApplication onCreate() 2019-06-04 23:17:30.995 6064-6064/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App@300b5f6 2019-06-04 23:17:30.995 6064-6064/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App@300b5f6 2019-06-04 23:17:30.995 6064-6064/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App 2019-06-04 23:17:31.001 6064-6064/com.yk.dexdeapplication I/DevYK: provider delete:com.example.proxy_core.ProxyApplication@1ec3c70 2019-06-04 23:17:31.021 6064-6064/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App@300b5f6 2019-06-04 23:17:31.021 6064-6064/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App@300b5f6 2019-06-04 23:17:31.021 6064-6064/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App 2019-06-04 23:17:31.022 6064-6064/com.yk.dexdeapplication I/DevYK: reciver:android.app.ReceiverRestrictedContext@9b92293 2019-06-04 23:17:31.022 6064-6064/com.yk.dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.App@300b5f6 2019-06-04 23:17:31.022 6064-6064/com.yk.dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.App 复制代码

注意看 LOG

MyApplication onCreate() 复制代码

这里已经替换成咱们本身的 MyApplication , 并且 Activity 和 Service 获取上下文也已是咱们替换成功的 Applicaton。可是...也许有的眼神比较好的已经看出问题了,为何内容提供者 Context 仍是代理的 Application 并且比咱们本身的应用还要先执行,那么咱们带着这个问题去看 Application onCreate 以前作了什么事儿。

咱们点击 installlContentProviders(app,providers);

注意这里传进去的仍是 代理 Context

重点在最后

注意看我 勾画 的圈里面的逻辑判断,判断当前应用的包名是否跟 XML 中的包名一致,若是一致咱们就赋值,再次提醒下 这里的 context 是咱们代理的 context ,那么咱们怎么作勒,咱们在代理中重写 PackageName 只要都不等 那么就会走 else 会根据包名建立一个 Context

/** * 让代码走入if中的第三段中 * @return */
    @Override
    public String getPackageName() {
        if(!TextUtils.isEmpty(app_name)){
            return "";
        }
        return super.getPackageName();
    }

    @Override
    public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException {
       if(TextUtils.isEmpty(app_name)){
           return super.createPackageContext(packageName, flags);
       }
        try {
            bindRealApplicatin();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return delegate;

    }
复制代码
  1. 首先判断咱们本身 XML 中的 app_name 是否为空
  2. 若是不为空,咱们传入一个 空包
  3. SDK 会判断是否跟 XML 中的 pck 同样,最后走 else 咱们在重写 createPackageContext 传入咱们本身应用的包名。会生成一个 Context

最后咱们来验证一下:

2019-06-05 00:12:30.271 7570-7570/com.yk.dexdeapplication I/DevYK: MyApplication onCreate() 2019-06-05 00:12:30.273 7570-7570/com.yk.dexdeapplication I/DevYK: provider onCreate:com.yk.dexdeapplication.App@1ec3c70 2019-06-05 00:12:30.273 7570-7570/com.yk.dexdeapplication I/DevYK: provider onCreate:com.yk.dexdeapplication.App@1ec3c70 2019-06-05 00:12:30.273 7570-7570/com.yk.dexdeapplication I/DevYK: provider onCreate:com.yk.dexdeapplication.App 2019-06-05 00:12:30.381 7570-7570/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App@1ec3c70 2019-06-05 00:12:30.381 7570-7570/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App@1ec3c70 2019-06-05 00:12:30.381 7570-7570/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App 2019-06-05 00:12:30.387 7570-7570/com.yk.dexdeapplication I/DevYK: provider delete:com.yk.dexdeapplication.App@1ec3c70 2019-06-05 00:12:30.406 7570-7570/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App@1ec3c70 2019-06-05 00:12:30.406 7570-7570/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App@1ec3c70 2019-06-05 00:12:30.406 7570-7570/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App 2019-06-05 00:12:30.408 7570-7570/com.yk.dexdeapplication I/DevYK: reciver:android.app.ReceiverRestrictedContext@b7a3b82 2019-06-05 00:12:30.408 7570-7570/com.yk.dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.App@1ec3c70 2019-06-05 00:12:30.408 7570-7570/com.yk.dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.App 复制代码

日志中除了 BroadCase Context 是系统的之外,全部的 Context 都是咱们替换的 Application Context。完美解决。不过这里有一个隐藏 BUG ,听说面试题会问那么是什么勒?

能够在广播中使用 context 在开启一个广播或者绑定一个服务吗?

咱们其实能够带着这个问题看下源码

H -> RECEIVER 消息

果真注册广播和绑定服务会抛一个异常。

总结

到这里咱们的加固已经讲完了,从 dex 分包 -> 加密 -> 对齐 > 签名 - > 打包压缩成 APK 。一套完整的流程和代码都已经写完了。跟市面上的加固流程原理都几乎同样。懂了原理再去使用第三方就轻车熟路了。

代码传送