Xpoded 模块开发教程

翻译:https://github.com/rovo89/XposedBridge/wiki/Development-tutorial


Xpoded模块开发教程

当然,你可以去学习如何创建一个Xposed模块。所以你可以阅读这篇教程(官方教程)去学习怎样解决这个问题。这不仅仅讲解如何新建模块、如何编写模块,我们要往更深处思考,为什么按照这些步骤,为什么要新建这个类。如果你是“TL博士”那样的人,那么可以直接阅读"Making the project an Xposed module这一章节。如果你想看完整个教程那么你需要很好的理解能力。你将会花费时间去阅读这篇文章,因为你不能但靠自己解决任何的问题。

一、修正通告

你可以在Github中找到重新创建红色时钟的例子,它包括状态栏时钟的颜色改为红色和添加一个笑脸,我选择这个例子,因为它是一个相当小,但很容易看到变化。此外,它使用了一些由框架提供的基本方法。

二、Xposed是如何工作的

在你修改之前,你应该思考一下Xposed是如何工作的(如果你觉得太无聊可以跳过这一节)。方法如下:

有个进程叫做“Zygote ”。这是Android运行环境的的核心。每一个应用程序启动都是通过它fork出来的。当手机被启动就会执行/init.rc这个脚本,这个进程会启动/system/bin/app_process ,然后他会调加载所需的类并调用初始化函数。 

现在说说Xposed什么时候开始启动。当您安装了Xposed,它会把修改的app_process可执行文件复制到/system/bin中。这个修改过的启动进程增加了一个额外的jar包到classpath路径,并在某些地方调用那里的方法。例如,在虚拟机刚刚创建后,在Zygote main方法调用后。我们的jar包也可以Zygote里面工作。

这个jar包位于:/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar和它的源代码可以在这里找到(here)。综观类XposedBridge,你可以看到的main 方法。这就是我上面写的,这个类会在进程启动之前被调用。在那时候执行一些初始化和模块的加载(我会在后面讲解模块的加载)。

三、Method hooking/replacing

Xposed真正的能力在于可以hook函数的调用。当您修改完反编译后的APK,你可以直接插入命令或者修改命令。但是您将需要重新编译/签名APK,并且重新安装这个安装包。你可以用Xposedhooks,你不用修改内部函数的任何代码(这无法清晰地定义什么叫“没有修改”这个词语)。相反,你可以注入java里最小的单元(每一句代码)到函数的前面和后面,这可以清晰地解决问题。

XposedBridge有一个私有的本地方法叫hookMethodNative。此方法同样的会在扩展的app_process进程中应用。它会将方法类型改变为“native 并且将这方法的实现替换为它自己本地的泛型方法。这意味着任何时候调用这个被hook的方法,方法调用者并不知道已经被native 方法给代替了java层的方法。在该方法中,在XposedBridge的方法handleHookedMethod会被调用,用来传递需要的参数和自身的引用。并且这个方法负责调用给这个函数注册过的回调方法。他可以改变传进来的参数,可以更改实例变量和静态变量、可以调用其他方法、处理一下返回值,或者跳过任何你想跳过的代码。这是非常灵活的。

好了,理论讲解完了。现在,让我们创建一个模块!

四、创建项目

模块是正常的应用程序,只需用一些特殊的元数据和文件。因此,首先创建一个新的Android项目。我假设你之前已经做到了这一点。如果不是,官方文档非常详细。当被问及对于SDK中,我选择了4.0.3API15)。我建议你试试这个,因为已经做过测试了。你不需要创建一个activity ,因为修改没有任何用户界面。回答完这个问题之后,你应该有一个空白的项目。

五、Making the project an Xposed module

现在我们开始讲解如何加载Xposed 模块 。需要按以下几个步骤来进行的。

1.AndroidManifest.xml

Xposed Installer根据模块列表查找模块程序的详细信息是通过meta-data这个标签的。您可以这样创建它:AndroidManifest.xml => Application => Application Nodes (在底部) => Add => Meta Data。这个名字应该是xposedmodule和值为true。让资源为空。然后重复相同的操作填写xposedminversion(见下文)和xposeddescription(你的模块的一个非常简短的描述)。 XML源代码现在看起来像这样:

 

2.XposedBridgeApi.jar

接下来,让项目认识XposedBridge API 。你可以从这里下载XposedBridgeApi-<version>.jar 。把它复制到子文件夹名为lib目录下。然后在其上单击右键,选择Build Path => Add to Build Path 。你需要将版本名插入到xposedminversion的声明清单中。

可以选择库引用的方式。但是确保你的API类被正确地编译到APK文件中 ,否则你会得到一个IllegalAccessError 。通过引用libs 文件(有“s),通过Eclipse的简单设置可以不用把XposedBridgeApi-<version>.jar 包含进去。

3.模块的实现

现在,您可以创建一个类的模块。我的被命名为“Tutorial ”,并在包de.robv.android.xposed.mods.tutorial里面:

 

用于第一步骤中,我们将只做一些记录以显示被加载的模块。一个模块可以有几个入口点。选择哪一个取决于您要修改的内容。您可以在Android系统启动时、当一个app将要被加载时、一个app的资源将要被初始化时,通过Xposed调用你模块中的一个函数。

本教程往下一点 ,您将学习必要的更改需要在一个特定的应用程序中完成,所以让我们一起去看看“当一个新的应用程序被载入的时候”的入口点。所有入口点都标有IXposedMod的子接口。在这种情况下,你需要实现IXposedHookLoadPackage这个函数。它实际上只是一个方法加上一个参数,它提供了有关环境的实现模块的更多信息。在我们的例子中,我们记录的加载应用程序的名称:

 

这个log方法将日志输出到标准logcat窗口中和/data/data/de.robv.android.xposed.installer/log/debug.log文件中。(通过它很容易了解Xposed Installer 

4.assets/xposed_init

现在需要关心的事情是仍然丢失了XposedBridge 的一些类包含的入口点的追踪。这是通过一个名为xposed_init的文件,在assets  文件夹中用它的名字创建一个新的文本文件。在这个文件中,每一行包含一个完全限定的类名,就像这样,这里的是:de.robv.android.xposed.mods.tutorial.Tutorial

六、Trying it out

保存文件。然后运行你的项目为Android应用程序。因为这是你第一次安装它,在使用它之前您需要启用它。打开Xposed安装的应用程序,并确保你已经安装了框架。然后去了“模块”选项卡。你应该找到你的应用程序在里面。选中该复选框来启用它。然后重新启动。你不会看到有任何的变化,但如果你检查日志,你应该看到的东西是这样的:


瞧!它这工作了。现在你有一个Xposed模块。它仅仅只有写log的作用。。。

七、探索你的目标,找到一个方法来修改它

好了,现在开始这一话题,可以是非常不同,这取决于你想要做什么。如果你以前有修改过的APK,你可能知道我在说什么。在一般情况下,你首先需要获得有关目标的实现的一些细节。在本教程中,目标是在状态栏的那个时钟。它有助于知道状态栏和一部分SystemUI的细节。因此,让我们开始搜索。 

可能性之一:反编译。这将给你准确的实现 ,但是它是难以阅读和理解的,因为你得到smali格式的代码。可能性二:获取AOSP源( here )并且浏览他。但这官方ROM可能与你的不一样,但在这种情况下,它是一个类似甚至相同的实现。第一,我想看看AOSP,看看是否是一样的。如果我需要更多的细节,看看实际的反编译代码。 

你可以寻找与“时钟”类名称或包含该字符串的类。下一步就是,寻找他所使用的资源和布局。如果您下载了官方AOSP的代码,就可以开始在这里开始寻找:frameworks/base/packages/SystemUI 。你会发现不少地方出现“时钟”。这是正常的,的确会有不同的方式来实现修改。请记住,你仅仅可以hook方法 。所以,你必须要找到一个可以在他之前、之后、或全部替换可以插入一些代码的地方。你应该hook 住尽可能具体的方法,而不是那些会被调用上千次的方法,去避免性能问题和意想不到的副作用。 

在这种情况下,您可能会发现这个layout布局res/layout/status_bar.xml  包含了一个自定义视图类:com.android.systemui.statusbar.policy.Clock。多个想法可能会现在你的头脑中。文字颜色的定义是通过textAppearance属性,所以最简单的方法就是改变它,将会改变外观的定义 。然而,这可能有效也可能无效(因为它可能存在于更深的native  代码中)。更换布局状态栏将是可能的,但是你们只可以做最小的变化去更改他,相反,看看这个类。有一个叫updateClock方法,它看上去会被每分钟调用去更新时间

 

看起来完美的修改,因为它是这似乎是唯一设置文本时钟的非常具体的方法。假如我们改变了这个clock的颜色或者字体,那么任何调用这个方法的都会受此影响。就达成我们的需求了,我们立刻行动.

(单独的文本颜色,这里有一种更好的方式.看到“修改布局”的例子在 "Replacing resources".)

八、使用反射来查找和hook一个方法

什么是我们已经知道的?我们找到一个方法:updateClock com.android.systemui.statusbar.policy.Clock,我们要拦截它。我们发现这个类是在SystemUI sources 里面,所以它只能对SystemUI的进程有效。修改一些其他的归属于框架的类是会任何地方都有效的。如果我们试图获得任何信息和直接引用此类在handleLoadPackage 方法中,这会失败的,因为它们在不同的进程中。所以,当一个指定的包将被加载时,让我们来实现一个执行特定代码的条件。

 

使用参数,我们可以很容易地检查,我们是否选中正确的包。一旦我们证实,我们可以从包中获得这个类通过也是从这个变量引用的类加载器。现在我们来看看在com.android.systemui.statusbar.policy.Clock类及其updateClock方法,可以告诉XposedBridge把它hook了:


  findAndHookMethod helper 类的一个方法。请注意,它是静态导入的,如果你配置了它描述的链接页面就会自动添加 。此方法通过ClassLoader  ClassLoader  包中查找Clock类 。然后,它会在里面寻找updateClock方法。如果这种方法有任何参数,那你就必须列出这些参数的类型。不同的情况不一样的处理,但我们的方法没有任何参数,可以跳过这个假设。作为最后一个参数,你需要提供XC_MethodHook类的实现。对于较小的改动,就可以使用一个匿名类。如果你有太多的代码,最好创建一个普通的类,只在这里创建实例。随后,helper 将尽一切方法hook住以上的函数。 

你可以重写XC_MethodHook的两个方法。您可以同时覆盖,甚至不做操作,但后者是完全没有意义的。这两个方法是beforeHookedMethodafterHookedMethod。这不是太难猜测,这两个方法会在原始的方法的之前和之后执行。您可以使用beforeHookedMethod 方法来评价/篡改方法调用的参数(通过param.args) ,甚至阻止调用原来的方法(发送自己的结果)。afterHookedMethod 方法可以用来做基于原始方法的结果的事情。您还可以用它来操纵结果 。当然,你可以添加自己的代码,它将会准确地在原始方法的前或后执行。

(如果你想完全取代方法,看看子类XC_MethodReplacement相反,你只需要覆盖replaceHookedMethod 

XposedBridge保留着一个记录了每个已经hook了的函数的注册回调函数 的列表。那些具有最高优先级(如hookMethod定义)会首先调用。原始方法始终是优先级最低的。所以,假如你hook了一个函数并注册了回调APRIO高点)和BPRIO默认值),那么每当hook的方法被调用,控制流将是这样的:A.before - > B.before - >原始的方法 - > B.after - > A.after。因此,A修改了的参数,B是可以看到的,这样可以在传递给原始方法之前多步地改变它。原方法的结果首先会被B处理,但是这个原始方法最终返回的结果是由A来决定的。

九、最后一个步骤:执行自己的代码在方法调用之前/之后 

好了,你现在有一个每次updateClock  调用时,都会被调用的方法,而且可以精确到原始方法的前后(你已经在SystemUI  的进程里面了)。现在,让我们来修改一些东西。 

首先要检查:我们有没有得到具体的时钟对象?是的,我们有,它在param.thisObject参数里。因此,如果该方法被myClock.updateClock()调用,然后param.thisObject将会使myClock这个对象。 

 

下一步:我们可以做什么用的时钟?这个Clock  类是不可以利用的,你可以不转换param.thisObject变成类(甚至不要去尝试)。然而,它继承自TextView的。所以,你可以使用像的setTextgettextsetTextColor的方法,一旦你已经把Clock引用映射成TextView。这些改变应该在原始方法调用后去设置新的时间。由于在方法调用前没有事做,我们就不考虑 beforeHookedMethod。调用 (empty) "super" 方法是没有必要的。所以不要重写这方法。

这是完整的源代码 :

 

十、对结果满意 

现在安装/重新启动您的应用程序。正如你在运行之前已经在XposedInstaller  启用了它,你就不需要再来一次了,重新启动就足够了。不过,如果你想使用它停用这个红色时钟的例子。两者都使用缺省的优先级给他们的updateClock处理程序,那么你不知道哪一个会胜出(它实际上取决于处理方法的字符串表示形式,但并不依赖于此)。

十一、结论

我知道,这个教程很长。但我希望你现在不仅可以实现一个绿色的时钟,还可以实现和这个完全不同的东西。找到好的方法来hook是一个经验上的问题,所以开始的东西比较容易。尝试刚开始就多使用日志功能去确保被调用的是预期的事件。现在:玩得开心!


  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值