一、前言
在上篇文章源码编译(2)——Xopsed 源码编译详解中具体介绍了 Xposed 源码编译的残缺过程,本文将从 Android 编译过程到 Xposed 运行机制,最初进行 Xposed 框架的具体定制。其中 Xposed 的定制次要参考世界美景大佬的定制 Xposed 框架和肉丝大佬的来自高纬的反抗:魔改 XPOSED 过框架检测(下)。
致谢:
首先感激世界美景大佬的定制 Xposed 框架,从外面学习到对 Xposed 框架特色的批改,然而因为集体程度无限,大佬的贴子不够具体,不能残缺复现,通过搜寻发现肉丝大佬的基于此的两篇具体的贴子解说:来自高纬的反抗:魔改 XPOSED 过框架检测 (上) 和来自高纬的反抗:魔改 XPOSED 过框架检测(下),本文的 Android 零碎运行参考老罗的博客
二、Android 运行机制
咱们在理解 Xposed 的运行机制前,不得不须要理解 Android 零碎的根本构造和运行机制,这样咱们能力进一步学习如何进行 Xposed 定制,能力缩小更多的谬误
1. Android 平台架构
Android 的平台架构如下图所示:
上面咱们顺次介绍各层之间的性能和作用:
(1)Linux 内核
Android 平台的根底是 linux 内核,Android Runtime(ART)依附 Linux 内核来执行底层性能,应用 Linux 内核可让 Android 利用次要平安性能,并且运行设施制造商为驰名的内核开发硬件驱动程序,能够了解基于 linux 内核让 Android 更平安并且能够领有很多设施驱动
(2)硬件形象层(HAL)
HAL 提供规范界面,向更高级别 Java API 框架显示设施硬件性能,HAL 蕴含多个模块,其中每个模块都为特定类型的硬件组件实现一个界面,例如相机和蓝牙模块,当框架 API 要拜访设施硬件时,Android 零碎为该硬件组件加载库模块。
(3)Android Runtime
Android 5.0 之前 Android Runtime 为 Dalvik,Android 5.0 之后 Android Runtime 为 ART
首先咱们先理解一些文件的含意:
(1)dex 文件:Android 将所有的 class 文件打包造成一个 dex 文件,是 Dalvik 运行的程序(2)odex 文件:优化过的 dex 文件,Apk 在装置时会进行验证和优化,通过 dexopt 生成 odex 文件,放慢 Apk 的响应工夫(3)oat 文件:Android 公有 ELF 文件格式,有 dex2oat 解决生成,蕴含(原 dex 文件 +dex 翻译的本地机器指令),是 ART 虚拟机应用的文件,能够间接加载(4)vdex 文件:Android 8.0 引入,蕴含 APK 的未压缩 DEX 代码,以及一些旨在放慢验证速度的元数据
上面咱们从 Android 零碎的倒退过程中具体介绍二者的区别:
版本 | 虚拟机类型 | 个性 |
---|---|---|
2.1-4.4 | Dalvik | JIT+ 解释器 |
5.0-7.0 | ART | AOT |
7.0-11 | ART | AOT+JIT+ 解释器 |
上面局部参考博客:博客地址
Android 2.2
Dalvik
反对已转换成 dex 格局的 android 利用,基于寄存器,指令执行更快,加载的是 odex 文件,采纳 JIT 运行时编译
JIT:
JIT 即运行时编译策略,能够了解成一种运行时编译器,此时 Android 的虚拟机应用的是 Dalvik,为了放慢 Dalvik 虚拟机解释 dex 速度,运行时动静地将执行频率很高的 dex 字节码翻译老本地机器码
毛病:(1)每次启动利用都须要从新编译(2)运行时比拟耗电,造成电池额定的开销
基于 Dalvik 的虚拟机,在 APK 装置时会对 dex 文件进行优化,产生 odex 文件,而后在启动 APP 后,运行时会利用 JIT 即时编译,解决执行频率高的一部分 dex,将其翻译成机器码,这样在再次调用的时候就能够间接运行机器码,从而进步了 Dalvik 翻译的速率,进步运行速度
毛病:(1)因为在 Dex 加载时会触发 dexopt , 导致 Multidex 加载的时候会十分慢(2)因为热点代码的 Monitor 始终在运行 , 解释器解释的字节码会带来 CPU 和工夫的耗费, 会带来电量的损耗
Android 4.4——ART 和 AOT
此时引入全新的虚拟机运行环境 ART 和全新的编译策略 AOT,此时 ART 和 Dalvik 是共存的,用户能够在两者之间抉择
Android 5.0——ART 全面取代 Dalvik
AOT:
AOT 是一种运行前编译的策略
毛病:(1)利用装置和系统升级之后的利用优化比拟耗时(2)优化后的文件会占用额定的存储空间
AOT 与 JIT 区别:
JIT 是在运行时进行编译,是动静编译,并且每次运行程序的时候都须要对 odex 从新进行编译
AOT 是动态编译,利用在装置的时候会启动 dex2oat 过程把 dex 预编译成 ELF 文件,每次运行程序的时候不必从新编译,是真正意义上的本地利用
JVM、Dalvik 和 ART 区别:
JVM:传统的 Java 虚拟机、基于栈、运行 class 文件
Dalvik: 反对已转换成 dex 格局的 android 利用,基于寄存器,指令执行更快, 加载的是 odex(优化的 dex)ART: 第一次装置时,将 dex 进行 Aot(预编译),字节码事后编译成机器码,生成可执行 oat 文件(ELF 文件)
基于 ART 的虚拟机,会在 APK 第一次装置时,将 dex 进行 AOT(预编译),通过 dex2oat 生成 oat 文件,即 Android 可执行 ELF 文件,包含原 dex 文件和翻译后的机器码,而后启动程序后,间接运行
毛病:(1)因为装置 APK 时触发 dex2oat , 须要编译成 native code , 导致安装时间过长(2)因为 dex2oat 生成的文件较大 , 会占用较多的空间
Android 7.0——JIT 回归
思考下面 AOT 的毛病,dex2oat 过程比拟耗时且会占用额定的存储空间,Android 7.0 再次退出 JIT 造成 AOT+JIT+ 解释器
模式
特点:
(1)利用在装置的时候 dex 不会被编译(2)利用在运行时 dex 文件先通过解析器(Interpreter)后会被间接执行,与此同时,热点函数(Hot Code)会被辨认并被 JIT 编译后存储在 jit code cache 中并生成 profile 文件以记录热点函数的信息(3)手机进入 IDLE(闲暇)或者 Charging(充电)状态的时候,零碎会扫描 App 目录下的 profile 文件并执行 AOT 过程进行编译
混合编译模式综合了 AOT 和 JIT 的各种长处,使得利用在装置速度放慢的同时,运行速度、存储空间和耗电量等指标都失去了优化
最初咱们能够看下 Android 各版本 ClassLoader 加载 dex 时的 dexopt 过程:
(4)原生 C /C++ 库
许多外围 Android 零碎组件和服务(例如 ART 和 HAL)构建自原生代码,须要以 C 和 C++ 编写的原生库。Android 平台提供 Java 框架 API 以向利用显示其中局部原生库的性能,咱们能够通过 NDK 开发 Android 中的 C /C++ 库
(5)Java API 框架
通过以 Java 语言编写的 API 应用 Android OS 的整个功能集。这些 API 造成创立 Android 利用所需的构建块,它们可简化外围模块化零碎组件和服务的重复使用,包含以下组件和服务
2. Android 启动流程
Android 启动流程如下图所示:
(1)Loader
Boot ROM: 当手机处于关机状态时,长按 Power 键开机,疏导芯片开始从固化在 ROM 里的预设代码开始执行,而后加载疏导程序到 RAM
Boot Loader:这是启动 Android 零碎之前的疏导程序,次要是查看 RAM,初始化硬件参数,拉起 Android OS
咱们长按电源键后,手机就会在 Loader 层加载疏导程序,并启动疏导程序,初始化参数
(2)Linux 内核
(1)启动 Kernel 的 swapper 过程(pid=0):该过程又称为 idle 过程, 零碎初始化过程 Kernel 由无到有创始的第一个过程, 用于初始化过程治理、内存治理,加载 Display,Camera Driver,Binder Driver 等相干工作,这些模块驱动都会封装到对应的 HAL 层中
(2)启动 init 过程(用户过程的祖宗)。pid = 1,用来孵化用户空间的守护过程、HAL、开机动画等
(3)启动 kthreadd 过程(pid=2):是 Linux 零碎的内核过程,会创立内核工作线程 kworkder,软中断线程 ksoftirqd,thermal 等内核守护过程。kthreadd 过程是所有内核过程的鼻祖
(3)执行 init 过程
init 过程是 Linux 零碎中用户空间的第一个过程,过程号为 1,是所以用户过程的先人
Linux Kernel 实现零碎设置后,会首先在零碎中寻找 init.rc 文件,并启动 init 过程,init.rc 脚本寄存门路: /system/core/rootdir/init.rc
,init 过程:/system/core/init
init 过程的启动能够分为三个局部:
(1)init 过程会孵化出 ueventd、logd、healthd、installd、adbd、lm 这里写代码片 kd 等用户守护过程(2)init 过程还会启动 ServiceManager(Binder 服务管家)、bootanim(开机动画)等重要服务(3)解析 init.rc 配置文件并孵化 zygote 过程,Zygote 过程是 Android 零碎的第一个 java 过程(虚拟机过程),zygote 过程是所以 Java 过程的父过程
创立 Zygote 过程:
(1)解析 init.zygote.rc //parse_service()(2)启动 main 类型服务 //do_class_start()(3)启动 zygote 服务 //service_start()(4)创立 Zygote 过程 //fork()(5)创立 Zygote Socket //create_socket()
(4)Zygote
Zygote 为孵化器,即所有 Android 利用的先人,Zygote 让 VM 共享代码、低内存占用以及最小的启动工夫成为可能,Zygote 是一个虚拟机过程,Zygote 是由 init 过程通过解析 init.zygote.rc
文件而创立的,zygote 所对应的可执行程序app_process
,所对应的源文件是App_main.cpp
,过程名为 zygote
Zygote 作用过程:
(1)解析 init.zygote.rc 中的参数,创立 AppRuntime 并调用 AppRuntime.start()办法;(2)调用 AndroidRuntime 的 startVM()办法创立虚拟机,再调用 startReg()注册 JNI 函数;(3)通过 JNI 形式调用 ZygoteInit.main(),第一次进入 Java 世界;(4)registerZygoteSocket()建设 socket 通道,zygote 作为通信的服务端,用于响应客户端申请;(5)preload()预加载通用类、drawable 和 color 资源、openGL 以及共享库以及 WebView,用于进步 app 启动效率;(6)zygote 结束大部分工作,接下来再通过 startSystemServer(),fork 得力帮手 system_server 过程,也是下层 framework 的运行载体。(7)zygote 功成身退,调用 runSelectLoop()(死循环),随时待命,当接管到申请创立新过程申请时立刻唤醒并执行相应工作。
Android 零碎流程总结:
(1) 手机开机后,疏导芯片启动,疏导芯片开始从固化在 ROM 里的预设代码执行,加载疏导程序到到 RAM,BootLoader 查看 RAM,初始化硬件参数等性能;(2) 硬件等参数初始化实现后,进入到 Kernel 层,Kernel 层次要加载一些硬件设施驱动,初始化过程治理等操作。在 Kernel 中首先启动 swapper 过程(pid=0),用于初始化过程治理、内管治理、加载 Driver 等操作,再启动 kthread 过程(pid=2), 这些 linux 零碎的内核过程,kthread 是所有内核过程的鼻祖;(3) Kernel 层加载结束后,硬件设施驱动与 HAL 层进行交互。初始化过程治理等操作会启动 init 过程,这些在 Native 层中;(4) init 过程 (pid=1,init 过程是所有过程的鼻祖,第一个启动) 启动后,会启动 adbd,logd 等用户守护过程,并且会启动 servicemanager(binder 服务管家)等重要服务,同时孵化出 zygote 过程,这里属于 C ++ Framework,代码为 C ++ 程序;(5) zygote 过程是由 init 过程解析 init.rc 文件后 fork 生成,它会加载虚拟机,启动 System Server(zygote 孵化的第一个过程);SystemServer 负责启动和治理整个 Java Framework,蕴含 ActivityManager,WindowManager,PackageManager,PowerManager 等服务;(6) zygote 同时会启动相干的 APP 过程,它启动的第一个 APP 过程为 Launcher,而后启动 Email,SMS 等过程,所有的 APP 过程都有 zygote fork 生成。
三、Xposed 框架运行机制
咱们从上文中曾经具体介绍了 Android 的启动流程,如下图所示:
上面咱们来具体介绍 Xposed 框架的实现原理:
1. Xposed 实现原理
咱们先进入 Xposed 官网,能够发现 Xposed 工程的 5 个文件夹:
具体的模块性能和作用,咱们在上文源码编译(2)——Xopsed 源码编译详解曾经介绍了,大家能够去参考
上文中咱们晓得,Xposed 集成到 Android 源码中次要是:
(1)替换了 Android 中的虚拟机 art(2)替换了 app_process 以及生成的一些 lib,bin 文件等
具体如下图所示
咱们从上文中能够得悉 Android 所有的用户过程都是通过 Zygote 孵化进去的,而 Zygote 的执行程序是 app_process,装置 Xposed,将 app_process 替换,而后替换对应的虚拟机,这样 Zygote 孵化器就变成了 Xposed 孵化器
咱们先来剖析一下替换后的app_process
:
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
LOCAL_SRC_FILES := app_main2.cpp
LOCAL_MULTILIB := both
LOCAL_MODULE_STEM_32 := app_process32_xposed
LOCAL_MODULE_STEM_64 := app_process64_xposed
else
LOCAL_SRC_FILES := app_main.cpp
LOCAL_MODULE_STEM := app_process_xposed
endif
...
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
include frameworks/base/cmds/xposed/ART.mk
else
include frameworks/base/cmds/xposed/Dalvik.mk
endif
程序通过判断 SDK
的版本抉择加载文件,这里咱们是在 Android 6.0 上运行,SDK 版本为 23,因而 app_process
会执行 app_main2.cpp
和ART.mk
为了不便咱们剖析,这里我画了一个思维导图不便大家联合前面源码剖析:
咱们再剖析 app_main2.cpp 中 main 函数:
int main(int argc, char* const argv[])
{if (xposed::handleOptions(argc, argv))
return 0;
// 代码省略...
runtime.mParentDir = parentDir;
// 初始化 xposed,次要是将 jar 包增加至 Classpath 中
isXposedLoaded = xposed::initialize(zygote, startSystemServer, className, argc, argv);
if (zygote) {
// 如果 xposed 初始化胜利,将 zygoteInit 替换为 de.robv.android.xposed.XposedBridge,而后创立虚拟机
runtime.start(isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit",
startSystemServer ? "start-system-server" : "");
}
...
}
main 函数次要做了两件事:(1)初始化 xposed
(2)创立虚拟机
初始化 xposed:
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
...
// 初始化 xposed 的相干变量
xposed->zygote = zygote;
xposed->startSystemServer = startSystemServer;
xposed->startClassName = className;
xposed->xposedVersionInt = xposedVersionInt;
...
// 打印 release、sdk、manufacturer、model、rom、fingerprint、platform 相干数据
printRomInfo();
// 次要在于将 jar 包退出 Classpath
return addJarToClasspath();}
(1)初始化 xposed 内相干变量(2)调用 addJarToClasspath 将 XposedBridge.jar 增加至系统目录
创立虚拟机:
void AndroidRuntime::start(const char* className, const Vector<String8>& options)
{
/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
// 创立虚拟机
if (startVm(&mJavaVM, &env) != 0) {return;}
// 初始化虚拟机,xposed 对虚拟机进行批改
onVmCreated(env);
// 虚拟机初始化实现后,会调用传入的 de.robv.android.xposed.XposedBridge 类,初始化 java 层 XposedBridge.jar
char* slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {...} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
...
}
}
(1)创立虚拟机(2)初始化虚拟机 xposed 对虚拟机进行批改, onVmCreated(env)(3)传入调用类 de.robv.android.xposed.XposedBridge(4)初始化 XposedBridge
这里咱们能够参考老罗的源码剖析:
咱们能够发现,咱们在初始化虚拟机后,xposed 会对虚拟机批改,函数onVmCreated(env)
onVmCreated(env):
void onVmCreated(JNIEnv* env) {
// Determine the currently active runtime
if (!determineRuntime(&xposedLibPath))
...
// Load the suitable libxposed_*.so for it 通过 dlopen 加载 libxposed_art.so
void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW);
...
// Initialize the library 初始化 xposed 相干库
bool (*xposedInitLib)(XposedShared* shared) = NULL;
*(void **) (&xposedInitLib) = dlsym(xposedLibHandle, "xposedInitLib");
if (!xposedInitLib) {ALOGE("Could not find function xposedInitLib");
return;
}
...
// xposedInitLib -> onVmCreatedCommon -> initXposedBridge -> 注册 Xposed 相干 Native 办法
if (xposedInitLib(xposed)) {xposed->onVmCreated(env);
}
}
咱们来剖析xposedInitLib
libxposed_art.cpp#xposedInitLib
libxposed_common.cpp#onVmCreatedCommon
libxposed_common.cpp#initXposedBridge
libxposed_art.cpp#onVmCreated
onVmCreated 总结:
(1)通过 dlopen 加载 libxposed_art.so(2)初始化 xposed 相干库 xposedInitLib(3)xposedInitLib->onVmCreatedCommon->initXposedBridge,初始化 XposedBridge,将 register_natives_XposedBridge 中的函数注册为 Native 办法(4)xposedInitLib->onVmCreatedCommon->onVmCreated,为 xposed_callback_class 与 xposed_callback_method 赋值
de.robv.android.xposed.XposedBridge#main
protected static void main(String[] args) {
// Initialize the Xposed framework and modules
try {if (!hadInitErrors()) {initXResources();
SELinuxHelper.initOnce();
SELinuxHelper.initForProcess(null);
runtime = getRuntime();
XPOSED_BRIDGE_VERSION = getXposedVersion();
if (isZygote) {XposedInit.hookResources();
XposedInit.initForZygote();}
XposedInit.loadModules();} else {Log.e(TAG, "Not initializing Xposed because of previous errors");
}
}
// Call the original startup code
if (isZygote) {ZygoteInit.main(args);
} else {RuntimeInit.main(args);
}
}
源码剖析:
虚拟机初始化实现后,会调用传入的 de.robv.android.xposed.XposedBridge 类,初始化 java 层 XposedBridge.jar,调用 main 函数(1)hook 系统资源相干的办法 XposedInit.hookResources()(2)hook zygote 的相干办法 XposedInit.initForZygote()(3)加载零碎中曾经装置的 xposed 模块 XposedInit.loadModules()
到此 Xposed 初始化完结
2. Xposed hook 原理
通过上文的详细分析,咱们能够得出 Xposed 的 hook 原理:
Xposed 的基本原理是批改了 ART/Davilk 虚拟机,将须要 hook 的函数注册为 Native 层函数。当执行到这一函数是虚构机会优先执行 Native 层函数,而后再去执行 Java 层函数,这样实现函数的 hook
启动过程总结:
(1)手机启动时 init 过程会启动 zygote 这个过程。因为 zygote 过程文件 app_process 已被替换,所以启动的时 Xposed 版的 zygote 过程(2)Xposed_zygote 过程启动后会初始化一些 so 文件(system/lib system/lib64),而后进入 XposedBridge.jar 中的 XposedBridge.main 中初始化 jar 包实现对一些要害 Android 零碎函数的 hook(3)Hook 则是利用批改过的虚拟机将函数注册为 native 函数(4)而后再返回 zygote 中实现本来 zygote 须要做的工作
咱们对 Xposed 的基本原理和 hook 原理就根本把握了,大家都晓得咱们这应用 Xposed 时,须要一直的去重启手机和勾选咱们装置的模块,为了方便使用,这里补充两个技巧,咱们理解 Xposed 源码后,就能够很不便实现了
(1)勾销重启手机
咱们先察看上文 XposedBridge 中 main
下面的 XposedInit.loadModules()这个函数,这个函数的作用就是 load hook 模块到过程中,因为 zygote 启动时先跑到 java 层 XposeBridge.main 中,在 main 外面有一步操作是将 hook 模块 load 进来,模块加载到 zygote 过程中,zygote fork 所有的 app 过程外面也有这个 hook 模块,所以这个模块能够 hook 任意 app。
编写 hook 模块的第一步就是判断以后的过程名字,如果是要 hook 的过程就 hook,不是则返回,所以批改模块后,要将模块从新 load zygote 外面必须重启 zygote,要想 zygote 重启就要重启手机了。
解决办法:
所以批改的逻辑是不把模块 load 到 zygote 外面,而是 load 到本人想要 hook 的过程外面,这样批改模块后只需重启该过程即可
步骤:
(1)将下面 XposedInit.loadModules()正文掉即可(2)在 2 处批改代码
if (isZygote) {
XposedHelpers.findAndHookMethod("com.android.internal.os.ZygoteConnection", BOOTCLASSLOADER, "handleChildProc",
"com.android.internal.os.ZygoteConnection.Arguments",FileDescriptor[].class,FileDescriptor.class,
PrintStream.class,new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// TODO Auto-generated method stub
super.afterHookedMethod(param);
String processName = (String) XposedHelpers.getObjectField(param.args[0], "niceName");
String coperationAppName = "指定过程名称如:com.android.settings";
if(processName != null){if(processName.startsWith(coperationAppName)){log("--------Begin Load Module-------");
XposedInit.loadModules();}
}
}
});
ZygoteInit.main(args);
} else {RuntimeInit.main(args);
}
咱们只须要将咱们的模块过程指定,这样就不必每次开机都重启那
(2)勾销操作 Installer APP
通过浏览源码,咱们发现读 Install App 的源码发现其实勾选 hook 模块其实 app 就是把模块的 apk 地位写到一个文件里,等 load 模块时会读取这个文件,从这个文件中的 apk 门路下把 apk load 到过程中
loadmodules 的源码:
apk 配置文件就是 installer app 文件门路下的 conf/modules.list
这个文件data/data/de.robv.android.xposed.installer/conf/modules.list
或者data/user_de/0/de.robv.android.xposed.installer/conf/modules.list
所以咱们勾选一个文件,理论是将其写到 conf/modules.list
文件下,此时咱们发现 Xposed 中还有一个办法loadModule
这个办法能够依据具体的门路和类加载器,间接导入模块,所以只有咱们在下面代码中批改一些,就能够间接导入,不须要勾选那,咱们确定 apk 门路:pathclass = "/data/local/tmp/module.apk"
和类加载器为根类加载器
if (isZygote) {
XposedHelpers.findAndHookMethod("com.android.internal.os.ZygoteConnection", BOOTCLASSLOADER, "handleChildProc",
"com.android.internal.os.ZygoteConnection.Arguments",FileDescriptor[].class,FileDescriptor.class,
PrintStream.class,new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// TODO Auto-generated method stub
super.afterHookedMethod(param);
String processName = (String) XposedHelpers.getObjectField(param.args[0], "niceName");
String coperationAppName = "指定过程名称如:com.android.settings";
if(processName != null){if(processName.startsWith(coperationAppName)){log("--------Begin Load Module-------");
String pathclass = "/data/local/tmp/module.apk";
// 留神这里是 loadModule 办法, 类加载器是咱们的根类加载器
XposedInit.loadModule(pathclass,BOOTCLASSLOADER);
}
}
}
});
ZygoteInit.main(args);
} else {RuntimeInit.main(args);
}
四、Xposed 框架特色
本节参考世界美景大佬定制 Xposed 框架和肉丝大佬来自高纬的反抗:魔改 XPOSED 过框架检测(下),通过咱们上文的 Android 运行机制和 Xposed 框架运行机制解说,置信大家对 Xposed 的框架曾经有了进一步的意识,这样咱们再来看这篇帖子里的批改的特色,就显得非常清晰了
五、Xposed 特色批改
1. XposedInstaller
咱们下载 XposedInstaller 的工程代码加载到 AndroidStudio 中,让 XposedInstaller 配置环境,谬误提醒解决见上文源码编译(2)——Xopsed 源码编译详解
批改点:
(1)批改整体包名(2)批改 xposed.prop
(1)批改整体包名
先来改下整体的包名,首先将目录折叠给勾销掉
而后咱们在包名门路中,将 xposed 改成 xppsed,这样能够保障包名长度是一样,同时 xposed 特色隐没不见,抉择 Refactor→Rename
咱们间接点击 Refactor,就能够替换胜利,点击 Preview,则须要再点击 Do Refactor
这时候咱们能够发现程序下的包名都扭转了
接下来就是在整个我的项目的根文件夹下,进行整体的包名替换,因为还有很多编译配置、或者门路配置等等,须要进行包名的更换
在 app 文件夹右击,抉择 Replace in Path
把所有的 de.robv.android.xposed.installe
都改成de.robv.android.xpsed.installer
搜进去匹配的中央只有 5 个文件中共计的 7 处中央,并不多,间接 replace All 替换即可
(2)xposed.prop
改成xpsed.prop
就是把如下图处的 xposed.prop
改成 xppsed.prop
即可
接下来就是编译了。编译时先 Build→Clean 一下,而后再 Build→Make Project,这样就间接编译通过了。能够连贯到手机,刷到手机下来,App 会被装在手机上,然而无奈主动启动,得手动点开
2. XposedBridge
(1)批改整体包名
首先是改包名,办法与上文截然不同,也是首先将 xposed 进行重构,改成 xppsed
注:咱们发现很多类型须要手动批改包名,咱们顺次将包名批改,这里咱们发现重构后没反馈,很可能是 Android Studio 的问题,换一个版本或重启
而后也是一样的在我的项目根目录下,执行 Replace in Path,将所有的 de.robv.android.xposed.installer
都改成de.robv.android.xppsed.installer
这里就批改实现
(2)生成文件
XposedBridge.jar:
先 Make Clean 一下,而后编译,将编译进去的文件复制一份,命名为 XppsedBridge.jar 即可
api.jar:
而后咱们在 Gradle->others->generate API 中生成 api.jar,保留在 build/api 下
3. Xposed
Xposed 中的文件须要批改的中央不少:
(1)libxposed_common.h
批改之前:
批改之后:
(2)Xposed.h
批改之前:
#define XPOSED_PROP_FILE "/system/xposed.prop"
#define XPOSED_LIB_ART XPOSED_LIB_DIR "libxposed_art.so"
#define XPOSED_JAR "/system/framework/XposedBridge.jar"
#define XPOSED_CLASS_DOTS_ZYGOTE "de.robv.android.xposed.XposedBridge"
#define XPOSED_CLASS_DOTS_TOOLS "de.robv.android.xposed.XposedBridge$ToolEntryPoint"
批改之后:
#define XPOSED_PROP_FILE "/system/xppsed.prop"
#define XPOSED_LIB_ART XPOSED_LIB_DIR "libxppsed_art.so"
#define XPOSED_JAR "/system/framework/XppsedBridge.jar“#define XPOSED_CLASS_DOTS_ZYGOTE "de.robv.android.xppsed.XposedBridge"
#define XPOSED_CLASS_DOTS_TOOLS "de.robv.android.xppsed.XposedBridge$ToolEntryPoint"
(3)xposed_service.cpp
批改之前:
IMPLEMENT_META_INTERFACE(XposedService, "de.robv.android.xposed.IXposedService");
批改之后:
IMPLEMENT_META_INTERFACE(XposedService, "de.robv.android.xppsed.IXposedService");
(4)xposed_shared.h
批改之前:
#define XPOSED_DIR "/data/user_de/0/de.robv.android.xposed.installer/"
#define XPOSED_DIR "/data/data/de.robv.android.xposed.installer/"
批改之后:
#define XPOSED_DIR "/data/user_de/0/de.robv.android.xppsed.installer/"
#define XPOSED_DIR "/data/data/de.robv.android.xppsed.installer/"
(5)ART.mk
批改之前:
libxposed_art.cpp
LOCAL_MODULE := libxposed_art
批改之后:
libxppsed_art.cpp
LOCAL_MODULE := libxppsed_art
(6)libxposed_art.cpp
将文件夹下的 libxposed_art.cpp 文件,重命名为 libxppsed_art.cpp
4. XposedTools
咱们在 XposedTools 中将 build.pl
和zipstatic/_all/META-INF/com/google/android/flash-script.sh
的字符替换就能够了
xposed.prop--->xppsed.prop
XposedBridge.jar--->XppsedBridge.jar
libxposed_art--->libxppsed_art
记得不要有脱漏,能够在批改完之后,到根目录下运行下述 grep 命令试试看,找不到相应的字符串即为全副替换实现
grep -ril xposed.prop
grep -ril "xposed.prop" . ## 过滤当前目录下含该字符串的文件
可是明明这里我是替换了的,我进入文件中也查找不到
通过剖析,咱们发现这里会将 xposed_prop 辨认为 xposed.prop,阐明咱们是替换实现了
5. 源码编译
源码编译流程,具体的参考上文
(1)这里咱们曾经替换了 art(2)记得将批改的 xposed 替换 /SourceCode/Android-6.0.1_r1/frameworks/base/cmds/` 文件夹下的 xposed 文件夹(3)还记得把编译进去的 XppsedBridge.jar 放到 $AOSP/out/java/ 目录中去噢,替换旧的 XposedBridge.jar
咱们再次输出编译指令:
./build.pl -t arm:23
编译胜利,生成文件:
咱们从新挪动生成文件到源码文件夹下,具体参考上文:
cp /home/tom/SourceCode/XposedBridge/sdk23/arm/files/system/bin/* .
cp /home/tom/SourceCode/XposedBridge/sdk23/arm/files/system/lib/* .
cp /home/tom/SourceCode/XposedBridge/sdk23/arm/files/system/xppsed.prop .
记得将 xposed 编译生成的 app_process32_xposed 替换 system/bin 文件夹下的 app_process32
而后咱们进入源码目录下,再次编译镜像:
source build/envsetup.sh
lunch 19
make snod //make snod 命令的作用是从新生成镜像文件 system.img
6. 后果与验证
(1)后果
咱们将镜像刷入手机:
fastboot flash system system.img
而后重启,发现 Xposed 装置胜利
(2)验证
咱们下载 XposedCheck,这里咱们从官网下载源码,用 Android Studio 关上,而后编译装置,发现咱们定制 Xposed 框架胜利
7. 谬误
(1)谬误 1
问题剖析:
这是咱们没有替换 xposed 导致的
问题解决:
咱们将批改的 Xposed 替换原来 /SourceCode/Android-6.0.1_r1/frameworks/base/cmds/
文件夹下的 xposed 文件夹
(2)谬误 2
问题剖析:
这是咱们的 Xposed 文件夹首字母为大写导致
问题解决:
咱们将其首字母改为小写
(3)谬误 3
谬误剖析:
这是 xposedinstaller 和 XposedBridge 版本不统一导致
问题解决:
匹配具体参考上文
(4)谬误 4
谬误剖析:
谬误剖析:
通过重复的查看,最初原来是 XposedBridge.jar 编译的问题
问题解决:
因为咱们编译过程中,将这里给正文掉了 所以导致并没导入 Android6.0 的环境反对,咱们须要退出反对,具体见上文
六、试验总结
通过几天的学习,解决完许许多多的 bug,从源码剖析到源码定制,花了一周终于将这几篇文章写完了,从中播种了很多,这两头参考了很多大佬的文章,在文中和参考文献中会一一列进去,如果其中还存在一些问题,就请各位大佬斧正了。
七、参考文献
Android 源码剖析:
https://juejin.cn/post/6844903748058218509、https://www.jianshu.com/p/2c0b76d0f4f2
https://www.jianshu.com/p/8bb770ec4c48
https://www.javatt.com/p/42078
https://blog.csdn.net/luoshengyang/article/details/8852432
https://blog.csdn.net/luoshengyang/article/details/8885792
https://www.jianshu.com/p/89d06f626540
https://www.wuyifei.cc/dex-vdex-odex-art/
https://skytoby.github.io/2019/Android%20dex%EF%BC%8Codex%EF%BC%8Coat%EF%BC%8Cvdex%EF%BC%8Cart%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84/
https://cloud.tencent.com/developer/article/1755790
https://www.kancloud.cn/alex_wsc/androids/473620
Xposed:
https://zhuanlan.zhihu.com/p/389889716
https://www.bbsmax.com/A/MAzAq2Ynz9/
https://www.cnblogs.com/baiqiantao/p/10699552.html
https://www.jianshu.com/p/6b4a80654d4e
http://www.uml.org.cn/mobiledev/201903052.asp
https://bbs.pediy.com/thread-255836.htm
https://mp.weixin.qq.com/s/YAMCrQSi0LFJGNIwB9qHDA
本文由平安后厨团队分享,转载请注明起源,违者必究!