性能優化 (八) 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 。一套完整的流程和代碼都已經寫完了。跟市面上的加固流程原理都幾乎同樣。懂了原理再去使用第三方就輕車熟路了。

代碼傳送

相關文章
相關標籤/搜索