Xposed 熱更新(干貨,建議收藏)

image.png

本文并不會告訴你Xposed如何使用,而是解決一個Xposed 每次更新代碼編譯安裝,都要重啟手機才能生效的問題,我們就叫 Xposed 熱更新 問題吧~

Xposed 是什么?如果你不知道,那么請點擊左上角返回按鈕,然后打開瀏覽器,輸入www.baidu.com,搜索關鍵詞”Xposed“~

之前的一篇Xposed破解某短視頻的文章被簡書推薦Xposed 破解某短視頻app(01),好幾天都被點贊和關注的消息”騷擾“,不過那篇文章并沒有講Xposed熱更新的功能(雖然代碼里已經有了),所以這篇文章單獨講一下這個功能。

1:為什么更新Xposed模塊apk,需要重啟手機才能生效?
2:如何做到不重啟能生效?

進入正題:

1、為什么更新Xposed模塊,需要重啟才能生效(原理)

這個問題涉及到Android底層知識和Xposed的原理,這里就簡單按我的理解說一下吧:

1.1 理解Zygote進程

Zygote進程是Android的核心,所有的應用程序進程以及系統服務進程都是由Zygote進程孵化出來的,所以如果能控制Zygote進程就等于控制了所有App進程。

Zygote的啟動配置在/init.rc腳本中,手機開機的時候先是啟動init進程,然后啟動Zygote進程,Zygote進程對應的執行文件是/system/bin/app_process,這個文件完成類庫加載及一些初始化函數調用的工作

1.2 Xposed的底層原理

Xposed的底層原理是通過替換/system/bin/app_precesss 來控制Zygote進程,并且加載Xposed framework的一個jar文件即 XposedBridge.jar,
這也就解釋了為什么使用Xposed需要root,因為要替換系統文件。

1.3 app_precesss 什么時候被替換?

首次安裝打開Xposed Installer這個App的時候,是需要在里面安裝Xposed版本的,這個安裝過程就是替換/system/bin/app_precesss的過程,替換之后需要重啟手機才能生效。

以上這些只是大概的原理,具體還需要看源碼才行。

2、如何做到不重啟能生效?

Xposed在手機開機的時候一次性加載和初始化已安裝并勾選的模塊,所以當模塊代碼改變之后,Xposed并不會主動去更新,需要重啟。

反射

Xposed模塊的入口類實現IXposedHookLoadPackage這個接口,然后重寫handleLoadPackage方法,這些基礎大家都知道

所以我們可以從 handleLoadPackage 方法入手,加載已經更新的apk的類,然后通過反射調用我們的邏輯代碼即可,具體怎么做呢?

handleLoadPackage 處理

    public void handleLoadPackage(final LoadPackageParam param) throws Throwable{
        //debug模式下才使用熱更新,因為涉及到反射,影響性能
        if (BuildConfig.DEBUG){
            debugHook(param);
        }else {
            releaseHook(param);
        }
    }

debug 模式下我們需要反射調用我們的入口類,看下 debugHook(param) 具體代碼

debugHook(param)

    private void debugHook(final LoadPackageParam param){

        final String packageName = Main.class.getPackage().getName();
        //查找apk路徑
        if (mApkFilePath == null){
            Log.d(TAG, "debugHook: mApkFilePath = null");
            //1、從 /data/app/com.lanshifu.xposeddemo-1.apk 找
            mApkFilePath = String.format("/data/app/%s-%s.apk", packageName, 1);
            if (!new File(mApkFilePath).exists()) {
                //2、從 /data/app/com.lanshifu.xposeddemo-2.apk 找
                mApkFilePath = String.format("/data/app/%s-%s.apk", packageName, 2);
                if (!new File(mApkFilePath).exists()) {
                    //3、從 /data/app/com.lanshifu.xposeddemo-1/base.apk 找
                    mApkFilePath = String.format("/data/app/%s-%s/base.apk", packageName, 1);
                    if (!new File(mApkFilePath).exists()) {
                        //4、從 /data/app/com.lanshifu.xposeddemo-2/base.apk 找
                        mApkFilePath = String.format("/data/app/%s-%s/base.apk", packageName, 2);
                        if (!new File(mApkFilePath).exists()) {
                            LogUtil.e("找不到apk路徑,熱更新失敗:filePath= "+ mApkFilePath + " ,packageName="+packageName);
                            mApkFilePath = null;
                            return;
                        }
                    }
                }
            }
        }

        //通過PathClassLoader 加載apk
        final PathClassLoader pathClassLoader = new PathClassLoader(mApkFilePath, ClassLoader.getSystemClassLoader());
        String className = MainModule.class.getName();
        //通過反射調用 MainModule的 handleLoadPackage 方法【重點】
        final Class<?> aClass;
        try {
            //反射調用MainModule的handleLoadPackage方法
            aClass = Class.forName(className, true, pathClassLoader);
            final Method aClassMethod = aClass.getMethod("handleLoadPackage", XC_LoadPackage.LoadPackageParam.class);
            aClassMethod.invoke(aClass.newInstance(), param);
        } catch (Exception e) {
            LogUtil.e("反射M ainModule 失敗:"+e.getMessage());
        }

    }

注釋夠清楚了吧,反射的關鍵一步在于找到apk路徑,我們安裝apk的時候,這個apk會被拷貝到/data/app/packagename*下面,不同的手機,命名是不一樣的,這里整理了4個不同的路徑:
/data/app/com.lanshifu.xposeddemo-2.apk
/data/app/com.lanshifu.xposeddemo-2.apk
/data/app/com.lanshifu.xposeddemo-1/base.apk
/data/app/com.lanshifu.xposeddemo-2/base.apk
...
其它手機如果熱更新無效,就是apk路徑不對,自己可以通過adb shell,進入/data/app/ 目錄下找對應包名的apk,然后稍微修改下代碼即可。

找到apk路徑之后,通過PathClassLoader加載這個apk,然后反射調用我們的入口類即可,這里的入口類是MainModule,入口方法 handleLoadPackage

releaseHook(param)

    private void releaseHook(final LoadPackageParam param){
        Log.d(TAG, "not hot load - >handleLoadPackage: " + param.packageName);
        MainModule module = new MainModule();
        module.handleLoadPackage(param);

    }

release 模式下直接調用MainModulehandleLoadPackage 方法

MainModule 沒啥好說的,就是開始我們的hook代碼

    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam param) {

        LogUtil.d(" handle packageName = " + param.packageName);

        if (param.packageName.equals("com.lanshifu.xposeddemo")) {
            hook_method("com.lanshifu.xposeddemo.ui.MainActivity", param.classLoader, "isOpen", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    Log.d("lxb", "hookMainActivity -- >initView");
                    param.setResult(true);
                    Toast.makeText((Context) param.thisObject, "模塊已經啟動", Toast.LENGTH_SHORT).show();
                }
            });
        }

        //番茄開始
        TomatoModule.hookClassLoader(param);

        //支付寶偷能量開始
//        AliPayModule.handle(param);
    }

好了,以上就是Xposed熱更新的全部內容,是不很簡單?
1、找到我們新安裝的apk路徑
2、然后通過反射調用入口方法。

讀了這篇文章,Xposed熱更新學會了嗎?

歡迎點贊+收藏+關注+star

需要源碼?
click hear, show you the code

需要注意一個點:
每次更新代碼,都要先run一次,然后改動下代碼(例如加空行),然后再run,才會顯示模塊已啟動,這個問題我還不知道為什么,反正改了代碼都要run兩次,大家可以自己去試試,看有沒有什么解決辦法。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容