0*00 前言
在互联网上, 关于 Xposed 模块编写的教程可谓是一抓一大把. 但由于时间的推移, 很多工具和方法都发生了变化 (如 Eclipse 退出安卓编程舞台, AndroidStudio 不断升级导致其一些设置也随之变化等) 也正因此, 网上的教程往往有一些时限性, 比如现如今 provide 这个关键字已经被舍弃了却仍有人在用, 还有些说要把 jar 包放到 lib 文件夹而非 libs 文件夹...... 种种错误或者落伍的教程对新手产生了很大的误导. 笔者近日收到过朋友初学 Xposed 模块编写时的求助, 看了一些他找的参考教程, 觉得多多少少都存在点问题, 因此萌生了写一篇关于在最新 AndriodStudio 开发环境下实现 Xposed 模块开发入门的文章.
0*01 Xposed 模块编写简介
Xposed 框架的原理就不多说了, 它部署在 ROOT 后的安卓手机上, 通过替换 / system/bin/app_process 程序控制 zygote 进程, 使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 jar 包, 从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持. 可以让我们在不修改 APK 源码的情况下, 通过自己编写的模块来影响程序运行的框架服务, 实现类似于自动抢红包, 微信消息自动回复等功能.
其实, 从本质上来讲, Xposed 模块也是一个 Android 程序. 但与普通程序不同的是, 想要让写出的 Android 程序成为一个 Xposed 模块, 要额外多完成以下四个硬性任务:
, 让手机上的 xposed 框架知道我们安装的这个程序是个 xposed 模块.
, 模块里要包含有 xposed 的 API 的 jar 包, 以实现下一步的 hook 操作.
, 这个模块里面要有对目标程序进行 hook 操作的方法.
, 要让手机上的 xposed 框架知道, 我们编写的 xposed 模块中, 哪一个方法是实现 hook 操作的.
这就引出我即将要介绍的四大件(与前四步一一对照):
,AndroidManifest.xml
,XposedBridgeApi-xx.jar 与 build.gradle
, 实现 hook 操作的具体代码
,xposed_Init
牢记以上四大件, 按照顺序一个一个实现, 就能完成我们的第一个 Xposed 模块编写. 下面我们就开始吧!
0*02 迈开第一步, 新建项目并编辑 AndroidManifest.xml
1, 首先打开 AndroidStudio(以版本 3.1 为例, 还在用老版本的请升级), 建立一个工程, 提示我们选择 "Activity", 那就选一个 Empty Activity 吧.(这个是模块的界面, 随意选择即可).
2, 我们可以把项目查看方式设置为 Project 模式, 以方便查看. 然后在 "项目名称 / app/src/main/" 目录下找到 AndroidManifest.xml, 双击之, 并在指定位置插入以下三段代码:
- <meta-data
- Android:name="xposedmodule"
- Android:value="true" />
- <meta-data
- Android:name="xposeddescription"
- Android:value="这是一个 Xposed 例程" />
- <meta-data
- Android:name="xposedminversion"
- Android:value="53" />
插入位置及代码说明如图所示:
插入之后, 如果你把手机连上 AndroidStudio , 点击 "编译" 或者 "运行" 的话, 手机就会启动刚刚编写的这个程序. 而在手机里的 Xposed 框架中也会显示出这个模块:
说明 Xposed 框架已经认出了我们写的程序. 但先别高兴太早 -- 虽然框架已经觉得他是一个 Xposed 模块了, 但我们自己心里清楚, 这个模块还啥都不会干呢. 下一步, 我们让这个模块长点本事.
0*03 走出第二步, 搞定 XposedBridgeApi-xx.jar 与 build.gradle
我们知道, Xposed 模块主要功能是用来 Hook 其他程序的各种函数. 但是, 如何让前一步中的那个 "一穷二白" 的模块长本事呢? 那就要引入 XposedBridgeApi.jar 这个包, 你可以理解为一把兵器, 模块有了这把宝刀才能施展出 Hook 本领. 以前, 都需要手动下载诸如 XposedBridgeApi-54.jar , XposedBridgeApi-82.jar 等 jar 包, 然后手工导入到 libs 目录里, 才能走下一步道路. 其实在 AndroidStudio 3.1 里面, 我们完全不用这么麻烦, 只需要多写一行代码, 就让 AndroidStuido 自动给我们配置 XposedBridgeApi.jar ! 下面操作开始(序号接着上一节):
3, 在 "项目名称 / app/src/main/" 目录下找到 build.gradle, 在图示位置加上:
- repositories {
- jcenter()
- }
以及
- compileOnly 'de.robv.android.xposed:api:82'
- compileOnly 'de.robv.android.xposed:api:82:sources'
这句代码是告诉 AndroidStuido 使用 jcenter 作为代码仓库, 从这个仓库里远程寻找 de.robv.Android.xposed:API:82 这个 API. 这个网上很少有 Xposed 教程介绍它的!(我们不用自己找 XposedBridgeApi.jar 了. 注意! 此处要用 compileOnly 这个修饰符! 网上有些写的是 provide , 现在已经停用了!)如图:
写完之后, build.gradle 会提示文件已经修改, 是否同步. 点击 "sync now", 同步即可:
[ps: 如果网络不通, 或者同步不畅, 就不要进行第三步的 repositories { jcenter()}这个步骤了, 改做这个步骤:]
手动下载 XposedBridgeApi-82.jar , 拖放到 "项目名称 / app/libs/" 里面(不是网上说的单独建立 lib 文件夹, 那是很久以前的故事了!), 然后右键 "Add As Library" 自行添加这个 jar 包. 而 compileOnly 'de.robv.android.xposed:api:82′和 compileOnly'de.robv.Android.xposed:API:82:sources'这两句仍然照常添加.
好了, 现在宝刀已经到手. 下一步, 就要开始 "施展刀法"(编写 hook 代码)了.
0*04 迈开第三步, 实现 hook 操作的具体代码
4, 在 "施展刀法"(编写 hook 代码)之前, 我们先要立一个靶子. 在界面上画一个按钮, 并在 MainAcitiviy 里写代码如下:
- package com.example.root.xposd_hook_new;
- import Android.support.v7.App.AppCompatActivity;
- import Android.os.Bundle;
- import Android.view.View;
- import Android.widget.Button;
- import Android.widget.Toast;
- public class MainActivity extends AppCompatActivity {
- private Button button;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- button = (Button) findViewById(R.id.button);
- button.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- Toast.makeText(MainActivity.this, toastMessage(), Toast.LENGTH_SHORT).show();
- }
- });
- }
- public String toastMessage() {
- return "我未被劫持";
- }
- }
这个靶子很简单: MainActivity 界面有个按钮, 点击按钮后会弹出一个 toast 提示, 该提示的内容由 toastMessage() 方法提供, 而 toastMessage()的返回值为 "我未被劫持":
下面我们正式开始 "施展刀法"(编写 hook 代码) 来 hook 我们的 MainActivity 并修改这个类的 toastMessage()方法, 让它的返回值为 "你已被劫持":
5, 在 MainActivity 的同级路径下新建一个类 "HookTest.java", 代码如下:
- package com.example.root.xposd_hook_new;
- import de.robv.Android.xposed.IXposedHookLoadPackage;
- import de.robv.Android.xposed.XC_MethodHook;
- import de.robv.Android.xposed.XposedBridge;
- import de.robv.Android.xposed.XposedHelpers;
- import de.robv.Android.xposed.callbacks.XC_LoadPackage;
- public class HookTest implements IXposedHookLoadPackage {
- public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
- if (loadPackageParam.packageName.equals("com.example.root.xposd_hook_new")) {
- XposedBridge.log("has Hooked!");
- Class clazz = loadPackageParam.classLoader.loadClass(
- "com.example.root.xposd_hook_new.MainActivity");
- XposedHelpers.findAndHookMethod(clazz, "toastMessage", new XC_MethodHook() {
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
- super.beforeHookedMethod(param);
- //XposedBridge.log("has Hooked!");
- }
- protected void afterHookedMethod(MethodHookParam param) throws Throwable {
- param.setResult("你已被劫持");
- }
- });
- }
- }
- }
由代码可知, 我们是通过 IXposedHookLoadPackage 接口中的 handleLoadPackage 方法来实现 Hook 并篡改程序的输出结果的. 代码中 "com.example.root.xposd_hook_new" 是目标程序的包名,"com.example.root.xposd_hook_new.MainActivity" 是想要 Hook 的类, "toastMessage" 是想要 Hook 的方法. 我们在 afterHookedMethod 方法 (用来定义 Hook 了目标方法之后的操作) 中, 修改了 toastMessage()方法的返回值为 "你已被劫持".
OK, 以上用来 hook 的代码编写完毕, 让我们进行下一步操作.
0*05 最后一步, 添加入口点
右键点击 "main" 文件夹 , 选择 new -> Folder ->Assets Folder, 新建 assets 文件夹:
然后右键点击 assets 文件夹, new-> file, 文件名为 xposed_init(文件类型选 text), 并在其中写上入口类的完整路径(就是自己编写的那一个 Hook 类), 这样, Xposed 框架就能够从这个 xposed_init 读取信息来找到模块的入口, 然后进行 Hook 操作了:
好了, 曙光就在前面! 最后选择禁用 Instant Run: 单击 File -> Settings -> Build, Execution, Deployment -> Instant Run, 把勾全部去掉.
然后点击小三角 "运行"! 在 Xposed 框架里找到自己写的模块, 打上勾, 重启 -- 点开自己的程序看看, 是不是 toast 的提示已经变了?
来源: http://www.tuicool.com/articles/YvqQRfa