Xposed免重啟調試工具類


直接放代碼

package com.xirtam.hello;

import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;

import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import dalvik.system.PathClassLoader;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

/**
 * @author DX
 * 這種方案建議只在開發調試的時候使用,因為這將損耗一些性能(需要額外加載apk文件),調試沒問題后,直接修改xposed_init文件為正確的類即可
 * 可以實現免重啟,由於存在緩存,需要殺死宿主程序以后才能生效
 * 這種免重啟的方式針對某些特殊情況的hook無效
 * 例如我們需要implements IXposedHookZygoteInit,並將自己的一個服務注冊為系統服務,這種就必須重啟了
 * Created by DX on 2017/10/4.
 */

public class HookLoader implements IXposedHookLoadPackage {
    //按照實際使用情況修改下面幾項的值
    /**
     * 當前Xposed模塊的包名,方便尋找apk文件 TODO 需要配置
     */
    private final String modulePackage = "com.xirtam.hello";
    /**
     * 宿主程序的包名(允許多個),過濾無意義的包名,防止無意義的apk文件加載
     */
    private static List<String> hostAppPackages = new ArrayList<>();

    static {
        // TODO: Add the package name of application your want to hook!
        hostAppPackages.add("com.xirtam.hello");
    }

    /**
     * 實際hook邏輯處理類 TODO 需要配置
     */
    private final String handleHookClass = TestHook2.class.getName();
    /**
     * 實際hook邏輯處理類的入口方法
     */
    private final String handleHookMethod = "handleLoadPackage";

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        if (hostAppPackages.contains(loadPackageParam.packageName)) {
            //將loadPackageParam的classloader替換為宿主程序Application的classloader,解決宿主程序存在多個.dex文件時,有時候ClassNotFound的問題
            XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    Context context = (Context) param.args[0];
                    loadPackageParam.classLoader = context.getClassLoader();
                    invokeHandleHookMethod(context, modulePackage, handleHookClass, handleHookMethod, loadPackageParam);
                }
            });
        }
    }

    /**
     * 安裝app以后,系統會在/data/app/下備份了一份.apk文件,通過動態加載這個apk文件,調用相應的方法
     * 這樣就可以實現,只需要第一次重啟,以后修改hook代碼就不用重啟了
     *
     * @param context           context參數
     * @param modulePackageName 當前模塊的packageName
     * @param handleHookClass   指定由哪一個類處理相關的hook邏輯
     * @param loadPackageParam  傳入XC_LoadPackage.LoadPackageParam參數
     * @throws Throwable 拋出各種異常,包括具體hook邏輯的異常,尋找apk文件異常,反射加載Class異常等
     */
    private void invokeHandleHookMethod(Context context, String modulePackageName, String handleHookClass, String handleHookMethod, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
//        File apkFile = findApkFileBySDK(modulePackageName);//會受其它Xposed模塊hook 當前宿主程序的SDK_INT的影響
//        File apkFile = findApkFile(modulePackageName);
        //原來的兩種方式不是很好,改用這種新的方式
        File apkFile = findApkFile(context, modulePackageName);
        if (apkFile == null) {
            throw new RuntimeException("尋找模塊apk失敗");
        }
        //加載指定的hook邏輯處理類,並調用它的handleHook方法
        PathClassLoader pathClassLoader = new PathClassLoader(apkFile.getAbsolutePath(), ClassLoader.getSystemClassLoader());
        Class<?> cls = Class.forName(handleHookClass, true, pathClassLoader);
        Object instance = cls.newInstance();
        Method method = cls.getDeclaredMethod(handleHookMethod, XC_LoadPackage.LoadPackageParam.class);
        method.invoke(instance, loadPackageParam);
    }

    /**
     * 根據包名構建目標Context,並調用getPackageCodePath()來定位apk
     *
     * @param context           context參數
     * @param modulePackageName 當前模塊包名
     * @return return apk file
     */
    private File findApkFile(Context context, String modulePackageName) {
        if (context == null) {
            return null;
        }
        try {
            Context moudleContext = context.createPackageContext(modulePackageName, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
            String apkPath = moudleContext.getPackageCodePath();
            return new File(apkPath);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 尋找這個Android設備上的當前apk文件,不受其它Xposed模塊hook SDK_INT的影響
     *
     * @param modulePackageName 當前模塊包名
     * @return File 返回apk文件
     * @throws FileNotFoundException 在/data/app/下的未找到本模塊apk文件,請檢查本模塊包名配置是否正確.
     *                               具體檢查build.gradle中的applicationId和AndroidManifest.xml中的package
     */
    @Deprecated
    private File findApkFile(String modulePackageName) throws FileNotFoundException {
        File apkFile = null;
        try {
            apkFile = findApkFileAfterSDK21(modulePackageName);
        } catch (Exception e) {
            try {
                apkFile = findApkFileBeforeSDK21(modulePackageName);
            } catch (Exception e2) {
                //忽略這個異常
            }
        }
        if (apkFile == null) {
            throw new FileNotFoundException("沒在/data/app/下找到文件對應的apk文件");
        }
        return apkFile;
    }

    /**
     * 根據當前的SDK_INT尋找這個Android設備上的當前apk文件
     *
     * @param modulePackageName 當前模塊包名
     * @return File 返回apk文件
     * @throws FileNotFoundException 在/data/app/下的未找到本模塊apk文件,請檢查本模塊包名配置是否正確.
     *                               具體檢查build.gradle中的applicationId和AndroidManifest.xml中的package
     */
    @Deprecated
    private File findApkFileBySDK(String modulePackageName) throws FileNotFoundException {
        File apkFile;
        //當前Xposed模塊hook了Build.VERSION.SDK_INT不用擔心,因為這是發生在hook之前,不會有影響
        //但是其它的Xposed模塊hook了當前宿主的這個值以后,就會有影響了,所以這里沒有使用這個方法
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            apkFile = findApkFileAfterSDK21(modulePackageName);
        } else {
            apkFile = findApkFileBeforeSDK21(modulePackageName);
        }
        return apkFile;
    }

    /**
     * 尋找apk文件(api_21之后)
     * 在Android sdk21以及之后,apk文件的路徑發生了變化
     *
     * @param packageName 當前模塊包名
     * @return File 返回apk文件
     * @throws FileNotFoundException apk文件未找到
     */
    @Deprecated
    private File findApkFileAfterSDK21(String packageName) throws FileNotFoundException {
        File apkFile;
        File path = new File(String.format("/data/app/%s-%s", packageName, "1"));
        if (!path.exists()) {
            path = new File(String.format("/data/app/%s-%s", packageName, "2"));
        }
        if (!path.exists() || !path.isDirectory()) {
            throw new FileNotFoundException(String.format("沒找到目錄/data/app/%s-%s", packageName, "1/2"));
        }
        apkFile = new File(path, "base.apk");
        if (!apkFile.exists() || apkFile.isDirectory()) {
            throw new FileNotFoundException(String.format("沒找到文件/data/app/%s-%s/base.apk", packageName, "1/2"));
        }
        return apkFile;
    }

    /**
     * 尋找apk文件(api_21之前)
     *
     * @param packageName 當前模塊包名
     * @return File 返回apk文件
     * @throws FileNotFoundException apk文件未找到
     */
    @Deprecated
    private File findApkFileBeforeSDK21(String packageName) throws FileNotFoundException {
        File apkFile = new File(String.format("/data/app/%s-%s.apk", packageName, "1"));
        if (!apkFile.exists()) {
            apkFile = new File(String.format("/data/app/%s-%s.apk", packageName, "2"));
        }
        if (!apkFile.exists() || apkFile.isDirectory()) {
            throw new FileNotFoundException(String.format("沒找到文件/data/app/%s-%s.apk", packageName, "1/2"));
        }
        return apkFile;
    }
}

 

配置一下3處TODO標簽的位置,然后把xposed_init文件中的配置改成這個類即可,並不適用於所有情況的免重啟,注釋寫的很明確了,感謝原作者,本文代碼有少量修改。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM