环境:
已root手机一枚(Android 4.4.4)
Android Studio一枚

官方文档参考这里.

准备工作

我们需要事先下载一个Xposed installer安装在手机上,用来管理所有的模块.

安装完成后打开:

点击框架,

点击安装/更新安装框架,

点击确定重启,框架界面是这样的:

编写新模块

打开android studio,新建工程,选择Add no activity

新建完成后,找到app目录下的build.gradle文件,将dependencies中的

1
compile fileTree(dir: 'libs', include: ['*.jar'])

改为:

1
provided fileTree(dir: 'libs', include: ['*.jar'])

下载XposedBridgeApi-54.jar并放入app目录下的libs文件夹.

AndroidManifest.xml文件的application中添加如下代码,其中的54是前面下载的文件中的号码.

1
2
3
4
5
6
7
8
9
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="kiya's test module" />
<meta-data
android:name="xposedminversion"
android:value="54" />

新建一个Test类,写入:

1
2
3
4
5
6
7
8
9
10
package space.kiya.xposedtest;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class Test implements IXposedHookLoadPackage{
@Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
XposedBridge.log("loaded: " + loadPackageParam.packageName);
}
}

新建assets文件夹,在里面新建文件名为xposed_init,写入刚刚的类名,此处应为space.kiya.xposedtest.Test.

这时就可以编译安装了.

测试

因为工程没有activity,所以在桌面上看不到该应用。
来到xposed installer模块中,可以看到我们的模块出现在这里,现在勾选它:

在重启使之生效之前,我们在logcat新建一个tag为Xposed的过滤器,这样就可以过滤出模块输出的log.
大概是这样的:

1
2
3
4
5
Loading Xposed v54(for Zygote)...
Loading modules from /data/app/space.kiya.xposedtest-1.apk
Loading class space.kiya.xposedtest.Test
Loaded: android
...

这样的日志在xposed installer日志中也是可以看到的.

可能出现的错误

如果输出的log中出现了如下错误:

1
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation

说明是前面步骤中没有修改build.gradle导致的.

具体原因见另一篇博客.

xposed怎样工作?

开机时,./init.rc脚本文件会启动Zygote进程,Zygote对应的具体程序是/system/bin/app_process,然后加载需要的类,调用初始化的方法,之后启动的每个应用都是Zygote的拷贝,所以Zygote进程是十分重要的.

通过在类路径中添加一个jar包,在app_process的特定位置调用jar包中的方法,Xposed框架实现了带扩展功能的app_process,然后将原有的app_process替换掉.
/data/data/de.robv.android.xposed.installer/bin/目录下有一个XposedBridge.jar文件,它就是被引用的jar包,源码在github,main函数在/src/de/robv/android/xposed/XposedBridge.java中,每个进程每次启动时都会被调用.加载模块的功能也是在这里实现.

Xposed真正强大的是它可以hook调用的方法.当你反编译修改apk时,你可以在里面插入xposed的命令,于是你就可以在方法调用前后注入自己的代码.

XposedBridge有一个私有的本地方法hookMethodNative,代码实现放在app_process中.在调用被hook的方法前会先调用此方法,hookMethodNative有一个handleHookedMethod方法,可以修改传递给被hook函数的参数,变量甚至是调用其他方法.

修改

以上仅仅是证明了我们的xposed模块是可以工作的.现在来修改点什么,比如状态栏上的时间?

在安装源码下的/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java文件中有一个updateClock函数用来更新时间.

1
2
3
4
5
final void updateClock() {
if (mDemoMode) return;
mCalendar.setTimeInMillis(System.currentTimeMillis());
setText(getSmallTime());
}

在代码中使用findAndHookMethod函数找到这一函数,并在updateClock函数执行后将TextView的内容重新设置即可.注意在使用前需要这样将其导入:import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;.
我们的效果是将时间后面加上一个笑脸,颜色设为红色.
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package space.kiya.xposedtest;
import android.graphics.Color;
import android.widget.TextView;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class Test implements IXposedHookLoadPackage{
@Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if(loadPackageParam.packageName.equals("com.android.systemui")){
findAndHookMethod("com.android.systemui.statusbar.policy.Clock", loadPackageParam.classLoader, "updateClock", new XC_MethodHook() {
@Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
TextView tv = (TextView)param.thisObject;
String text = tv.getText().toString();
tv.setText(text + ":)");
tv.setTextColor(Color.RED);
}
});
}
}
}

效果如下: