前言
本文涉及的 Android
源碼部分基于 Android 10
。
Xposed
在聊 Xposed
前我們先簡單在腦海里回顧下 Zygote
進(jìn)程的啟動(dòng)流程,如果記不清的可以先看一看 Android Framework 之 Zygote。
入口函數(shù)的差異
Zygote
進(jìn)程 native
層入口函數(shù)為 frameworks/base/cmds/app_process/app_main.cpp
,而在 Xposed 項(xiàng)目中也有 app_main.cpp
病曾。
那 Xposed
的這個(gè)文件有什么用呢?
其實(shí)在 recovery
模式下刷 Xposed
時(shí)漾根,它會(huì)用 app_main.cpp
或 app_main2.cpp
替換系統(tǒng)的 app_process/app_main.cpp
泰涂,在 Android.mk
中有如下代碼。
// Android.mk
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
LOCAL_SRC_FILES := app_main2.cpp
else
LOCAL_SRC_FILES := app_main.cpp
endif
即版本高于 21 時(shí)使用 app_main2.cpp
辐怕,否則用 app_main.cpp
逼蒙。
來看看 app_main2.cpp
有哪些主要的改動(dòng)。
// app_process/app_main.cpp
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
}
// app_main2.cpp
if (zygote) {
isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv);
runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
isXposedLoaded = xposed::initialize(false, false, className, argc, argv);
runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS : "com.android.internal.os.RuntimeInit", args, zygote);
}
可以看到寄疏,主要有如下差異:
- 在啟動(dòng)
Zygote
進(jìn)程或孵化應(yīng)用進(jìn)程時(shí)會(huì)先執(zhí)行xposed::initialize()
進(jìn)行初始化是牢; - 執(zhí)行
runtimeStart()
,注意這里的XPOSED_CLASS_DOTS_ZYGOTE
和XPOSED_CLASS_DOTS_TOOLS
陕截。
xposed::initalize()
先看看 xposed::initialize()
驳棱,源碼在 xposed.h
和 xposed.cpp
。
// xposed.h
#define XPOSED_JAR "/system/framework/XposedBridge.jar"
// xposed.cpp
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
// ...
return addJarToClasspath();
}
bool addJarToClasspath() {
//...
if (access(XPOSED_JAR, R_OK) == 0) {
// 把 XposedBridge.jar 添加到 classPath
if (!addPathToEnv("CLASSPATH", XPOSED_JAR))
return false;
return true;
}
}
總的來說 xposed::initialize()
會(huì)把 XposedBridge.jar
添加到 classpath
农曲,這樣 Zygote
進(jìn)程孵化的應(yīng)用進(jìn)程就都具備了 XposedBridge.jar
的代碼社搅。
執(zhí)行 runtimeStart()
來看看 runtimeStart()
。
// app_main2.cpp
static void runtimeStart(AppRuntime& runtime, const char *classname, const Vector<String8>& options, bool zygote)
{
#if PLATFORM_SDK_VERSION >= 23
runtime.start(classname, options, zygote);
#else
void (*ptr1)(AppRuntime&, const char*, const Vector<String8>&, bool);
*(void **) (&ptr1) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEEb");
if (ptr1 != NULL) {
ptr1(runtime, classname, options, zygote);
return;
}
void (*ptr2)(AppRuntime&, const char*, const Vector<String8>&);
*(void **) (&ptr2) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEE");
if (ptr2 != NULL) {
ptr2(runtime, classname, options);
return;
}
#endif
}
做了版本判斷乳规,這里最終還是會(huì)調(diào)用 AndroidRuntime
的 start()
形葬,了解 Zygote
進(jìn)程啟動(dòng)流程的應(yīng)該知道這里主要做了三件事,創(chuàng)建虛擬機(jī)暮的,注冊(cè) JNI
荷并,通過反射調(diào)用 classname
的 main()
。
創(chuàng)建虛擬機(jī)
Xposed
在創(chuàng)建虛擬機(jī)之后做了一些修改青扔,先看看 AndroidRuntime
的 onVmCreated()
源织。
// app_main2.cpp
class AppRuntime : public AndroidRuntime
{
virtual void onVmCreated(JNIEnv* env)
{
if (isXposedLoaded)
xposed::onVmCreated(env);
}
}
這里調(diào)用 xposed::onVmCreated()
,其源碼在 xposed.cpp
微猖。
// xposed.cpp
void onVmCreated(JNIEnv* env) {
// 加載 libxposed_art.so
void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW);
// 初始化 xposed 相關(guān) library
bool (*xposedInitLib)(XposedShared* shared) = NULL;
*(void **) (&xposedInitLib) = dlsym(xposedLibHandle, "xposedInitLib");
if (xposedInitLib(xposed)) {
// 執(zhí)行 onVmCreated(env)
xposed->onVmCreated(env);
}
}
這里通過 dlopen()
函數(shù)加載 libxposed_art.so
谈息,在 Android.mk
中有如下定義。
// Android.mk
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
include frameworks/base/cmds/xposed/ART.mk
endif
使用 ART.mk
生成 libxposed_art.so
凛剥。
// ART.mk
LOCAL_SRC_FILES += \
libxposed_common.cpp \
libxposed_art.cpp
LOCAL_MODULE := libxposed_art
libxposed_art.so
加載后侠仇,接著會(huì)初始化 xposed
相關(guān) library
,然后調(diào)用 xposedInitLib()
,其源碼在 libxposed_art.cpp
逻炊。
// libxposed_art.cpp
bool xposedInitLib(XposedShared* shared) {
xposed = shared;
xposed->onVmCreated = &onVmCreatedCommon;
return true;
}
這里進(jìn)行賦值互亮,即 xposed->onVmCreated(env)
會(huì)執(zhí)行 onVmCreatedCommon()
,其源碼在 libxposed_common.cpp
余素。
// libxposed_common.cpp
void onVmCreatedCommon(JNIEnv* env) {
if (!initXposedBridge(env) || !initZygoteService(env)) {
return;
}
if (!onVmCreated(env)) {
return;
}
xposedLoadedSuccessfully = true;
return;
}
initXposedBridge()
會(huì)初始化 XposedBridge.java
豹休,initZygoteService()
會(huì)初始化 ZygoteService.java
,主要看看 initXposedBridge()
桨吊。
bool initXposedBridge(JNIEnv* env) {
classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE);
classXposedBridge = reinterpret_cast<jclass>(env->NewGlobalRef(classXposedBridge));
// 這里會(huì)注冊(cè) native 函數(shù)
if (register_natives_XposedBridge(env, classXposedBridge) != JNI_OK) {
return false;
}
// 這里會(huì)獲取到 XposedBridge 的 handleHookedMethod
methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod",
"(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
return true;
}
// 注冊(cè) native 函數(shù)
int register_natives_XposedBridge(JNIEnv* env, jclass clazz) {
const JNINativeMethod methods[] = {
NATIVE_METHOD(XposedBridge, hadInitErrors, "()Z"),
NATIVE_METHOD(XposedBridge, getStartClassName, "()Ljava/lang/String;"),
NATIVE_METHOD(XposedBridge, getRuntime, "()I"),
NATIVE_METHOD(XposedBridge, startsSystemServer, "()Z"),
NATIVE_METHOD(XposedBridge, getXposedVersion, "()I"),
NATIVE_METHOD(XposedBridge, initXResourcesNative, "()Z"),
NATIVE_METHOD(XposedBridge, hookMethodNative, "(Ljava/lang/reflect/Member;Ljava/lang/Class;ILjava/lang/Object;)V"),
NATIVE_METHOD(XposedBridge, setObjectClassNative, "(Ljava/lang/Object;Ljava/lang/Class;)V"),
NATIVE_METHOD(XposedBridge, dumpObjectNative, "(Ljava/lang/Object;)V"),
NATIVE_METHOD(XposedBridge, cloneToSubclassNative, "(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;"),
NATIVE_METHOD(XposedBridge, removeFinalFlagNative, "(Ljava/lang/Class;)V"),
#if PLATFORM_SDK_VERSION >= 21
NATIVE_METHOD(XposedBridge, invokeOriginalMethodNative,
"!(Ljava/lang/reflect/Member;I[Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
NATIVE_METHOD(XposedBridge, closeFilesBeforeForkNative, "()V"),
NATIVE_METHOD(XposedBridge, reopenFilesAfterForkNative, "()V"),
#endif
#if PLATFORM_SDK_VERSION >= 24
NATIVE_METHOD(XposedBridge, invalidateCallersNative, "([Ljava/lang/reflect/Member;)V"),
#endif
};
return env->RegisterNatives(clazz, methods, NELEM(methods));
}
這里主要是注冊(cè) native
函數(shù)威根,并獲取 handleHookedMethod()
賦值給 methodXposedBridgeHandleHookedMethod
。
總的來說這里主要是加載 libxposed_art.so
视乐,并初始化一些 library
洛搀,然后注冊(cè) native
函數(shù)。
反射調(diào)用 XPOSED_CLASS_DOTS_ZYGOTE 的 main()
最終會(huì)通過反射調(diào)用 XPOSED_CLASS_DOTS_ZYGOTE
的 main()
佑淀,其定義位于 xposed.h
留美。
#define XPOSED_CLASS_DOTS_ZYGOTE "de.robv.android.xposed.XposedBridge"
即最終會(huì)執(zhí)行 de.robv.android.xposed.XposedBridge.main()
。它是 XposedBridge.jar
中的類伸刃,源碼位于 XposedBridge 項(xiàng)目独榴,來看看其 main()
。
protected static void main(String[] args) {
// 1. 初始化 Xposed framework 和 Xposed module(也就是我們平常寫的 Xposed 插件)
try {
if (!hadInitErrors()) {
initXResources();
SELinuxHelper.initOnce();
SELinuxHelper.initForProcess(null);
runtime = getRuntime();
XPOSED_BRIDGE_VERSION = getXposedVersion();
if (isZygote) {
// hook 各版本資源獲取與創(chuàng)建
XposedInit.hookResources();
// hook 系統(tǒng)關(guān)鍵方法
XposedInit.initForZygote();
}
// 加載 Xposed Modules
XposedInit.loadModules();
}
} catch (Throwable t) {
disableHooks = true;
}
// 2. 原 Zygote 和 應(yīng)用進(jìn)程 啟動(dòng)流程
if (isZygote) {
ZygoteInit.main(args);
} else {
RuntimeInit.main(args);
}
}
這里主要做了如下事情:
- 調(diào)用
XposedInit.hookResources()
來hook
系統(tǒng)資源相關(guān)方法奕枝; - 調(diào)用
XposedInit.initForZygote()
來hook Zygote
相關(guān)方法; - 調(diào)用
XposedInit.loadModules()
來加載Xposed Module
瓶堕。
這里說下 loadModules()
隘道,來看看 loadModules()
源碼。
private static final String INSTALLER_PACKAGE_NAME = "de.robv.android.xposed.installer";
private static final String BASE_DIR = Build.VERSION.SDK_INT >= 24
? "/data/user_de/0/" + INSTALLER_PACKAGE_NAME + "/"
: "/data/data/" + INSTALLER_PACKAGE_NAME + "/";
static void loadModules() throws IOException {
final String filename = BASE_DIR + "conf/modules.list";
ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
ClassLoader parent;
while ((parent = topClassLoader.getParent()) != null) {
topClassLoader = parent;
}
// 讀安裝目錄的 conf/modules.list 文件
InputStream stream = service.getFileInputStream(filename);
BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
String apk;
while ((apk = apks.readLine()) != null) {
// 調(diào)用 loadModule() 加載 xposed module
loadModule(apk, topClassLoader);
}
apks.close();
}
這里會(huì)加載安裝目錄下的 conf/modules.list
文件郎笆,然后對(duì)每個(gè) xposed module
調(diào)用 loadModule()
來進(jìn)行注冊(cè)谭梗。
private static void loadModule(String apk, ClassLoader topClassLoader) {
DexFile dexFile;
try {
dexFile = new DexFile(apk);
} catch (IOException e) {
return;
}
// 禁止 Android Studio 的 instant run
if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) {
Log.e(TAG, " Cannot load module, please disable \"Instant Run\" in Android Studio.");
closeSilently(dexFile);
return;
}
// 注意 Xposed Module 對(duì) xposed 庫的依賴是 compileOnly,不會(huì)被打入到 apk 中
if (dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) != null) {
Log.e(TAG, " Cannot load module:");
Log.e(TAG, " The Xposed API classes are compiled into the module's APK.");
Log.e(TAG, " This may cause strange issues and must be fixed by the module developer.");
Log.e(TAG, " For details, see: http://api.xposed.info/using.html");
closeSilently(dexFile);
return;
}
closeSilently(dexFile);
// 讀取 assets/xposed_init 配置文件
ZipFile zipFile = null;
InputStream is;
try {
zipFile = new ZipFile(apk);
ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init");
// 找不到 xposed_init 配置文件
if (zipEntry == null) {
Log.e(TAG, " assets/xposed_init not found in the APK");
closeSilently(zipFile);
return;
}
is = zipFile.getInputStream(zipEntry);
} catch (IOException e) {
closeSilently(zipFile);
return;
}
// 找注冊(cè)類
ClassLoader mcl = new PathClassLoader(apk, XposedBridge.BOOTCLASSLOADER);
BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
try {
String moduleClassName;
while ((moduleClassName = moduleClassesReader.readLine()) != null) {
moduleClassName = moduleClassName.trim();
if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
continue;
try {
Class<?> moduleClass = mcl.loadClass(moduleClassName);
if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
continue;
} else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
continue;
}
final Object moduleInstance = moduleClass.newInstance();
if (XposedBridge.isZygote) {
if (moduleInstance instanceof IXposedHookZygoteInit) {
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
param.modulePath = apk;
param.startsSystemServer = startsSystemServer;
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
}
if (moduleInstance instanceof IXposedHookLoadPackage)
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
if (moduleInstance instanceof IXposedHookInitPackageResources)
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
}
} catch (Throwable t) {
}
}
} catch (IOException e) {
} finally {
closeSilently(is);
closeSilently(zipFile);
}
}
這里會(huì)解析 assets/xposed_init
配置文件宛蚓,然后對(duì)配置中聲明的類進(jìn)行注冊(cè)激捏。
方法如何被 hook
在 Xposed Module
中,想要 hook
某個(gè)方法可以通過 XposedHelpers.findAndHookMethod()
凄吏,來看看其源碼远舅。
public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
}
public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
// 就是 findAndHookMethod() 的最后一個(gè)參數(shù)
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
// 反射找方法
Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
// 調(diào)用 XposedBridge.hookMethod()
return XposedBridge.hookMethod(m, callback);
}
來看看 XposedBridge.hookMethod()
。
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
// 方法是否滿足 hook 條件
if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
} else if (hookMethod.getDeclaringClass().isInterface()) {
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
} else if (Modifier.isAbstract(hookMethod.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
}
// 緩存
boolean newMethod = false;
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
synchronized (sHookedMethodCallbacks) {
callbacks = sHookedMethodCallbacks.get(hookMethod);
if (callbacks == null) {
callbacks = new CopyOnWriteSortedSet<XC_MethodHook>();
sHookedMethodCallbacks.put(hookMethod, callbacks);
newMethod = true;
}
}
callbacks.add(callback);
// 主要是這里
if (newMethod) {
Class<?> declaringClass = hookMethod.getDeclaringClass();
int slot;
Class<?>[] parameterTypes;
Class<?> returnType;
if (runtime == RUNTIME_ART) {
slot = 0;
parameterTypes = null;
returnType = null;
} else if (hookMethod instanceof Method) {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Method) hookMethod).getParameterTypes();
returnType = ((Method) hookMethod).getReturnType();
} else {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
returnType = null;
}
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
// 調(diào)用 native 方法
hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
}
return callback.new Unhook(hookMethod);
}
從這里可以看到痕钢,一個(gè)方法能否被 hook
图柏,必須滿足:
- 是普通的方法或構(gòu)造器;
- 不是接口方法任连;
- 不是抽象方法蚤吹。
最終執(zhí)行 hook
操作的是 native
方法 hookMethodNative()
,其源碼位于 libxposed_art.cpp
void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
jobject, jint, jobject javaAdditionalInfo) {
// 把 java method 轉(zhuǎn)換成 ArtMethod
ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);
// 這里是真正的 hook 邏輯
artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}
這里將 Java
的 Method
轉(zhuǎn)為 ArtMethod
,然后調(diào)用 EnableXposedHook()
裁着,這里是最終做 hook
的地方繁涂,其源碼位于 android_art 的 art_method.cc
。
void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
// 1. 備份
auto* cl = Runtime::Current()->GetClassLinker();
auto* linear_alloc = cl->GetAllocatorForClassLoader(GetClassLoader());
ArtMethod* backup_method = cl->CreateRuntimeMethod(linear_alloc);
backup_method->CopyFrom(this, cl->GetImagePointerSize());
backup_method->SetAccessFlags(backup_method->GetAccessFlags() | kAccXposedOriginalMethod);
// 2. 創(chuàng)建備份方法的反射對(duì)象
mirror::AbstractMethod* reflected_method;
if (IsConstructor()) {
reflected_method = mirror::Constructor::CreateFromArtMethod(soa.Self(), backup_method);
} else {
reflected_method = mirror::Method::CreateFromArtMethod(soa.Self(), backup_method);
}
reflected_method->SetAccessible<false>(true);
// 3. 將信息存放到結(jié)構(gòu)體中
XposedHookInfo* hook_info = reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(), sizeof(XposedHookInfo)));
hook_info->reflected_method = soa.Vm()->AddGlobalRef(soa.Self(), reflected_method);
hook_info->additional_info = soa.Env()->NewGlobalRef(additional_info);
hook_info->original_method = backup_method;
// 4. 準(zhǔn)備工作二驰,處理函數(shù)的JIT即時(shí)編譯以及其他
ScopedThreadSuspension sts(soa.Self(), kSuspended);
jit::ScopedJitSuspend sjs;
gc::ScopedGCCriticalSection gcs(soa.Self(),
gc::kGcCauseXposed,
gc::kCollectorTypeXposed);
ScopedSuspendAll ssa(__FUNCTION__);
cl->InvalidateCallersForMethod(soa.Self(), this);
jit::Jit* jit = art::Runtime::Current()->GetJit();
if (jit != nullptr) {
jit->GetCodeCache()->MoveObsoleteMethod(this, backup_method);
}
// 5. 設(shè)置被 hook 函數(shù)的入口點(diǎn)(關(guān)鍵的 hook 邏輯)
// 將 hook 信息存到 entry_point_from_jni 這個(gè)指針
SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
// 設(shè)替換函數(shù)入口點(diǎn) entry_point_from_quick_compiled_code_ 為自己的 art_quick_proxy_invoke_handler
SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
// 設(shè)置函數(shù)在 CodeItem 偏移
SetCodeItemOffset(0);
// 更改屬性并添加 kAccXposedHookedMethod 標(biāo)記
const uint32_t kRemoveFlags = kAccNative | kAccSynchronized | kAccAbstract | kAccDefault | kAccDefaultConflict;
SetAccessFlags((GetAccessFlags() & ~kRemoveFlags) | kAccXposedHookedMethod);
MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation, this);
}
這里面涉及太多 art
相關(guān)知識(shí)扔罪,暫時(shí)沒能力細(xì)究,簡單說一下诸蚕,其實(shí)就是找到被 hook
函數(shù)的地址值步势,然后替換成另外一個(gè)函數(shù),這樣當(dāng)函數(shù)執(zhí)行時(shí)會(huì)先執(zhí)行 callback
的 beforeHookedMethod()
背犯,然后執(zhí)行被 hook
函數(shù)坏瘩,最后執(zhí)行 callback
的afterHookedMethod()
。
Xposed Module
下面再說說如何編寫一個(gè) Xposed Module
漠魏,編寫 Xposed Module
其實(shí)只需要四步即可倔矾。
添加對(duì) Xposed 庫的依賴
在 app/build.gradle
中添加對(duì) Xposed
庫的依賴。
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
注意這里依賴的方式為 compileOnly
柱锹,不知道為什么的往前看 loadModule()
哪自。
增加 Xposed 配置
在 AndroidManifest.xml
中添加 Xposed
相關(guān)配置。
<!-- 標(biāo)志該 apk 為一個(gè) Xposed 模塊禁熏,供 Xposed 框架識(shí)別-->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!--模塊說明壤巷,一般為模塊的功能描述-->
<meta-data
android:name="xposeddescription"
android:value="這個(gè)模塊是用來檢測(cè)用戶隱私合規(guī)的,在用戶未授權(quán)同意前瞧毙,調(diào)用接口獲取信息屬于違規(guī)" />
<!--模塊兼容版本-->
<meta-data
android:name="xposedminversion"
android:value="54" />
編寫 hook 插件
一般情況下會(huì)編寫類實(shí)現(xiàn) IXposedHookLoadPackage
胧华,并在 handleLoadPackage()
做相關(guān)操作。
public class HookTrack implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
// ... 在這里進(jìn)行相關(guān)操作
}
增加 xposed_init 配置
在 assets
目錄下新增 xposed_init
配置文件宙彪,并添加插件類的全路徑聲明矩动。
這樣 Xposed Module
就編寫完了。