# EdXposed例子

## 背景

"纸上得来终觉浅，绝知此事要躬行"，尤其看代码的时候经常觉得都懂了，自己写得时候全忘了。所以最好的记忆还是自己写个例子。所以学习EdXposed的时候写了个例子把整个流程走一遍。

## 依赖

需要完成安装Magisk和EdXposed

## 例子说明

EdXposed完全Follow Xposed的API，所以整个开发流程完全一致。 从本质上来讲，EdXposed 模块也是一个 Android 程序。但与普通程序不同的是，想要让写出的Android程序成为一个Xposed 模块，要额外多完成以下四个硬性任务：

1. 让手机上的xposed框架知道我们安装的这个程序是个xposed模块。
2. 模块里要包含有xposed的API的jar包，以实现下一步的hook操作。
3. 这个模块里面要有对目标程序进行hook操作的方法。
4. 要让手机上的xposed框架知道，我们编写的xposed模块中，哪一个方法是实现hook操作的。

对应上面的四个步骤我们需要做的修改有：

1. AndroidManifest.xml
2. XposedBridgeApi-xx.jar 与 build.gradle
3. 实现hook操作的具体代码
4. xposed\_Init

### 目标APP

首先我们实现一个目标App， App的功能很简单，只有一个button，点击button返回一个字符串

```java
package com.wq.xposedtargetapp;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.util.concurrent.ExecutionException;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, getIpAddr(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    public String getIpAddr() {
        return "192.168.180.123";
    }
}
```

![目标APP Hook前](/files/-MPP44sN6DXaI5viJW0a)

### Hook App

另外实现一个Hook App用于替换点击按钮后的返回值，我们按照上面说的四步一步一步修改：

#### AndroidManifest.xml

```markup
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wq.xpasedmodule">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <!--表示当前app是一个xposedmodule-->
        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <!--当前module的描述-->
        <meta-data
            android:name="xposeddescription"
            android:value="edxposed test" />
        <!--edxposed 最低版本-->
        <meta-data
            android:name="xposedminversion"
            android:value="53" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>
```

当修改完`AndroidManifest.xml`后, EdXposedManager就会显示当前模块

![EdXposed Manager](/files/-MPP44sPqD2BUr4XfkwZ)

#### Xposed API build.gradle修改

[Xposed API](https://api.xposed.info/reference/packages.html)说明

```java
dependencies {
    //指定Xposed API
    compileOnly 'de.robv.android.xposed:api:82'
    compileOnly 'de.robv.android.xposed:api:82:sources'

    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}
```

#### 实现Hook具体实现代码

```java
package com.wq.xpasedmodule;

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 EdXposedHookTest implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        // 指定Hook目标app
        if (loadPackageParam.packageName.equals("com.wq.xposedtargetapp")) {
            // 指定Hook的目标类
            Class clazz = loadPackageParam.classLoader.loadClass("com.wq.xposedtargetapp.MainActivity");
            // 查找Hook的方法
            XposedHelpers.findAndHookMethod(clazz, "getIpAddr", new XC_MethodHook() {
                // 具体执行逻辑
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    // 替换getIpAddr方法的返回值
                    param.setResult("111.111.111.111");
                }
            });
        }
    }
}
```

#### xposed\_init指定Hook模块

```
com.wq.xpasedmodule.EdXposedHookTest
```

## Hook 结果

![目标APP Hook后](/files/-MPP44sQHva0w5YrJrFp)

**上面的例子可以在**[**Github**](https://github.com/sphantix/EdXposed-Test)**上找到**


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sphantix-hang.gitbook.io/timebook/5.-ji-shu-pian/android-ji-shu/hook/edxposed_example.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
