第一个Xposed插件

本篇记载我搭建Xposed开发环境到第一个Xposed插件的过程。

Xposed原理概述:

Android系统中,一个App的运行要借助于虚拟机进程,因为我们都知道Android系统的底层其实是由C++编写的Linux,那么这样的系统如何运行我们使用Java编写的上层App呢?很简单,为每一个App提供一个虚拟机进程,App在虚拟机中运行就没有问题了。Android中有一个特殊的进程负责创建这些虚拟机进程,它就是Zygote进程,Zygote进程在创建时。每一次创建一个虚拟机进程都是从Zygote进程fork出来,学过操作系统的同学都知道所谓的fork创建就是去拷贝一份进程的备份,因此每一个虚拟机进程都会享有这个Zygote进程的所有资源,比如运行时库,因此只要能修改Zygote进程,理论上就能控制所有其他应用进程,Xposed正是通过修改Zygote进程来完成劫持的。

注:这里的虚拟机进程指的是运行了一个虚拟机实例的进程,而Java程序就在这个虚拟机实例中运行。我简单的理一下这个关系:Zygote进程->拷贝一份应用进程->拥有一个虚拟机实例->运行Java程序。通过这样一种方式,Android系统只需要对虚拟机初始化一次,以后每一次创建虚拟机只用拷贝就完成了,且对于静态库,所有虚拟机与Zygote进程共享,这也可以节省内存。

Xposed的最终效果是实现对方法的劫持,也就是我们可以对任何一个方法的内容进行重写。

1.在手机上安装Xposed。

安装Xposed是个非常麻烦的事情,我的测试机是红米2 miUI7稳定版,我本来想在这台测试机上安装Xposed,但是这台手机没有root也没有合适的rom,如果要装在这台手机上可能要浪费大量的时间去刷机,最后我选择放弃在实机上安装,转而在Genymotion下的虚拟机上安装。

我虚拟机配置:

Google Nexus 6 - 5.0.0 API 21

Genymotion上的虚拟机自带root授权管理,我在root权限下安装。

Xposed有两个部分:

(1)XposedInstall—一个apk应用,这个东西是负责与用户交互和管理Xposed插件的Android应用,这个东西安装起来是很容易的,使用Genymotion只需要把apk拖入虚拟机窗口就能自行识别。

(2)Xposed SDK—实现root的底层库,这个东西是一个zip文件,需要以Recovery的方式刷上手机,且官方对Recovery的工具有明确的要求,不能使用源生的工具,也就是说你得先刷上要求的Recovery工具,非常的麻烦。幸运的是Genymotion下有很简单的工具在虚拟机环境下帮助我们快速刷机,GenyFlash,使用这个工具可以直接像安装apk一样刷机,很方便的就能装上SDK。

2.在Mac下配置Xposed插件开发环境

我起初以为开发Xposed的插件会非常的麻烦,因为它可能不是标准的Android工程,不能通过Android Studio这样IDE的编译。后来试了一下,其实很简单,开发Xposed插件就和一般的App没有什么两样,它只要求你mainfest和一些文件里多加少许识别的标记。

Xposed插件本身就是一个Android App,我们之前安装的XposedInstall就专门管理这批App,在编写插件的时候可以把App本身的启动icon设置为不显示,这样就只能在XposedInstall里找到它。

Xposed的开发和普通的App是一样的,因此我们只需要添加Xposed插件需要的依赖就行了。

我在Mac下的Android Studio中编写Xposed插件,因此我使用Gradle添加依赖如下:

app/build.gradle

provided 'de.robv.android.xposed:api:82'

这个东西好像是会引用本机上安装的sdk,但是我现在也搞不太清楚,目前先记住它是我们项目需要的依赖就够了。

3.Xposed Hello World

下面开始第一个Xposed插件的编写:

为了更直观的看到Xposed的作用,我们跳过官方文档提供的最简单的simple,直接编写第一个Hook插件。

首先创建一个简单的工程,它将被Xposed劫持并修改。

因为这个工程的内容很简单,我只贴Acitivity里面的代码:

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
26
package com.zuliangwang.test;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

final TextView textView = (TextView) findViewById(R.id.test_text);
Button button = (Button) findViewById(R.id.test_button);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText("GG");
}
});
}
}

一个TextView和一个Button,当点击Button,TextView的内容将被修改。

我们的目的就是劫持这个TextView的setText方法,修改传入的参数,最后的text将不是“GG”

接下来创建一个Xposed插件工程。

首先修改AndroidMainfest的内容:

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
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.zuliangwang.firstxposed"
xmlns:android="http://schemas.android.com/apk/res/android">


<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">


<!-- 是否是xposed模块,xposed根据这个来判断是否是模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" />


<!-- 模块描述,显示在xposed模块列表那里第二行 -->
<meta-data
android:name="xposeddescription"
android:value="测试Xposed模块" />


<!-- 最低xposed版本号(lib文件名可知) -->
<meta-data
android:name="xposedminversion"
android:value="30" />



<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

这个玩意是给XposedInstall提供的本插件的描述。

接下来在main文件夹下创建assets文件夹,在里面创建一个简单的文本文件xposed_init,这个名字是有要求的。

1
com.zuliangwang.firstxposed.DIAO

这个文件里只有一行代码,是一个类的名字,它告诉xposed我们这个插件的执行入口。

接下来就在这个类里编写具体的逻辑:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.zuliangwang.firstxposed;

import android.widget.TextView;

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;

/**
* Created by zuliangwang on 16/11/27.
*/


public class DIAO implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
XposedBridge.log("begin");

if (loadPackageParam.packageName.equals("com.zuliangwang.test")){
XposedBridge.log("hint");


XposedHelpers.findAndHookMethod(TextView.class, "setText", CharSequence.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("before");
param.args[0] = "修改了";
}

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("after");
}
});

}


}
}

通过Xposed提供的方法,我们可以修改另一个Android App的方法的参数,现在编译这个工程,你会在XposedInstall里看到这个新的插件。点击运行第一个Test工程,你可以看到结果如下:

result

可以看到,这个App的逻辑被我们写的插件给劫持了!

第一个插件到此完成。

XposedInstall和XposedSdk 传送门

GenyFlash 传送门

XposedBridge官方文档 传送门