├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── xposed_init
│ ├── java
│ └── top
│ │ └── sacz
│ │ └── hook
│ │ ├── DexkitTest.kt
│ │ ├── HookSteps.java
│ │ ├── InjectHook.java
│ │ ├── SimpleConfig.java
│ │ ├── activity
│ │ └── ModuleActivity.java
│ │ └── ext
│ │ └── StringExt.kt
│ └── res
│ ├── drawable
│ ├── ic_launcher_background.xml
│ └── ic_launcher_foreground.xml
│ ├── layout
│ └── activity_module.xml
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── values-night
│ └── themes.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
├── build.gradle
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── settings.gradle
└── xphelper
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
└── java
└── top
└── sacz
└── xphelper
├── XpHelper.java
├── activity
├── ActivityProxyManager.java
├── BaseActivity.java
└── replace
│ ├── IActivityManagerHandler.java
│ ├── ProxyHandler.java
│ └── ProxyInstrumentation.java
├── base
├── BaseDexQuery.java
└── BaseFinder.java
├── dexkit
├── ClassFinder.java
├── DexFinder.kt
├── FieldFinder.java
├── MethodFinder.java
├── bean
│ ├── ClassInfo.kt
│ ├── FieldInfo.kt
│ └── MethodInfo.kt
└── cache
│ ├── DexKitCache.java
│ └── DexKitCacheProxy.java
├── exception
└── ReflectException.java
├── ext
└── MemberExt.kt
├── reflect
├── ClassUtils.java
├── ConstructorUtils.java
├── FieldUtils.java
├── Ignore.java
└── MethodUtils.java
└── util
├── AESHelper.java
├── ActivityTools.java
├── CheckClassType.java
├── ConfigUtils.kt
├── DexFieldDescriptor.java
└── DexMethodDescriptor.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 | .idea/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### XPHelper 快速开发XPosed模块
2 |
3 | ## 仓库最新版本 [](https://jitpack.io/#suzhelan/XpHelper)
4 | ---
5 |
6 | ## [文档](https://github.com/suzhelan/XPHelper/wiki)
7 |
8 | ---
9 |
10 | ### 包含的功能
11 |
12 | - Field Method Constructor的快速查找与调用(带缓存)
13 | - 启动未注册在manifest清单的Activity
14 | - 往宿主APP注入模块的Res资源
15 | - 快速使用Dexkit来查找方法和字段 并自动缓存方法与回收内存 调用方便(使用缓存模式 已搭配FastKV性能绝佳) (https://github.com/suzhelan/XPHelper/wiki/Dexkit)
16 | - 附带FastKV(Java编写,兼容性更好,传闻比MMKV效率更高)存储框架的封装类 (https://github.com/suzhelan/XPHelper/wiki/Config)
17 | - 超多便捷方法拓展 (https://github.com/suzhelan/XPHelper/wiki/Kotlin%E2%80%90Ext(%E6%8B%93%E5%B1%95))
18 | 有什么问题或者建议可以留言issue
19 |
20 | ----
21 | ### 参考
22 | [EzxHelper](https://github.com/KyuubiRan/EzXHelper)
23 | [DexKit](https://github.com/LuckyPray/DexKit)
24 | [FastKV](https://github.com/BillyWei01/FastKV)
25 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace 'top.sacz.hook'
8 | compileSdk 35
9 |
10 | defaultConfig {
11 | applicationId "top.sacz.hook"
12 | minSdk 26
13 | targetSdk 35
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | aaptOptions {
21 | additionalParameters '--allow-reserved-package-id', '--package-id', '0xf2'
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_11
32 | targetCompatibility JavaVersion.VERSION_11
33 | }
34 | kotlinOptions {
35 | jvmTarget = '11'
36 | }
37 | }
38 |
39 | dependencies {
40 |
41 | implementation libs.appcompat
42 | implementation libs.material
43 | implementation libs.core.ktx
44 | compileOnly libs.xposed.api
45 | var dialogXVersion = "0.0.50.beta32"
46 | //引入DialogX主体
47 | implementation("com.github.suzhelan.DialogX:DialogX:$dialogXVersion")
48 | //非必须 DialogX官方提供的主题样式
49 | implementation("com.github.suzhelan.DialogX:DialogXMaterialYou:$dialogXVersion")
50 | implementation project(":xphelper")
51 | // implementation 'com.github.suzhelan:XpHelper:v1.2'
52 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
15 |
18 |
21 |
24 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/assets/xposed_init:
--------------------------------------------------------------------------------
1 | top.sacz.hook.InjectHook
--------------------------------------------------------------------------------
/app/src/main/java/top/sacz/hook/DexkitTest.kt:
--------------------------------------------------------------------------------
1 | package top.sacz.hook
2 |
3 | import android.util.Log
4 | import top.sacz.xphelper.reflect.ClassUtils
5 |
6 | class DexkitTest {
7 | fun hook() {
8 | startFindMethod()
9 | }
10 |
11 | private fun startFindMethod() {
12 | val classTest = ClassUtils.findClass("Ljava.lang.String")
13 | Log.d("FindXph", "startFindMethod: ${classTest.name} ")
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/top/sacz/hook/HookSteps.java:
--------------------------------------------------------------------------------
1 | package top.sacz.hook;
2 |
3 | import android.content.Context;
4 | import android.content.ContextWrapper;
5 |
6 | import com.kongzue.dialogx.DialogX;
7 | import com.kongzue.dialogxmaterialyou.style.MaterialYouStyle;
8 |
9 | import java.lang.reflect.Method;
10 |
11 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
12 |
13 | public class HookSteps {
14 |
15 | /**
16 | * 获取初始的Hook方法
17 | *
18 | * @param loadPackageParam
19 | * @return
20 | */
21 | public Method getApplicationCreateMethod(XC_LoadPackage.LoadPackageParam loadPackageParam) {
22 | try {
23 | String applicationName = loadPackageParam.appInfo.name;
24 | Class> clz = loadPackageParam.classLoader.loadClass(applicationName);
25 | try {
26 | return clz.getDeclaredMethod("attachBaseContext", Context.class);
27 | } catch (Throwable i) {
28 | try {
29 | return clz.getDeclaredMethod("onCreate");
30 | } catch (Throwable e) {
31 | try {
32 | return clz.getSuperclass().getDeclaredMethod("attachBaseContext", Context.class);
33 | } catch (Throwable m) {
34 | return clz.getSuperclass().getDeclaredMethod("onCreate");
35 | }
36 | }
37 | }
38 | } catch (Exception e) {
39 | try {
40 | return ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
41 | } catch (NoSuchMethodException ex) {
42 | return null;
43 | }
44 | }
45 | }
46 |
47 | public void initHook(Context context) {
48 | initDialogX(context);
49 | new DexkitTest().hook();
50 | }
51 |
52 | private void initDialogX(Context context) {
53 | DialogX.init(context);
54 | DialogX.globalTheme = DialogX.THEME.AUTO;
55 | DialogX.globalStyle = new MaterialYouStyle();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/top/sacz/hook/InjectHook.java:
--------------------------------------------------------------------------------
1 | package top.sacz.hook;
2 |
3 | import android.content.Context;
4 | import android.content.ContextWrapper;
5 |
6 | import java.lang.reflect.Method;
7 |
8 | import de.robv.android.xposed.IXposedHookLoadPackage;
9 | import de.robv.android.xposed.IXposedHookZygoteInit;
10 | import de.robv.android.xposed.XC_MethodHook;
11 | import de.robv.android.xposed.XposedBridge;
12 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
13 | import top.sacz.xphelper.XpHelper;
14 | import top.sacz.xphelper.dexkit.DexFinder;
15 |
16 | public class InjectHook implements IXposedHookLoadPackage, IXposedHookZygoteInit {
17 |
18 | public static final String TAG = "XpHelper";
19 | public static XC_LoadPackage.LoadPackageParam loadPackageParam;
20 | private final HookSteps hookSteps = new HookSteps();
21 |
22 | @Override
23 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadParam) throws Throwable {
24 | if (loadParam.isFirstApplication) {
25 | loadPackageParam = loadParam;
26 | Method applicationCreateMethod = hookSteps.getApplicationCreateMethod(loadParam);
27 |
28 | XposedBridge.hookMethod(applicationCreateMethod, new XC_MethodHook() {
29 | @Override
30 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
31 | ContextWrapper context = (ContextWrapper) param.thisObject;
32 | entryHook(context.getBaseContext());
33 | }
34 | });
35 | }
36 | }
37 |
38 | @Override
39 | public void initZygote(StartupParam startupParam) throws Throwable {
40 | //初始化xphelper
41 | XpHelper.initZygote(startupParam);
42 | }
43 |
44 | private void entryHook(Context context) {
45 | //初始化context
46 | XpHelper.initContext(context);
47 | //进入自己的Hook逻辑
48 | hookSteps.initHook(context);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/top/sacz/hook/SimpleConfig.java:
--------------------------------------------------------------------------------
1 | package top.sacz.hook;
2 |
3 | import top.sacz.xphelper.util.ConfigUtils;
4 |
5 | public class SimpleConfig {
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/top/sacz/hook/activity/ModuleActivity.java:
--------------------------------------------------------------------------------
1 | package top.sacz.hook.activity;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.annotation.Nullable;
6 |
7 | import top.sacz.hook.R;
8 | import top.sacz.xphelper.activity.BaseActivity;
9 |
10 | public class ModuleActivity extends BaseActivity {
11 | @Override
12 | protected void onCreate(@Nullable Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 | setContentView(R.layout.activity_module);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/top/sacz/hook/ext/StringExt.kt:
--------------------------------------------------------------------------------
1 | package top.sacz.hook.ext
2 |
3 | import com.kongzue.dialogx.dialogs.PopTip
4 |
5 |
6 | fun String.showToast() {
7 | PopTip.show(this)
8 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_module.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suzhelan/XPHelper/5b3afacd1fe8be13eb4ac4870ad5e301dc65c894/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suzhelan/XPHelper/5b3afacd1fe8be13eb4ac4870ad5e301dc65c894/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | XpHelper
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.android.library) apply false
5 | alias(libs.plugins.kotlin.android) apply false
6 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.9.2"
3 | appcompat = "1.7.0"
4 | material = "1.12.0"
5 | fastkv = "2.6.0"
6 | fastjson2 = "2.0.57"
7 | kotlin = "2.1.20"
8 |
9 | coreKtx = "1.16.0"
10 |
11 | [libraries]
12 |
13 | appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
14 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
15 |
16 | #xposed api
17 | xposed-api = { module = "de.robv.android.xposed:api", version = "82" }
18 | dexkit = { module = "org.luckypray:dexkit", version = "2.0.3" }
19 |
20 | fastkv = { group = 'io.github.billywei01', name = 'fastkv', version.ref = "fastkv" }
21 | fastjson2 = { module = "com.alibaba.fastjson2:fastjson2", version.ref = "fastjson2" }
22 | core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
23 |
24 | [plugins]
25 | android-application = { id = "com.android.application", version.ref = "agp" }
26 | android-library = { id = "com.android.library", version.ref = "agp" }
27 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
28 |
29 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suzhelan/XPHelper/5b3afacd1fe8be13eb4ac4870ad5e301dc65c894/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Feb 01 20:08:17 CST 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk17
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | mavenLocal()
14 | maven { url = uri("https://maven.aliyun.com/repository/public/") }
15 | maven { url = uri("https://maven.aliyun.com/repository/google/") }
16 | maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin/") }
17 | maven { url = uri("https://jitpack.io") }
18 | maven { url = uri("https://api.xposed.info/") }
19 | }
20 | }
21 | rootProject.name = "XPHelper"
22 | include ':app'
23 | include ':xphelper'
24 |
--------------------------------------------------------------------------------
/xphelper/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/xphelper/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | id 'maven-publish'
5 | }
6 |
7 | android {
8 | namespace 'top.sacz.xphelper'
9 | compileSdk 35
10 |
11 | defaultConfig {
12 | minSdk 26
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | publishing {
19 | singleVariant("release")
20 | }
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_11
29 | targetCompatibility JavaVersion.VERSION_11
30 | }
31 | kotlinOptions {
32 | jvmTarget = '11'
33 | }
34 | }
35 |
36 | dependencies {
37 | compileOnly(libs.xposed.api)
38 | compileOnly(libs.appcompat)
39 | implementation(libs.dexkit)
40 | implementation(libs.fastkv)
41 | implementation(libs.fastjson2)
42 | }
43 | afterEvaluate {
44 | // 官方建议使用上传方法
45 | publishing {
46 | publications {
47 | // Creates a Maven publication called "release".
48 | release(MavenPublication) {
49 | from components.release // 表示发布 release(jitpack 都不会使用到)
50 | groupId = 'com.github.suzhelan' //groupId 随便取 , 这个是依赖库的组 id
51 | artifactId = 'XPHelper' //artifactId 随便取 , 依赖库的名称(jitpack 都不会使用到)
52 | version = '2.2' // 当前版本依赖库版本号,这个jitpack不会使用到,只是我们开发者自己查看
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/xphelper/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suzhelan/XPHelper/5b3afacd1fe8be13eb4ac4870ad5e301dc65c894/xphelper/consumer-rules.pro
--------------------------------------------------------------------------------
/xphelper/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/xphelper/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/XpHelper.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 |
6 | import de.robv.android.xposed.IXposedHookZygoteInit;
7 | import top.sacz.xphelper.activity.ActivityProxyManager;
8 | import top.sacz.xphelper.dexkit.cache.DexKitCache;
9 | import top.sacz.xphelper.reflect.ClassUtils;
10 | import top.sacz.xphelper.util.ActivityTools;
11 | import top.sacz.xphelper.util.ConfigUtils;
12 |
13 | public class XpHelper {
14 | @SuppressLint("StaticFieldLeak")
15 | public static Context context;
16 | public static ClassLoader classLoader;
17 |
18 | public static String moduleApkPath;
19 |
20 | public static void initContext(Context application) {
21 | context = application;
22 | classLoader = application.getClassLoader();
23 | ClassUtils.intiClassLoader(classLoader);
24 | ConfigUtils.initialize(application);
25 | ActivityProxyManager.initActivityProxyManager(application);
26 | DexKitCache.checkCacheExpired(application);
27 | }
28 |
29 | public static void initZygote(IXposedHookZygoteInit.StartupParam startupParam) {
30 | moduleApkPath = startupParam.modulePath;
31 | }
32 |
33 | /**
34 | * 设置配置存储路径
35 | */
36 | public static void setConfigPath(String pathDir) {
37 | ConfigUtils.initialize(pathDir);
38 | }
39 |
40 | /**
41 | * 设置配置默认密码
42 | * @param password 密码 采用AES加密 不设置则不使用加密
43 | */
44 | public static void setConfigPassword(String password) {
45 | ConfigUtils.setGlobalPassword(password);
46 | }
47 |
48 | /**
49 | * 注入模块的Res资源到上下文中
50 | *
51 | * @param context 要注入的上下文
52 | */
53 | public static void injectResourcesToContext(Context context) {
54 | ActivityTools.injectResourcesToContext(context, moduleApkPath);
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/activity/ActivityProxyManager.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.activity;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Instrumentation;
5 | import android.content.Context;
6 | import android.content.pm.ApplicationInfo;
7 | import android.content.pm.PackageInfo;
8 | import android.content.pm.PackageManager;
9 | import android.os.Handler;
10 |
11 | import java.io.File;
12 | import java.lang.reflect.Field;
13 | import java.lang.reflect.Proxy;
14 | import java.util.concurrent.atomic.AtomicBoolean;
15 |
16 | import dalvik.system.DexClassLoader;
17 | import top.sacz.xphelper.activity.replace.IActivityManagerHandler;
18 | import top.sacz.xphelper.activity.replace.ProxyHandler;
19 | import top.sacz.xphelper.activity.replace.ProxyInstrumentation;
20 | import top.sacz.xphelper.reflect.ClassUtils;
21 | import top.sacz.xphelper.util.ActivityTools;
22 |
23 | @SuppressLint({"DiscouragedPrivateApi", "PrivateApi"})
24 | public class ActivityProxyManager {
25 | private static final AtomicBoolean Initialized = new AtomicBoolean();
26 |
27 | public static String HostActivityClassName;
28 | public static String ACTIVITY_PROXY_INTENT = "lin_proxy_intent";
29 |
30 | public static boolean isModuleActivity(String className) {
31 | try {
32 | return BaseActivity.class.isAssignableFrom(ClassUtils.getModuleClassLoader().loadClass(className));
33 | // ClassUtils.getModuleClassLoader().loadClass(className);
34 | } catch (Exception e) {
35 | return false;
36 | }
37 | }
38 |
39 | /**
40 | * 用于启动未注册在AndroidManifest的Activity(也就是模块自身的activity)
41 | * 模块自身的Activity需要继承本库的 {@link BaseActivity} 才能启动
42 | *
43 | * @param hostContext 宿主的上下文
44 | */
45 | public static void initActivityProxyManager(Context hostContext) {
46 |
47 | HostActivityClassName = ActivityTools.getAllActivity(hostContext)[0].name;
48 | if (Initialized.getAndSet(true)) return;
49 | try {
50 | Class> cActivityThread = Class.forName("android.app.ActivityThread");
51 | // 获取sCurrentActivityThread对象
52 | Field fCurrentActivityThread = cActivityThread.getDeclaredField("sCurrentActivityThread");
53 | fCurrentActivityThread.setAccessible(true);
54 | Object currentActivityThread = fCurrentActivityThread.get(null);
55 |
56 | replaceInstrumentation(currentActivityThread);
57 | replaceHandler(currentActivityThread);
58 | replaceIActivityManager();
59 | try {
60 | replaceIActivityTaskManager();
61 | } catch (Exception ignored) {
62 | }
63 | } catch (Exception e) {
64 | throw new RuntimeException(e);
65 | }
66 | }
67 |
68 | /**
69 | * 获取插件的随机id
70 | *
71 | * @param context 宿主上下文
72 | * @param pluginApkPath 插件路径
73 | * @return 随机id
74 | */
75 | private static int getModuleRandomID(Context context, String pluginApkPath) {
76 | try {
77 | //在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建 /data/data/hostPackageName/app_LActivity_DEXHotLoad
78 | File optimizedDirectoryFile = context.getDir("LActivity_DEXHotLoad", Context.MODE_PRIVATE);
79 | // 构建插件的DexClassLoader类加载器,参数:
80 | // 1、包含dex的apk文件或jar文件的路径,
81 | // 2、apk、jar解压缩生成dex存储的目录,
82 | // 3、本地library库目录,一般为null,
83 | // 4、父ClassLoader
84 | DexClassLoader dexClassLoader = new DexClassLoader(pluginApkPath, optimizedDirectoryFile.getPath(),
85 | null, ClassLoader.getSystemClassLoader());
86 | PackageManager pm = context.getPackageManager();
87 | PackageInfo info = pm.getPackageArchiveInfo(pluginApkPath, PackageManager.GET_ACTIVITIES);
88 | if (info == null)
89 | throw new RuntimeException("Package.getPackageArchiveInfo(pluginApkPath, PackageManager.GET_ACTIVITIES) ");
90 |
91 | ApplicationInfo appInfo = info.applicationInfo;
92 | // String appName = pm.getApplicationLabel(appInfo).toString();
93 | String packageName = appInfo.packageName;
94 |
95 | //通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id
96 | Class> resClz = dexClassLoader.loadClass(packageName + ".R");
97 | for (Class> res : resClz.getDeclaredClasses()) {
98 | for (Field idField : res.getDeclaredFields()) {
99 | idField.setAccessible(true);
100 | return (int) idField.get(null);
101 | }
102 | }
103 |
104 | } catch (Exception e) {
105 | throw new RuntimeException(e);
106 | }
107 | throw new RuntimeException("LActivity_DEXHotLoad getModuleRandomID Error");
108 | }
109 |
110 | private static void replaceInstrumentation(Object activityThread) throws Exception {
111 | Field fInstrumentation = activityThread.getClass().getDeclaredField("mInstrumentation");
112 | fInstrumentation.setAccessible(true);
113 | Instrumentation mInstrumentation = (Instrumentation) fInstrumentation.get(activityThread);
114 | fInstrumentation.set(activityThread, new ProxyInstrumentation(mInstrumentation));
115 | }
116 |
117 | private static void replaceHandler(Object activityThread) throws Exception {
118 | Field fHandler = activityThread.getClass().getDeclaredField("mH");
119 | fHandler.setAccessible(true);
120 | Handler mHandler = (Handler) fHandler.get(activityThread);
121 |
122 | Class> chandler = Class.forName("android.os.Handler");
123 | Field fCallback = chandler.getDeclaredField("mCallback");
124 | fCallback.setAccessible(true);
125 | Handler.Callback mCallback = (Handler.Callback) fCallback.get(mHandler);
126 | fCallback.set(mHandler, new ProxyHandler(mCallback));
127 | }
128 |
129 | private static void replaceIActivityManager() throws Exception {
130 | Class> activityManagerClass;
131 | Field gDefaultField;
132 | try {
133 | activityManagerClass = Class.forName("android.app.ActivityManagerNative");
134 | gDefaultField = activityManagerClass.getDeclaredField("gDefault");
135 | } catch (Exception err1) {
136 | try {
137 | activityManagerClass = Class.forName("android.app.ActivityManager");
138 | gDefaultField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
139 | } catch (Exception err2) {
140 | return;
141 | }
142 | }
143 | gDefaultField.setAccessible(true);
144 | Object gDefault = gDefaultField.get(null);
145 | Class> singletonClass = Class.forName("android.util.Singleton");
146 | Field mInstanceField = singletonClass.getDeclaredField("mInstance");
147 | mInstanceField.setAccessible(true);
148 | Object mInstance = mInstanceField.get(gDefault);
149 | Object amProxy = Proxy.newProxyInstance(
150 | ClassUtils.getModuleClassLoader(),
151 | new Class[]{Class.forName("android.app.IActivityManager")},
152 | new IActivityManagerHandler(mInstance));
153 | mInstanceField.set(gDefault, amProxy);
154 | }
155 |
156 | private static void replaceIActivityTaskManager() throws Exception {
157 | Class> activityTaskManagerClass = Class.forName("android.app.ActivityTaskManager");
158 | Field fIActivityTaskManagerSingleton = activityTaskManagerClass.getDeclaredField("IActivityTaskManagerSingleton");
159 | fIActivityTaskManagerSingleton.setAccessible(true);
160 | Object singleton = fIActivityTaskManagerSingleton.get(null);
161 | Class> activityManagerClass;
162 | Field gDefaultField;
163 | try {
164 | activityManagerClass = Class.forName("android.app.ActivityManagerNative");
165 | gDefaultField = activityManagerClass.getDeclaredField("gDefault");
166 | } catch (Exception err1) {
167 | try {
168 | activityManagerClass = Class.forName("android.app.ActivityManager");
169 | gDefaultField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
170 | } catch (Exception err2) {
171 | return;
172 | }
173 | }
174 | gDefaultField.setAccessible(true);
175 | Object gDefault = gDefaultField.get(null);
176 | Class> singletonClass = Class.forName("android.util.Singleton");
177 | Field mInstanceField = singletonClass.getDeclaredField("mInstance");
178 | mInstanceField.setAccessible(true);
179 | singletonClass.getMethod("get").invoke(singleton);
180 | Object mDefaultTaskMgr = mInstanceField.get(singleton);
181 | Object proxy2 = Proxy.newProxyInstance(
182 | ClassUtils.getModuleClassLoader(),
183 | new Class[]{Class.forName("android.app.IActivityTaskManager")},
184 | new IActivityManagerHandler(mDefaultTaskMgr));
185 | mInstanceField.set(singleton, proxy2);
186 | }
187 |
188 | }
189 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/activity/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.activity;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.view.Window;
8 | import android.view.WindowManager;
9 |
10 | import androidx.annotation.Nullable;
11 | import androidx.fragment.app.FragmentActivity;
12 |
13 | import top.sacz.xphelper.XpHelper;
14 | import top.sacz.xphelper.reflect.ClassUtils;
15 |
16 |
17 | /**
18 | * 使用前参阅 {@link ActivityProxyManager#initActivityProxyManager(Context)}
19 | */
20 | public class BaseActivity extends FragmentActivity {
21 | private final BaseActivityClassLoader mLoader = new BaseActivityClassLoader(BaseActivity.class.getClassLoader());
22 |
23 | @Override
24 | public ClassLoader getClassLoader() {
25 | return mLoader;
26 | }
27 |
28 | @Override
29 | protected void onRestoreInstanceState(Bundle savedInstanceState) {
30 | Bundle windowState = savedInstanceState.getBundle("android:viewHierarchyState");
31 | if (windowState != null) {
32 | windowState.setClassLoader(mLoader);
33 | }
34 | super.onRestoreInstanceState(savedInstanceState);
35 | }
36 |
37 | @Override
38 | protected void onCreate(@Nullable Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 | XpHelper.injectResourcesToContext(this);
41 | }
42 |
43 |
44 | /**
45 | * 将本 Activity 的状态栏设置为透明状态
46 | */
47 | protected void requestTranslucentStatusBar() {
48 | Window window = getWindow();
49 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
50 | | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
51 | window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
52 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
53 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
54 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
55 | window.setStatusBarColor(Color.TRANSPARENT);
56 | window.setNavigationBarColor(Color.TRANSPARENT);
57 | }
58 |
59 | private static class BaseActivityClassLoader extends ClassLoader {
60 | private final ClassLoader mBaseReferencer;
61 | private final ClassLoader mHostReferencer;
62 |
63 | public BaseActivityClassLoader(ClassLoader referencer) {
64 | mBaseReferencer = referencer;
65 | mHostReferencer = ClassUtils.getClassLoader();
66 | }
67 |
68 | @Override
69 | protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
70 | try {
71 | if (name.startsWith("androidx.compose") || name.startsWith("androidx.navigation") || name.startsWith("androidx.activity")) {
72 | return mBaseReferencer.loadClass(name);
73 | }
74 | return Context.class.getClassLoader().loadClass(name);
75 | } catch (ClassNotFoundException ignored) {
76 | }
77 | try {
78 | //start: overloaded
79 | if (name.equals("androidx.lifecycle.LifecycleOwner") || name.equals("androidx.lifecycle.ViewModelStoreOwner") || name.equals("androidx.savedstate.SavedStateRegistryOwner")) {
80 | return mHostReferencer.loadClass(name);
81 | }
82 | } catch (ClassNotFoundException ignored) {
83 | }
84 | //with ClassNotFoundException
85 | return mBaseReferencer.loadClass(name);
86 | }
87 | }
88 | }
89 |
90 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/activity/replace/IActivityManagerHandler.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.activity.replace;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Intent;
5 |
6 | import androidx.core.util.Pair;
7 |
8 | import java.lang.reflect.InvocationHandler;
9 | import java.lang.reflect.InvocationTargetException;
10 | import java.lang.reflect.Method;
11 |
12 | import top.sacz.xphelper.XpHelper;
13 | import top.sacz.xphelper.activity.ActivityProxyManager;
14 |
15 |
16 | public class IActivityManagerHandler implements InvocationHandler {
17 | private final Object activityManager;
18 |
19 | public IActivityManagerHandler(Object activityManager) {
20 | this.activityManager = activityManager;
21 | }
22 |
23 | private Pair foundFirstIntentOfArgs(Object[] args) {
24 | for (int i = 0; i < args.length; i++) {
25 | if (args[i] instanceof Intent) {
26 | return new Pair<>(i, (Intent) args[i]);
27 | }
28 | }
29 | return null;
30 | }
31 |
32 | @Override
33 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
34 | try {
35 | if (args != null) {
36 | if (method.getName().equals("startActivity")) {
37 | Pair pair = foundFirstIntentOfArgs(args);
38 | if (pair != null) {
39 | Intent intent = pair.second;
40 | ComponentName component = intent.getComponent();
41 | if (component != null) {
42 | String packageName = component.getPackageName();
43 | String className = component.getClassName();
44 | if (packageName.equals(XpHelper.context.getPackageName()) && ActivityProxyManager.isModuleActivity(className)) {
45 | Intent wrapper = new Intent();
46 | wrapper.setClassName(component.getPackageName(), ActivityProxyManager.HostActivityClassName);
47 | String proxyTargetActivity = intent.getStringExtra("proxy_target_activity");
48 | if (proxyTargetActivity != null) {
49 | wrapper.setClassName(component.getPackageName(), proxyTargetActivity);
50 | }
51 | wrapper.putExtra(ActivityProxyManager.ACTIVITY_PROXY_INTENT, pair.second);
52 | args[pair.first] = wrapper;
53 | }
54 | }
55 | }
56 | }
57 | return method.invoke(activityManager, args);
58 | }
59 | return method.invoke(activityManager);
60 | } catch (InvocationTargetException e) {
61 | throw e.getTargetException();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/activity/replace/ProxyHandler.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.activity.replace;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Intent;
5 | import android.os.Build;
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.os.IBinder;
9 | import android.os.Message;
10 | import android.util.Log;
11 |
12 | import androidx.annotation.NonNull;
13 |
14 | import java.lang.reflect.Field;
15 | import java.lang.reflect.InvocationTargetException;
16 | import java.lang.reflect.Method;
17 | import java.util.List;
18 |
19 | import top.sacz.xphelper.activity.ActivityProxyManager;
20 | import top.sacz.xphelper.reflect.ClassUtils;
21 |
22 |
23 | public class ProxyHandler implements Handler.Callback {
24 | private final Handler.Callback mDefault;
25 |
26 | public ProxyHandler(Handler.Callback defaultCallback) {
27 | mDefault = defaultCallback;
28 | }
29 |
30 | private static void processLaunchActivityItem(Object clientTransaction, Object item) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
31 | Class> clz = item.getClass();
32 | Field fmIntent = clz.getDeclaredField("mIntent");
33 | fmIntent.setAccessible(true);
34 | Intent wrapper = (Intent) fmIntent.get(item);
35 | Log.d("ParasiticsUtils:", "handleMessage: target wrapper =" + wrapper);
36 | assert wrapper != null;
37 | //获取Bundle
38 | Bundle bundle = null;
39 | try {
40 | Field fExtras = Intent.class.getDeclaredField("mExtras");
41 | fExtras.setAccessible(true);
42 | bundle = (Bundle) fExtras.get(wrapper);
43 | } catch (Exception e) {
44 | }
45 | //设置
46 | if (bundle != null) {
47 | bundle.setClassLoader(ClassUtils.getClassLoader());
48 | if (wrapper.hasExtra(ActivityProxyManager.ACTIVITY_PROXY_INTENT)) {
49 | Intent realIntent = wrapper.getParcelableExtra(ActivityProxyManager.ACTIVITY_PROXY_INTENT);
50 | fmIntent.set(item, realIntent);
51 | // android 12
52 | if (Build.VERSION.SDK_INT >= 31) {
53 | IBinder token = (IBinder) clientTransaction.getClass().getMethod("getActivityToken").invoke(clientTransaction);
54 | Class> cActivityThread = Class.forName("android.app.ActivityThread");
55 | Method currentActivityThread = cActivityThread.getDeclaredMethod("currentActivityThread");
56 | currentActivityThread.setAccessible(true);
57 | Object activityThread = currentActivityThread.invoke(null);
58 | assert activityThread != null;
59 | try {
60 | Object acr = activityThread.getClass()
61 | .getMethod("getLaunchingActivity", IBinder.class)
62 | .invoke(activityThread, token);
63 | if (acr != null) {
64 | Field fAcrIntent = acr.getClass().getDeclaredField("intent");
65 | fAcrIntent.setAccessible(true);
66 | fAcrIntent.set(acr, realIntent);
67 | }
68 | } catch (NoSuchMethodException e) {
69 | if (Build.VERSION.SDK_INT == 33) {
70 | // expected behavior...?!
71 | } else {
72 | throw e;
73 | }
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
80 | @SuppressLint({"PrivateApi", "DiscouragedPrivateApi"})
81 | @Override
82 | public boolean handleMessage(@NonNull Message msg) {
83 | switch (msg.what) {
84 | // LAUNCH_ACTIVITY sdk <= 8.0
85 | case 100:
86 | try {
87 | Object record = msg.obj;
88 | Field fIntent = record.getClass().getDeclaredField("intent");
89 | fIntent.setAccessible(true);
90 | Intent intent = (Intent) fIntent.get(record);
91 | assert intent != null;
92 | //获取bundle
93 | Bundle bundle = null;
94 | try {
95 | Field fExtras = Intent.class.getDeclaredField("mExtras");
96 | fExtras.setAccessible(true);
97 | bundle = (Bundle) fExtras.get(intent);
98 | } catch (Exception e) {
99 | }
100 | //设置
101 | if (bundle != null) {
102 | bundle.setClassLoader(ClassUtils.getClassLoader());
103 | if (intent.hasExtra(ActivityProxyManager.ACTIVITY_PROXY_INTENT)) {
104 | Intent rIntent = intent.getParcelableExtra(ActivityProxyManager.ACTIVITY_PROXY_INTENT);
105 | fIntent.set(record, rIntent);
106 | }
107 | }
108 | } catch (Exception e) {
109 | }
110 | break;
111 | // EXECUTE_TRANSACTION 8.0+
112 | case 159:
113 | Object clientTransaction = msg.obj;
114 | try {
115 | if (clientTransaction != null) {
116 | // Method getCallbacksMethod = clientTransaction.getClass().getDeclaredMethod("getCallbacks");
117 | Method getCallbacksMethod = Class.forName("android.app.servertransaction.ClientTransaction").getDeclaredMethod("getCallbacks");
118 | getCallbacksMethod.setAccessible(true);
119 | List> clientTransactionItems = (List>) getCallbacksMethod.invoke(clientTransaction);
120 | if (clientTransactionItems == null && clientTransactionItems.isEmpty())
121 | break;
122 | for (Object item : clientTransactionItems) {
123 | Class> clz = item.getClass();
124 | if (clz.getName().contains("LaunchActivityItem")) {
125 | processLaunchActivityItem(clientTransaction, item);
126 | }
127 | }
128 | }
129 | } catch (Exception e) {
130 | }
131 | break;
132 | // default:
133 | // LogUtil.log("code -> " + msg.what);
134 | }
135 | return mDefault != null && mDefault.handleMessage(msg);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/activity/replace/ProxyInstrumentation.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.activity.replace;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.app.Instrumentation;
6 | import android.app.UiAutomation;
7 | import android.content.ComponentName;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.IntentFilter;
11 | import android.content.pm.ActivityInfo;
12 | import android.os.Bundle;
13 | import android.os.IBinder;
14 | import android.os.Looper;
15 | import android.os.PersistableBundle;
16 | import android.os.TestLooperManager;
17 | import android.view.KeyEvent;
18 | import android.view.MotionEvent;
19 |
20 | import top.sacz.xphelper.XpHelper;
21 | import top.sacz.xphelper.activity.ActivityProxyManager;
22 | import top.sacz.xphelper.reflect.ClassUtils;
23 |
24 |
25 | public class ProxyInstrumentation extends Instrumentation {
26 | private final Instrumentation mBase;
27 |
28 | public ProxyInstrumentation(Instrumentation mInstrumentation) {
29 | mBase = mInstrumentation;
30 | }
31 |
32 | @Override
33 | public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
34 | try {
35 | return mBase.newActivity(cl, className, intent);
36 | } catch (Exception e) {
37 | if (ActivityProxyManager.isModuleActivity(className)) {
38 | return (Activity) ClassUtils.getModuleClassLoader().loadClass(className).newInstance();
39 | }
40 | throw e;
41 | }
42 | }
43 |
44 | @Override
45 | public void onCreate(Bundle arguments) {
46 | mBase.onCreate(arguments);
47 | }
48 |
49 | @Override
50 | public void start() {
51 | mBase.start();
52 | }
53 |
54 | @Override
55 | public void onStart() {
56 | mBase.onStart();
57 | }
58 |
59 | @Override
60 | public boolean onException(Object obj, Throwable e) {
61 | return mBase.onException(obj, e);
62 | }
63 |
64 | @Override
65 | public void sendStatus(int resultCode, Bundle results) {
66 | mBase.sendStatus(resultCode, results);
67 | }
68 |
69 | @Override
70 | public void addResults(Bundle results) {
71 | mBase.addResults(results);
72 | }
73 |
74 | @Override
75 | public void finish(int resultCode, Bundle results) {
76 | mBase.finish(resultCode, results);
77 | }
78 |
79 | @Override
80 | public void setAutomaticPerformanceSnapshots() {
81 | mBase.setAutomaticPerformanceSnapshots();
82 | }
83 |
84 | @Override
85 | public void startPerformanceSnapshot() {
86 | mBase.startPerformanceSnapshot();
87 | }
88 |
89 | @Override
90 | public void endPerformanceSnapshot() {
91 | mBase.endPerformanceSnapshot();
92 | }
93 |
94 | @Override
95 | public void onDestroy() {
96 | mBase.onDestroy();
97 | }
98 |
99 | @Override
100 | public Context getContext() {
101 | return mBase.getContext();
102 | }
103 |
104 | @Override
105 | public ComponentName getComponentName() {
106 | return mBase.getComponentName();
107 | }
108 |
109 | @Override
110 | public Context getTargetContext() {
111 | return mBase.getTargetContext();
112 | }
113 |
114 | @Override
115 | public String getProcessName() {
116 | return mBase.getProcessName();
117 | }
118 |
119 | @Override
120 | public boolean isProfiling() {
121 | return mBase.isProfiling();
122 | }
123 |
124 | @Override
125 | public void startProfiling() {
126 | mBase.startProfiling();
127 | }
128 |
129 | @Override
130 | public void stopProfiling() {
131 | mBase.stopProfiling();
132 | }
133 |
134 | @Override
135 | public void setInTouchMode(boolean inTouch) {
136 | mBase.setInTouchMode(inTouch);
137 | }
138 |
139 | @Override
140 | public void waitForIdle(Runnable recipient) {
141 | mBase.waitForIdle(recipient);
142 | }
143 |
144 | @Override
145 | public void waitForIdleSync() {
146 | mBase.waitForIdleSync();
147 | }
148 |
149 | @Override
150 | public void runOnMainSync(Runnable runner) {
151 | mBase.runOnMainSync(runner);
152 | }
153 |
154 | @Override
155 | public Activity startActivitySync(Intent intent) {
156 | return mBase.startActivitySync(intent);
157 | }
158 |
159 | @Override
160 | public Activity startActivitySync(Intent intent, Bundle options) {
161 | return mBase.startActivitySync(intent, options);
162 | }
163 |
164 | @Override
165 | public void addMonitor(ActivityMonitor monitor) {
166 | mBase.addMonitor(monitor);
167 | }
168 |
169 | @Override
170 | public ActivityMonitor addMonitor(String cls, ActivityResult result, boolean block) {
171 | return mBase.addMonitor(cls, result, block);
172 | }
173 |
174 | @Override
175 | public ActivityMonitor addMonitor(IntentFilter filter, ActivityResult result, boolean block) {
176 | return mBase.addMonitor(filter, result, block);
177 | }
178 |
179 | @Override
180 | public boolean checkMonitorHit(ActivityMonitor monitor, int minHits) {
181 | return mBase.checkMonitorHit(monitor, minHits);
182 | }
183 |
184 | @Override
185 | public Activity waitForMonitor(ActivityMonitor monitor) {
186 | return mBase.waitForMonitor(monitor);
187 | }
188 |
189 | @Override
190 | public Activity waitForMonitorWithTimeout(ActivityMonitor monitor, long timeOut) {
191 | return mBase.waitForMonitorWithTimeout(monitor, timeOut);
192 | }
193 |
194 | @Override
195 | public void removeMonitor(ActivityMonitor monitor) {
196 | mBase.removeMonitor(monitor);
197 | }
198 |
199 | @Override
200 | public boolean invokeContextMenuAction(Activity targetActivity, int id, int flag) {
201 | return mBase.invokeContextMenuAction(targetActivity, id, flag);
202 | }
203 |
204 | @Override
205 | public boolean invokeMenuActionSync(Activity targetActivity, int id, int flag) {
206 | return mBase.invokeMenuActionSync(targetActivity, id, flag);
207 | }
208 |
209 | @Override
210 | public void sendCharacterSync(int keyCode) {
211 | mBase.sendCharacterSync(keyCode);
212 | }
213 |
214 | @Override
215 | public void sendKeyDownUpSync(int key) {
216 | mBase.sendKeyDownUpSync(key);
217 | }
218 |
219 | @Override
220 | public void sendKeySync(KeyEvent event) {
221 | mBase.sendKeySync(event);
222 | }
223 |
224 | @Override
225 | public void sendPointerSync(MotionEvent event) {
226 | mBase.sendPointerSync(event);
227 | }
228 |
229 | @Override
230 | public void sendStringSync(String text) {
231 | mBase.sendStringSync(text);
232 | }
233 |
234 | @Override
235 | public void sendTrackballEventSync(MotionEvent event) {
236 | mBase.sendTrackballEventSync(event);
237 | }
238 |
239 | @Override
240 | public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
241 | return mBase.newApplication(cl, className, context);
242 | }
243 |
244 | @Override
245 | public void callApplicationOnCreate(Application app) {
246 | mBase.callApplicationOnCreate(app);
247 | }
248 |
249 | @Override
250 | public Activity newActivity(Class> clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException {
251 | return mBase.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance);
252 | }
253 |
254 | private void inject(Activity activity, Bundle icicle) {
255 | if (icicle != null) {
256 | String clzName = activity.getClass().getName();
257 | if (ActivityProxyManager.isModuleActivity(clzName)) {
258 | icicle.setClassLoader(ClassUtils.getModuleClassLoader());
259 | }
260 | }
261 |
262 | XpHelper.injectResourcesToContext(activity);
263 | }
264 |
265 | @Override
266 | public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
267 | inject(activity, icicle);
268 | mBase.callActivityOnCreate(activity, icicle, persistentState);
269 | }
270 |
271 | @Override
272 | public void callActivityOnCreate(Activity activity, Bundle icicle) {
273 | inject(activity, icicle);
274 | mBase.callActivityOnCreate(activity, icicle);
275 | }
276 |
277 | @Override
278 | public void callActivityOnDestroy(Activity activity) {
279 | mBase.callActivityOnDestroy(activity);
280 | }
281 |
282 | @Override
283 | public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
284 | mBase.callActivityOnRestoreInstanceState(activity, savedInstanceState);
285 | }
286 |
287 | @Override
288 | public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState, PersistableBundle persistentState) {
289 | mBase.callActivityOnRestoreInstanceState(activity, savedInstanceState, persistentState);
290 | }
291 |
292 | @Override
293 | public void callActivityOnPostCreate(Activity activity, Bundle savedInstanceState) {
294 | mBase.callActivityOnPostCreate(activity, savedInstanceState);
295 | }
296 |
297 | @Override
298 | public void callActivityOnPostCreate(Activity activity, Bundle savedInstanceState, PersistableBundle persistentState) {
299 | mBase.callActivityOnPostCreate(activity, savedInstanceState, persistentState);
300 | }
301 |
302 | @Override
303 | public void callActivityOnNewIntent(Activity activity, Intent intent) {
304 | mBase.callActivityOnNewIntent(activity, intent);
305 | }
306 |
307 | @Override
308 | public void callActivityOnStart(Activity activity) {
309 | mBase.callActivityOnStart(activity);
310 | }
311 |
312 | @Override
313 | public void callActivityOnRestart(Activity activity) {
314 | mBase.callActivityOnRestart(activity);
315 | }
316 |
317 | @Override
318 | public void callActivityOnPause(Activity activity) {
319 | mBase.callActivityOnPause(activity);
320 | }
321 |
322 | @Override
323 | public void callActivityOnResume(Activity activity) {
324 | mBase.callActivityOnResume(activity);
325 | }
326 |
327 | @Override
328 | public void callActivityOnStop(Activity activity) {
329 | mBase.callActivityOnStop(activity);
330 | }
331 |
332 | @Override
333 | public void callActivityOnUserLeaving(Activity activity) {
334 | mBase.callActivityOnUserLeaving(activity);
335 | }
336 |
337 | @Override
338 | public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
339 | mBase.callActivityOnSaveInstanceState(activity, outState);
340 | }
341 |
342 | @Override
343 | public void callActivityOnSaveInstanceState(Activity activity, Bundle outState, PersistableBundle outPersistentState) {
344 | mBase.callActivityOnSaveInstanceState(activity, outState, outPersistentState);
345 | }
346 |
347 | @Override
348 | public void callActivityOnPictureInPictureRequested(Activity activity) {
349 | mBase.callActivityOnPictureInPictureRequested(activity);
350 | }
351 |
352 | @Override
353 | @SuppressWarnings("deprecation")
354 | public void startAllocCounting() {
355 | mBase.startAllocCounting();
356 | }
357 |
358 | @Override
359 | @SuppressWarnings("deprecation")
360 | public void stopAllocCounting() {
361 | mBase.stopAllocCounting();
362 | }
363 |
364 | @Override
365 | public Bundle getAllocCounts() {
366 | return mBase.getAllocCounts();
367 | }
368 |
369 | @Override
370 | public Bundle getBinderCounts() {
371 | return mBase.getBinderCounts();
372 | }
373 |
374 | @Override
375 | public UiAutomation getUiAutomation() {
376 | return mBase.getUiAutomation();
377 | }
378 |
379 | @Override
380 | public UiAutomation getUiAutomation(int flags) {
381 | return mBase.getUiAutomation(flags);
382 | }
383 |
384 | @Override
385 | public TestLooperManager acquireLooperManager(Looper looper) {
386 | return mBase.acquireLooperManager(looper);
387 | }
388 |
389 | }
390 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/base/BaseDexQuery.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.base;
2 |
3 | import top.sacz.xphelper.dexkit.cache.DexKitCache;
4 |
5 | public abstract class BaseDexQuery {
6 | public final boolean existCache() {
7 | return DexKitCache.exist(toString());
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/base/BaseFinder.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.base;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.Field;
5 | import java.lang.reflect.Member;
6 | import java.lang.reflect.Method;
7 | import java.util.ArrayList;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import de.robv.android.xposed.XposedHelpers;
13 | import top.sacz.xphelper.exception.ReflectException;
14 |
15 | /**
16 | * 超精妙设计
17 | */
18 | public abstract class BaseFinder {
19 | private static final Map> FIELD_CACHE = new HashMap<>();
20 | private static final Map> METHOD_CACHE = new HashMap<>();
21 |
22 | private static final Map>> CONSTRUCTOR_CACHE = new HashMap<>();
23 |
24 | protected Class> declaringClass;
25 |
26 | protected String fromClassName;
27 |
28 | protected List result = new ArrayList<>();
29 |
30 | private boolean isFind = false;
31 |
32 | protected BaseFinder() {
33 | }
34 |
35 | protected void writeToFieldCache(List value) {
36 | String sign = buildSign();
37 | FIELD_CACHE.put(sign, value);
38 | }
39 |
40 | protected List findFiledCache() {
41 | String sign = buildSign();
42 | if (FIELD_CACHE.containsKey(sign)) {
43 | return FIELD_CACHE.get(sign);
44 | }
45 | return null;
46 | }
47 |
48 | protected void writeToMethodCache(List value) {
49 | String sign = buildSign();
50 | METHOD_CACHE.put(sign, value);
51 | }
52 |
53 | protected List findMethodCache() {
54 | String sign = buildSign();
55 | if (METHOD_CACHE.containsKey(sign)) {
56 | return METHOD_CACHE.get(sign);
57 | }
58 | return null;
59 | }
60 |
61 | protected void writeToConstructorCache(List> value) {
62 | String sign = buildSign();
63 | CONSTRUCTOR_CACHE.put(sign, value);
64 | }
65 |
66 | protected List> findConstructorCache() {
67 | String sign = buildSign();
68 | if (CONSTRUCTOR_CACHE.containsKey(sign)) {
69 | return CONSTRUCTOR_CACHE.get(sign);
70 | }
71 | return null;
72 | }
73 |
74 | /**
75 | * 由子类实现的实际查找方法
76 | *
77 | * @return this
78 | */
79 | public abstract BaseFinder find();
80 |
81 | /**
82 | * 构建缓存签名
83 | */
84 | public abstract String buildSign();
85 |
86 | /**
87 | * 进行查找,查找后如果没有结果会自动向父类查找
88 | *
89 | * @return this
90 | */
91 | public BaseFinder findAndSuper() {
92 | if (this.isFind) {
93 | return this;
94 | }
95 | find();
96 | if (result.isEmpty() && getDeclaringClass() != Object.class) {
97 | //如果查找不到 向父类查找
98 | return setDeclaringClass(getDeclaringClass().getSuperclass())
99 | .findAndSuper();
100 | }
101 | //彻底的查找结束
102 | this.isFind = true;
103 | //设置可访问
104 | for (T member : result) {
105 | XposedHelpers.callMethod(member, "setAccessible", true);
106 | }
107 |
108 | return this;
109 | }
110 |
111 | /**
112 | * 在所有获取结果的方法都需要调用 不然不会查找 {@link #findAndSuper();}
113 | *
114 | * @return 结果列表
115 | */
116 | public List getResult() {
117 | findAndSuper();
118 | return result;
119 | }
120 |
121 | public T get(int index) {
122 | if (getResult().isEmpty()) {
123 | throw new ReflectException("can not find " + buildSign());
124 | }
125 | if (result.size() <= index) {
126 | throw new ReflectException("The resulting number is " + result.size() + " and the index is " + index + ":" + buildSign());
127 | }
128 | return result.get(index);
129 | }
130 |
131 | public T first() {
132 | if (getResult().isEmpty()) {
133 | throw new ReflectException("can not find " + buildSign());
134 | }
135 | return result.get(0);
136 | }
137 |
138 | public T last() {
139 | if (getResult().isEmpty()) {
140 | throw new ReflectException("can not find " + buildSign());
141 | }
142 | return result.get(result.size() - 1);
143 | }
144 |
145 | protected Class> getDeclaringClass() {
146 | return declaringClass;
147 | }
148 |
149 | protected BaseFinder setDeclaringClass(Class> declaringClass) {
150 | this.declaringClass = declaringClass;
151 | if (fromClassName == null) {
152 | this.fromClassName = declaringClass.getName();
153 | }
154 | return this;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/ClassFinder.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit;
2 |
3 |
4 | import org.jetbrains.annotations.NotNull;
5 | import org.luckypray.dexkit.query.FindClass;
6 | import org.luckypray.dexkit.query.enums.MatchType;
7 | import org.luckypray.dexkit.query.matchers.ClassMatcher;
8 | import org.luckypray.dexkit.result.ClassData;
9 | import org.luckypray.dexkit.result.ClassDataList;
10 |
11 | import java.lang.reflect.Modifier;
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.List;
15 |
16 | import top.sacz.xphelper.base.BaseDexQuery;
17 | import top.sacz.xphelper.dexkit.cache.DexKitCache;
18 | import top.sacz.xphelper.reflect.ClassUtils;
19 |
20 | public class ClassFinder extends BaseDexQuery {
21 |
22 | private final List interfaces = new ArrayList<>(); // 实现的接口
23 | private final List searchPackages = new ArrayList<>(); // 搜索包过滤
24 | private final List excludePackages = new ArrayList<>(); // 排除包过滤
25 | private final List fields = new ArrayList<>(); // 包含的字段
26 | private final List methods = new ArrayList<>(); // 包含的方法
27 | private final List usedString = new ArrayList<>();//类方法中使用的字符串列表
28 |
29 | private String className; // 类名匹配
30 | private String superClass; // 父类匹配
31 | private int modifiers = -1; // 修饰符
32 |
33 | private MatchType matchType = MatchType.Contains;
34 |
35 | public static ClassFinder build() {
36 | return new ClassFinder();
37 | }
38 |
39 | public static ClassFinder from(Class> clazz) {
40 | ClassFinder finder = new ClassFinder();
41 | finder.className = clazz.getName();
42 | finder.superClass = clazz.getSuperclass().getName();
43 | for (Class> interfaceClass : clazz.getInterfaces()) {
44 | finder.interfaces.add(interfaceClass.getName());
45 | }
46 | finder.modifiers = clazz.getModifiers();
47 | finder.matchType = MatchType.Equals;
48 | return finder;
49 | }
50 |
51 | /**
52 | * 设置方法中使用的字符串列表
53 | *
54 | * @param strings
55 | * @return
56 | */
57 | public ClassFinder usedString(String... strings) {
58 | this.usedString.addAll(Arrays.asList(strings));
59 | return this;
60 | }
61 |
62 | public ClassFinder className(String name) {
63 | this.className = name;
64 | return this;
65 | }
66 |
67 | public ClassFinder superClass(String superClass) {
68 | this.superClass = superClass;
69 | return this;
70 | }
71 |
72 | public ClassFinder addInterface(String... interfaces) {
73 | this.interfaces.addAll(Arrays.asList(interfaces));
74 | return this;
75 | }
76 |
77 | public ClassFinder modifiers(int modifiers, MatchType matchType) {
78 | this.modifiers = modifiers;
79 | this.matchType = matchType;
80 | return this;
81 | }
82 |
83 |
84 | public ClassFinder searchPackages(String... packages) {
85 | this.searchPackages.addAll(Arrays.asList(packages));
86 | return this;
87 | }
88 |
89 | public ClassFinder excludePackages(String... packages) {
90 | this.excludePackages.addAll(Arrays.asList(packages));
91 | return this;
92 | }
93 |
94 | public ClassFinder fields(FieldFinder... fields) {
95 | this.fields.addAll(Arrays.asList(fields));
96 | return this;
97 | }
98 |
99 | public ClassFinder methods(MethodFinder... methods) {
100 | this.methods.addAll(Arrays.asList(methods));
101 | return this;
102 | }
103 |
104 | private FindClass buildFindClass() {
105 | FindClass findClass = FindClass.create();
106 | if (!searchPackages.isEmpty())
107 | findClass.searchPackages(searchPackages.toArray(new String[0]));
108 | if (!excludePackages.isEmpty())
109 | findClass.excludePackages(excludePackages.toArray(new String[0]));
110 | return findClass.matcher(buildClassMatcher());
111 | }
112 |
113 | public ClassMatcher buildClassMatcher() {
114 | ClassMatcher matcher = ClassMatcher.create();
115 | if (className != null) matcher.className(className);
116 | if (superClass != null) matcher.superClass(superClass);
117 | if (!interfaces.isEmpty()) {
118 | for (String interfaceClassName : interfaces) {
119 | matcher.addInterface(interfaceClassName);
120 | }
121 | }
122 | if (!usedString.isEmpty()) {
123 | matcher.usingStrings(usedString);
124 | }
125 | if (modifiers != -1) {
126 | matcher.modifiers(modifiers, matchType);
127 | }
128 | if (!fields.isEmpty()) {
129 | for (FieldFinder field : fields) {
130 | matcher.addField(field.buildFieldMatcher());
131 | }
132 | }
133 | if (!methods.isEmpty()) {
134 | for (MethodFinder method : methods) {
135 | matcher.addMethod(method.buildMethodMatcher());
136 | }
137 | }
138 | return matcher;
139 | }
140 |
141 | public List> find() {
142 | try {
143 | List> cache = DexKitCache.getClassList(toString());
144 | if (cache != null) return cache;
145 |
146 | ArrayList> result = new ArrayList<>();
147 | ClassDataList dataList = DexFinder.getDexKitBridge().findClass(buildFindClass());
148 | if (dataList.isEmpty()) {
149 | DexKitCache.putClassList(toString(), result);
150 | return result;
151 | }
152 |
153 | for (ClassData data : dataList) {
154 | Class> clazz = data.getInstance(ClassUtils.getClassLoader());
155 | result.add(clazz);
156 | }
157 | DexKitCache.putClassList(toString(), result);
158 | return result;
159 | } catch (Exception e) {
160 | return new ArrayList<>();
161 | }
162 | }
163 |
164 |
165 | public Class> firstOrNull() {
166 | List> list = find();
167 | return list.isEmpty() ? null : list.get(0);
168 | }
169 |
170 | public Class> first() throws ClassNotFoundException {
171 | List> list = find();
172 | if (list.isEmpty()) throw new ClassNotFoundException("Class not found: " + this);
173 | return list.get(0);
174 | }
175 |
176 | @NotNull
177 | @Override
178 | public String toString() {
179 | StringBuilder sb = new StringBuilder("cf");
180 | if (className != null) sb.append(className);
181 | if (superClass != null) sb.append(superClass);
182 | if (!usedString.isEmpty()) sb.append(usedString);
183 | if (!interfaces.isEmpty()) sb.append(interfaces);
184 | if (modifiers != -1) sb.append(Modifier.toString(modifiers));
185 | if (!searchPackages.isEmpty()) sb.append(searchPackages);
186 | if (!excludePackages.isEmpty()) sb.append(excludePackages);
187 | if (!fields.isEmpty()) sb.append(fields);
188 | if (!methods.isEmpty()) sb.append(methods);
189 | return sb.toString();
190 | }
191 | }
192 |
193 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/DexFinder.kt:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit
2 |
3 |
4 | import org.luckypray.dexkit.DexKitBridge
5 | import top.sacz.xphelper.XpHelper
6 | import top.sacz.xphelper.dexkit.bean.ClassInfo
7 | import top.sacz.xphelper.dexkit.bean.FieldInfo
8 | import top.sacz.xphelper.dexkit.bean.MethodInfo
9 | import top.sacz.xphelper.dexkit.cache.DexKitCache
10 | import java.util.Timer
11 | import java.util.TimerTask
12 | import java.util.concurrent.atomic.AtomicBoolean
13 |
14 | object DexFinder {
15 | private val isLoadLibrary = AtomicBoolean()
16 | private var dexKitBridge: DexKitBridge? = null
17 | private var timer: Timer? = null
18 |
19 | private var autoCloseTime = (10 * 1000).toLong()
20 |
21 | /**
22 | * 设置关闭的时间 超过此时间没有使用dexkit 则自动关闭 (其实有可能查找过程中被关闭)
23 | * 所以确保每次调用getDexKitBridge后十秒内完成查找
24 | * 默认十秒 设置为0则不会自动关闭
25 | *
26 | * @param time 单位毫秒
27 | */
28 | fun setAutoCloseTime(time: Long) {
29 | autoCloseTime = time
30 | }
31 |
32 | /**
33 | * 初始化dexkit
34 | *
35 | * @param apkPath
36 | */
37 | @Synchronized
38 | fun create(apkPath: String) {
39 | if (dexKitBridge != null) {
40 | return
41 | }
42 | if (!isLoadLibrary.getAndSet(true)) {
43 | try {
44 | System.loadLibrary("dexkit")
45 | } catch (e: Exception) {
46 | }
47 | }
48 | dexKitBridge = DexKitBridge.create(apkPath)
49 | }
50 |
51 | /**
52 | * 得到dexkit实例
53 | */
54 | @JvmStatic
55 | fun getDexKitBridge(): DexKitBridge {
56 | if (dexKitBridge == null) {
57 | create(XpHelper.context.applicationInfo.sourceDir)
58 | }
59 | resetTimer()
60 | return dexKitBridge!!
61 | }
62 |
63 | @JvmSynthetic
64 | fun findMethod(methodInfo: MethodInfo.() -> Unit): MethodFinder {
65 | val newInfo = MethodInfo().also(methodInfo)
66 | return newInfo.generate()
67 | }
68 |
69 | @JvmSynthetic
70 | fun findField(fieldInfo: FieldInfo.() -> Unit): FieldFinder {
71 | val newInfo = FieldInfo().also(fieldInfo)
72 | return newInfo.generate()
73 | }
74 |
75 | @JvmSynthetic
76 | fun findClass(classInfo: ClassInfo.() -> Unit): ClassFinder {
77 | val newInfo = ClassInfo().also(classInfo)
78 | return newInfo.generate()
79 | }
80 |
81 | /**
82 | * 清空缓存
83 | */
84 | fun clearCache() {
85 | DexKitCache.clearCache()
86 | }
87 |
88 | private fun resetTimer() {
89 | if (autoCloseTime <= 0) {
90 | return
91 | }
92 | //如果存在则取消 达到重置时间的效果
93 | if (timer != null) {
94 | timer!!.cancel()
95 | }
96 | //定时 10秒钟后关闭
97 | timer = Timer()
98 | timer!!.schedule(object : TimerTask() {
99 | override fun run() {
100 | close()
101 | }
102 | }, autoCloseTime) // 10 seconds
103 | }
104 |
105 | /**
106 | * 释放dexkit资源
107 | */
108 | fun close() {
109 | if (dexKitBridge != null) {
110 | dexKitBridge!!.close()
111 | dexKitBridge = null
112 | }
113 | if (timer != null) {
114 | timer!!.cancel()
115 | timer = null
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/FieldFinder.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import org.luckypray.dexkit.query.FindField;
6 | import org.luckypray.dexkit.query.enums.MatchType;
7 | import org.luckypray.dexkit.query.matchers.FieldMatcher;
8 | import org.luckypray.dexkit.result.FieldData;
9 | import org.luckypray.dexkit.result.FieldDataList;
10 |
11 | import java.lang.reflect.Field;
12 | import java.lang.reflect.Method;
13 | import java.lang.reflect.Modifier;
14 | import java.util.ArrayList;
15 | import java.util.Arrays;
16 | import java.util.List;
17 |
18 | import top.sacz.xphelper.base.BaseDexQuery;
19 | import top.sacz.xphelper.dexkit.cache.DexKitCache;
20 | import top.sacz.xphelper.reflect.ClassUtils;
21 |
22 | public class FieldFinder extends BaseDexQuery {
23 |
24 | private Class> declaredClass; // 字段声明类
25 | private String fieldName; // 字段名称
26 | private Class> fieldType; // 字段类型
27 | private int modifiers = -1; // 修饰符
28 | private MatchType matchType = MatchType.Contains;
29 |
30 | private final List searchPackages = new ArrayList<>(); // 搜索包
31 | private final List excludePackages = new ArrayList<>(); // 排除包
32 |
33 | private final List readMethods = new ArrayList<>();
34 | private final List writeMethods = new ArrayList<>();
35 |
36 | /**
37 | * 构建器模式
38 | *
39 | * @return FieldFinder实例
40 | */
41 | public static FieldFinder build() {
42 | return new FieldFinder();
43 | }
44 |
45 | /**
46 | * 将字段转换为dexkit-FieldMatcher
47 | *
48 | * @param field 字段
49 | * @return FieldMatcher实例
50 | */
51 | public static FieldMatcher toFieldMatcher(Field field) {
52 | return FieldMatcher.create(field);
53 | }
54 |
55 | /**
56 | * 将字段转换为FieldFinder
57 | * @param field
58 | * @return
59 | */
60 | public static FieldFinder from(Field field) {
61 | FieldFinder finder = new FieldFinder();
62 | finder.declaredClass = field.getDeclaringClass();
63 | finder.fieldName = field.getName();
64 | finder.fieldType = field.getType();
65 | finder.modifiers = field.getModifiers();
66 | finder.matchType = MatchType.Equals;
67 | return finder;
68 | }
69 |
70 | /**
71 | * 读取了该字段的方法
72 | *
73 | * @param readMethods
74 | * @return
75 | */
76 | public FieldFinder readMethods(MethodFinder... readMethods) {
77 | this.readMethods.addAll(Arrays.asList(readMethods));
78 | return this;
79 | }
80 | /**
81 | * 读取了该字段的方法
82 | *
83 | * @param readMethods
84 | * @return
85 | */
86 | public FieldFinder readMethods(Method... readMethods) {
87 | for (Method readMethod : readMethods) {
88 | this.readMethods.add(MethodFinder.from(readMethod));
89 | }
90 | return this;
91 | }
92 |
93 | /**
94 | * 写入了该字段的方法
95 | *
96 | * @param writeMethods
97 | * @return
98 | */
99 | public FieldFinder writeMethods(MethodFinder... writeMethods) {
100 | this.writeMethods.addAll(Arrays.asList(writeMethods));
101 | return this;
102 | }
103 |
104 | /**
105 | * 写入了该字段的方法
106 | *
107 | * @param writeMethods
108 | * @return
109 | */
110 | public FieldFinder writeMethods(Method... writeMethods) {
111 | for (Method writeMethod : writeMethods) {
112 | this.writeMethods.add(MethodFinder.from(writeMethod));
113 | }
114 | return this;
115 | }
116 |
117 | /**
118 | * 设置字段所属类
119 | *
120 | * @param declaredClass 字段声明类
121 | * @return 当前FieldFinder实例
122 | */
123 | public FieldFinder declaredClass(Class> declaredClass) {
124 | this.declaredClass = declaredClass;
125 | return this;
126 | }
127 |
128 | /**
129 | * 设置字段名称
130 | *
131 | * @param name 字段名称
132 | * @return 当前FieldFinder实例
133 | */
134 | public FieldFinder fieldName(String name) {
135 | this.fieldName = name;
136 | return this;
137 | }
138 |
139 | /**
140 | * 设置字段类型
141 | *
142 | * @param fieldType 字段类型
143 | * @return 当前FieldFinder实例
144 | */
145 | public FieldFinder fieldType(Class> fieldType) {
146 | this.fieldType = fieldType;
147 | return this;
148 | }
149 |
150 | /**
151 | * 设置修饰符(如public/private等)
152 | *
153 | * @param modifiers 修饰符
154 | * @param matchType 匹配类型
155 | * @return 当前FieldFinder实例
156 | */
157 | public FieldFinder modifiers(int modifiers, MatchType matchType) {
158 | this.modifiers = modifiers;
159 | this.matchType = matchType;
160 | return this;
161 | }
162 |
163 | public FieldFinder searchPackages(String... packages) {
164 | this.searchPackages.addAll(Arrays.asList(packages));
165 | return this;
166 | }
167 |
168 | public FieldFinder excludePackages(String... packages) {
169 | this.excludePackages.addAll(Arrays.asList(packages));
170 | return this;
171 | }
172 |
173 | private FindField buildFindField() {
174 | FindField findField = FindField.create();
175 | if (!searchPackages.isEmpty()) findField.searchPackages(searchPackages.toArray(new String[0]));
176 | if (!excludePackages.isEmpty()) findField.excludePackages(excludePackages.toArray(new String[0]));
177 | return findField.matcher(buildFieldMatcher());
178 | }
179 |
180 | public FieldMatcher buildFieldMatcher() {
181 | FieldMatcher matcher = FieldMatcher.create();
182 | if (declaredClass != null) matcher.declaredClass(declaredClass);
183 | if (fieldName != null) matcher.name(fieldName);
184 | if (fieldType != null) matcher.type(fieldType);
185 | if (modifiers != -1) matcher.modifiers(modifiers, matchType);
186 | if (!readMethods.isEmpty()) {
187 | for (MethodFinder readMethod : readMethods) {
188 | matcher.addReadMethod(readMethod.buildMethodMatcher());
189 | }
190 | }
191 | if (!writeMethods.isEmpty()) {
192 | for (MethodFinder writeMethod : writeMethods) {
193 | matcher.addWriteMethod(writeMethod.buildMethodMatcher());
194 | }
195 | }
196 | return matcher;
197 | }
198 |
199 | /**
200 | * 执行查找
201 | *
202 | * @return 查找到的字段列表
203 | */
204 | public List find() {
205 | try {
206 | // 检查缓存
207 | List cache = DexKitCache.getFieldList(toString());
208 | if (cache != null) {
209 | return cache;
210 | }
211 | // 执行查询
212 | ArrayList fieldResult = new ArrayList<>();
213 | FieldDataList dataList = DexFinder.getDexKitBridge().findField(buildFindField());
214 | if (dataList.isEmpty()) {
215 | DexKitCache.putFieldList(toString(), fieldResult);
216 | return fieldResult;
217 | }
218 | for (FieldData data : dataList) {
219 | Field field = data.getFieldInstance(ClassUtils.getClassLoader());
220 | field.setAccessible(true);
221 | fieldResult.add(field);
222 | }
223 | // 缓存结果
224 | DexKitCache.putFieldList(toString(), fieldResult);
225 | return fieldResult;
226 | } catch (Exception e) {
227 | return new ArrayList<>();
228 | }
229 | }
230 |
231 | /**
232 | * 获取第一个结果或null
233 | *
234 | * @return 第一个找到的字段或null
235 | */
236 | public Field firstOrNull() {
237 | List list = find();
238 | return list.isEmpty() ? null : list.get(0);
239 | }
240 |
241 | /**
242 | * 获取第一个结果或抛异常
243 | *
244 | * @return 第一个找到的字段
245 | * @throws NoSuchFieldException 如果没有找到字段
246 | */
247 | public Field first() throws NoSuchFieldException {
248 | List list = find();
249 | if (list.isEmpty()) throw new NoSuchFieldException("Field not found: " + this);
250 | return list.get(0);
251 | }
252 |
253 | @NonNull
254 | @Override
255 | public String toString() {
256 | StringBuilder sb = new StringBuilder("ff");
257 | if (declaredClass != null) sb.append(declaredClass.getName());
258 | if (fieldName != null) sb.append(fieldName);
259 | if (fieldType != null) sb.append(fieldType.getName());
260 | if (modifiers != -1) sb.append(Modifier.toString(modifiers));
261 | if (!searchPackages.isEmpty()) sb.append(searchPackages);
262 | if (!excludePackages.isEmpty()) sb.append(excludePackages);
263 | if (!readMethods.isEmpty()) sb.append(readMethods);
264 | if (!writeMethods.isEmpty()) sb.append(writeMethods);
265 | return sb.toString();
266 | }
267 | }
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/MethodFinder.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.luckypray.dexkit.query.FindMethod;
5 | import org.luckypray.dexkit.query.enums.MatchType;
6 | import org.luckypray.dexkit.query.matchers.MethodMatcher;
7 | import org.luckypray.dexkit.result.MethodData;
8 | import org.luckypray.dexkit.result.MethodDataList;
9 |
10 | import java.lang.reflect.Field;
11 | import java.lang.reflect.Method;
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.List;
15 |
16 | import top.sacz.xphelper.base.BaseDexQuery;
17 | import top.sacz.xphelper.dexkit.cache.DexKitCache;
18 | import top.sacz.xphelper.reflect.ClassUtils;
19 |
20 | public class MethodFinder extends BaseDexQuery {
21 |
22 | private Class> declaredClass;//方法声明类
23 | private final List> parameters = new ArrayList<>();//方法的参数列表
24 | private String methodName;//方法名称
25 | private Class> returnType;//方法的返回值类型
26 | private final List usedString = new ArrayList<>();//方法中使用的字符串列表
27 | private final List invokeMethods = new ArrayList<>();//方法中调用的方法列表
28 | private final List callMethods = new ArrayList<>();//调用了该方法的方法列表
29 | private final List usingNumbers = new ArrayList<>();//方法中使用的数字列表
30 | private int paramCount = -1;//参数数量
31 | private int modifiers = -1;//修饰符
32 |
33 | private MatchType matchType = MatchType.Contains;
34 | private final List searchPackages = new ArrayList<>();
35 | private final List excludePackages = new ArrayList<>();
36 |
37 | private final List usedFields = new ArrayList<>();
38 |
39 | /**
40 | * 构造实例
41 | *
42 | * @return
43 | */
44 | public static MethodFinder build() {
45 | return new MethodFinder();
46 | }
47 |
48 | /**
49 | * Method转Dexkit的MethodMatcher
50 | *
51 | * @param method
52 | * @return
53 | */
54 | public static MethodMatcher toMethodMatcher(Method method) {
55 | return MethodMatcher.create(method);
56 | }
57 |
58 | /**
59 | * 通过Method构造实例
60 | *
61 | * @param method
62 | * @return
63 | */
64 | public static MethodFinder from(Method method) {
65 | MethodFinder methodFinder = new MethodFinder();
66 | methodFinder.declaredClass = method.getDeclaringClass();
67 | methodFinder.parameters.addAll(Arrays.asList(method.getParameterTypes()));
68 | methodFinder.methodName = method.getName();
69 | methodFinder.returnType = method.getReturnType();
70 | methodFinder.modifiers = method.getModifiers();
71 | methodFinder.matchType = MatchType.Equals;
72 | return methodFinder;
73 | }
74 |
75 | /**
76 | * 设置方法中调用的字段列表
77 | *
78 | * @param fieldFinders
79 | * @return
80 | */
81 | public MethodFinder usedFields(FieldFinder... fieldFinders) {
82 | usedFields.addAll(Arrays.asList(fieldFinders));
83 | return this;
84 | }
85 |
86 | /**
87 | * 设置方法中调用的字段列表
88 | *
89 | * @param fields
90 | * @return
91 | */
92 | public MethodFinder usedFields(Field... fields) {
93 | for (Field field : fields) {
94 | usedFields.add(FieldFinder.from(field));
95 | }
96 | return this;
97 | }
98 |
99 | /**
100 | * 设置方法声明类
101 | *
102 | * @param declaredClass
103 | * @return
104 | */
105 | public MethodFinder declaredClass(Class> declaredClass) {
106 | this.declaredClass = declaredClass;
107 | return this;
108 | }
109 |
110 | /**
111 | * 设置方法的参数列表
112 | *
113 | * @param parameters
114 | * @return
115 | */
116 | public MethodFinder parameters(Class>... parameters) {
117 | this.parameters.addAll(Arrays.asList(parameters));
118 | return this;
119 | }
120 |
121 | /**
122 | * 设置方法名称
123 | *
124 | * @param name
125 | * @return
126 | */
127 | public MethodFinder methodName(String name) {
128 | methodName = name;
129 | return this;
130 | }
131 |
132 | /**
133 | * 设置方法的返回值类型
134 | *
135 | * @param returnTypeClass
136 | * @return
137 | */
138 | public MethodFinder returnType(Class> returnTypeClass) {
139 | returnType = returnTypeClass;
140 | return this;
141 | }
142 |
143 | /**
144 | * 设置方法中调用的方法列表
145 | *
146 | * @param methods
147 | * @return
148 | */
149 | public MethodFinder invokeMethods(Method... methods) {
150 | this.invokeMethods.addAll(Arrays.asList(methods));
151 | return this;
152 | }
153 |
154 | /**
155 | * 设置调用了该方法的方法列表(也就是此方法被哪些方法调用)
156 | *
157 | * @param methods
158 | * @return
159 | */
160 | public MethodFinder callMethods(Method... methods) {
161 | this.callMethods.addAll(Arrays.asList(methods));
162 | return this;
163 | }
164 |
165 | /**
166 | * 设置方法中使用的数字列表
167 | *
168 | * @param numbers
169 | * @return
170 | */
171 | public MethodFinder usingNumbers(long... numbers) {
172 | for (long number : numbers) {
173 | this.usingNumbers.add(number);
174 | }
175 | return this;
176 | }
177 |
178 | /**
179 | * 设置参数数量
180 | *
181 | * @param count
182 | * @return
183 | */
184 | public MethodFinder paramCount(int count) {
185 | this.paramCount = count;
186 | return this;
187 | }
188 |
189 | /**
190 | * 设置方法中使用的字符串列表
191 | *
192 | * @param strings
193 | * @return
194 | */
195 | public MethodFinder usedString(String... strings) {
196 | this.usedString.addAll(Arrays.asList(strings));
197 | return this;
198 | }
199 |
200 | /**
201 | * 设置方法的修饰符
202 | *
203 | * @param modifiers
204 | * @param matchType
205 | * @return
206 | */
207 | public MethodFinder modifiers(int modifiers, MatchType matchType) {
208 | this.modifiers = modifiers;
209 | this.matchType = matchType;
210 | return this;
211 | }
212 |
213 | /**
214 | * 设置搜索的包名列表
215 | *
216 | * @param strings
217 | * @return
218 | */
219 | public MethodFinder searchPackages(String... strings) {
220 | this.searchPackages.addAll(Arrays.asList(strings));
221 | return this;
222 | }
223 |
224 | /**
225 | * 设置排除的包名列表
226 | *
227 | * @param strings
228 | * @return
229 | */
230 | public MethodFinder excludePackages(String... strings) {
231 | this.excludePackages.addAll(Arrays.asList(strings));
232 | return this;
233 | }
234 |
235 | private FindMethod buildFindMethod() {
236 | FindMethod findMethod = FindMethod.create();
237 | if (!searchPackages.isEmpty()) {
238 | findMethod.searchPackages(searchPackages.toArray(new String[0]));
239 | }
240 | if (!excludePackages.isEmpty()) {
241 | findMethod.excludePackages(excludePackages.toArray(new String[0]));
242 | }
243 | return findMethod.matcher(buildMethodMatcher());
244 | }
245 |
246 | /**
247 | * 构造dexkit method方法的匹配条件
248 | * @return
249 | */
250 | public MethodMatcher buildMethodMatcher() {
251 | MethodMatcher methodMatcher = MethodMatcher.create();
252 | if (declaredClass != null) {
253 | methodMatcher.declaredClass(declaredClass);
254 | }
255 | if (methodName != null && !methodName.isEmpty()) {
256 | methodMatcher.name(methodName);
257 | }
258 | if (returnType != null) {
259 | methodMatcher.returnType(returnType);
260 | }
261 | if (!usedString.isEmpty()) {
262 | methodMatcher.usingStrings(usedString.toArray(new String[0]));
263 | }
264 |
265 | if (!parameters.isEmpty()) {
266 | for (Class> parameterClass : parameters) {
267 | methodMatcher.addParamType(parameterClass);
268 | }
269 | }
270 | if (!usedFields.isEmpty()) {
271 | for (FieldFinder usedField : usedFields) {
272 | methodMatcher.addUsingField(usedField.buildFieldMatcher());
273 | }
274 | }
275 | if (!invokeMethods.isEmpty()) {
276 | for (Method invokeMethod : invokeMethods) {
277 | methodMatcher.addInvoke(MethodMatcher.create(invokeMethod));
278 | }
279 | }
280 | if (!callMethods.isEmpty()) {
281 | for (Method callMethod : callMethods) {
282 | methodMatcher.addCaller(MethodMatcher.create(callMethod));
283 | }
284 | }
285 | if (!usingNumbers.isEmpty()) {
286 | for (long usingNumber : usingNumbers) {
287 | methodMatcher.addUsingNumber(usingNumber);
288 | }
289 | }
290 | if (paramCount != -1) {
291 | methodMatcher.paramCount(paramCount);
292 | }
293 | if (modifiers != -1) {
294 | methodMatcher.modifiers(modifiers, matchType);
295 | }
296 | return methodMatcher;
297 | }
298 |
299 | /**
300 | * 查找方法 返回结果列表
301 | *
302 | * @return
303 | */
304 | public List find() {
305 | try {
306 | //先查缓存
307 | List cache = DexKitCache.getMethodList(toString());
308 | if (cache != null) {
309 | return cache;
310 | }
311 | ArrayList methods = new ArrayList<>();
312 | //使用dexkit查找方法
313 | MethodDataList methodDataList = DexFinder.getDexKitBridge().findMethod(buildFindMethod());
314 | if (methodDataList.isEmpty()) {
315 | DexKitCache.putMethodList(toString(), methods);
316 | return methods;
317 | }
318 | for (MethodData methodData : methodDataList) {
319 | Method method = methodData.getMethodInstance(ClassUtils.getClassLoader());
320 | method.setAccessible(true);
321 | methods.add(method);
322 | }
323 | //写入缓存
324 | DexKitCache.putMethodList(toString(), methods);
325 | return methods;
326 | } catch (NoSuchMethodException e) {
327 | return new ArrayList<>();
328 | }
329 | }
330 |
331 | /**
332 | * 查找方法 返回第一个方法 如果不存在则返回null
333 | */
334 | public Method firstOrNull() {
335 | List methods = find();
336 | if (methods.isEmpty()) {
337 | return null;
338 | }
339 | return methods.get(0);
340 | }
341 |
342 | /**
343 | * 查找方法 返回第一个方法 如果不存在则抛出异常
344 | */
345 | public Method first() throws Exception {
346 | List methods = find();
347 | if (methods.isEmpty()) {
348 | throw new NoSuchMethodException("No method found :" + this);
349 | }
350 | return methods.get(0);
351 | }
352 |
353 |
354 | @NotNull
355 | @Override
356 | public String toString() {
357 | StringBuilder builder = new StringBuilder("mf");
358 | //拼入字段值 无需拼入字段名 如果为空则不拼入
359 | if (declaredClass != null) {
360 | builder.append(declaredClass.getName());
361 | }
362 | if (methodName != null && !methodName.isEmpty()) {
363 | builder.append(methodName);
364 | }
365 | if (returnType != null) {
366 | builder.append(returnType.getName());
367 | }
368 | if (!parameters.isEmpty()) {
369 | builder.append((parameters));
370 | }
371 | if (!invokeMethods.isEmpty()) {
372 | builder.append((invokeMethods));
373 | }
374 | if (!callMethods.isEmpty()) {
375 | builder.append((callMethods));
376 | }
377 | if (!usedFields.isEmpty()) {
378 | builder.append((usedFields));
379 | }
380 | if (!usingNumbers.isEmpty()) {
381 | builder.append((usingNumbers));
382 | }
383 | if (paramCount != -1) {
384 | builder.append(paramCount);
385 | }
386 | if (modifiers != -1) {
387 | builder.append(modifiers);
388 | }
389 | if (!usedString.isEmpty()) {
390 | builder.append((usedString));
391 | }
392 | if (!searchPackages.isEmpty()) {
393 | builder.append((searchPackages));
394 | }
395 | if (!excludePackages.isEmpty()) {
396 | builder.append((excludePackages));
397 | }
398 | return builder.toString();
399 | }
400 |
401 | }
402 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/bean/ClassInfo.kt:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit.bean
2 |
3 | import org.luckypray.dexkit.query.enums.MatchType
4 | import top.sacz.xphelper.dexkit.ClassFinder
5 | import top.sacz.xphelper.dexkit.FieldFinder
6 | import top.sacz.xphelper.dexkit.MethodFinder
7 |
8 | class ClassInfo {
9 | var className: String? = null // 类名
10 | var superClass: String? = null // 父类
11 | var interfaces: Array? = null // 实现的接口
12 | var modifiers: Int = -1 // 修饰符
13 | var matchType: MatchType = MatchType.Contains
14 | var searchPackages: Array? = null // 搜索包
15 | var excludePackages: Array? = null // 排除包
16 | var fields: Array? = null // 包含的字段
17 | var methods: Array? = null // 包含的方法
18 | var usedString: Array? = null
19 | fun generate(): ClassFinder {
20 | val finder = ClassFinder.build()
21 | .className(className)
22 | .superClass(superClass)
23 | if (interfaces != null) {
24 | finder.addInterface(*interfaces!!)
25 | }
26 | if (modifiers != -1) {
27 | finder.modifiers(modifiers, matchType)
28 | }
29 | if (searchPackages != null) {
30 | finder.searchPackages(*searchPackages!!)
31 | }
32 | if (excludePackages != null) {
33 | finder.excludePackages(*excludePackages!!)
34 | }
35 | if (fields != null) {
36 | finder.fields(*fields!!)
37 | }
38 | if (usedString != null) {
39 | finder.usedString(*usedString!!)
40 | }
41 | if (methods != null) {
42 | finder.methods(*methods!!)
43 | }
44 | return finder
45 | }
46 | }
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/bean/FieldInfo.kt:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit.bean
2 |
3 | import org.luckypray.dexkit.query.enums.MatchType
4 | import top.sacz.xphelper.dexkit.FieldFinder
5 | import top.sacz.xphelper.dexkit.MethodFinder
6 |
7 | class FieldInfo {
8 | var declaredClass: Class<*>? = null // 字段声明类
9 | var fieldName: String? = null // 字段名称
10 | var fieldType: Class<*>? = null // 字段类型
11 | var modifiers = -1 // 修饰符
12 | var matchType: MatchType = MatchType.Contains
13 | var searchPackages: Array? = null // 搜索包
14 | var excludePackages: Array? = null // 排除包
15 | var readMethods: Array? = null // 读取了该字段的方法
16 | var writeMethods: Array? = null // 写入了该字段的方法
17 |
18 | fun generate(): FieldFinder {
19 | val finder = FieldFinder.build()
20 | .declaredClass(declaredClass)
21 | .fieldName(fieldName)
22 | .fieldType(fieldType)
23 | if (modifiers != -1) {
24 | finder.modifiers(modifiers, matchType)
25 | }
26 | if (searchPackages != null) {
27 | finder.searchPackages(*searchPackages!!)
28 | }
29 | if (excludePackages != null) {
30 | finder.excludePackages(*excludePackages!!)
31 | }
32 | if (readMethods != null) {
33 | finder.readMethods(*readMethods!!)
34 | }
35 | if (writeMethods != null) {
36 | finder.writeMethods(*writeMethods!!)
37 | }
38 | return finder
39 | }
40 | }
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/bean/MethodInfo.kt:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit.bean
2 |
3 | import org.luckypray.dexkit.query.enums.MatchType
4 | import top.sacz.xphelper.dexkit.FieldFinder
5 | import top.sacz.xphelper.dexkit.MethodFinder
6 | import java.lang.reflect.Method
7 |
8 | class MethodInfo {
9 | var declaredClass: Class<*>? = null //方法声明类
10 | var parameters: Array>? = null //方法的参数列表
11 | var methodName: String? = null //方法名称
12 | var returnType: Class<*>? = null //方法的返回值类型
13 | var usedString: Array? = null //方法中使用的字符串列表
14 | var invokeMethods: Array? = null //方法中调用的方法列表
15 | var callMethods: Array? = null //调用了该方法的方法列表
16 | var usingNumbers: LongArray? = null //方法中使用的数字列表
17 | var paramCount = -1 //参数数量
18 | var modifiers = -1 //修饰符
19 | var matchType: MatchType = MatchType.Contains
20 | var searchPackages: Array? = null
21 | var excludePackages: Array? = null
22 | var usedFields: Array? = null
23 |
24 | fun generate(): MethodFinder {
25 | val finder = MethodFinder.build()
26 | .declaredClass(declaredClass)
27 | .methodName(methodName)
28 | .returnType(returnType)
29 | if (parameters != null) {
30 | finder.parameters(*parameters!!)
31 | }
32 | if (usedString != null) {
33 | finder.usedString(*usedString!!)
34 | }
35 | if (invokeMethods != null) {
36 | finder.invokeMethods(*invokeMethods!!)
37 | }
38 | if (callMethods != null) {
39 | finder.callMethods(*callMethods!!)
40 | }
41 | if (usingNumbers != null) {
42 | finder.usingNumbers(*usingNumbers!!)
43 | }
44 | if (usedFields != null) {
45 | finder.usedFields(*usedFields!!)
46 | }
47 | if (searchPackages != null) {
48 | finder.searchPackages(*searchPackages!!)
49 | }
50 | if (excludePackages != null) {
51 | finder.excludePackages(*excludePackages!!)
52 | }
53 | if (modifiers != -1) {
54 | finder.modifiers(modifiers, matchType)
55 | }
56 | if (paramCount != -1) {
57 | finder.paramCount(paramCount)
58 | }
59 | return finder
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/cache/DexKitCache.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit.cache;
2 |
3 |
4 | import android.content.Context;
5 |
6 | import java.lang.reflect.Field;
7 | import java.lang.reflect.Method;
8 | import java.util.List;
9 |
10 | public class DexKitCache {
11 |
12 | public static void clearCache() {
13 | new DexKitCacheProxy().clearCache();
14 | }
15 |
16 | public static void checkCacheExpired(Context context) {
17 | new DexKitCacheProxy().checkCacheExpired(context);
18 | }
19 |
20 | public static boolean exist(String key) {
21 | return new DexKitCacheProxy().keys().contains(key);
22 | }
23 |
24 | public static void putMethodList(String key, List methodList) {
25 | new DexKitCacheProxy().putMethodList(key, methodList);
26 | }
27 |
28 | public static List getMethodList(String key) {
29 | return new DexKitCacheProxy().getMethodList(key);
30 | }
31 |
32 |
33 | public static void putFieldList(String key, List fieldList) {
34 | new DexKitCacheProxy().putFieldList(key, fieldList);
35 | }
36 |
37 | public static List getFieldList(String key) {
38 | return new DexKitCacheProxy().getFieldList(key);
39 | }
40 |
41 | public static void putClassList(String key, List> classList) {
42 | new DexKitCacheProxy().putClassList(key, classList);
43 | }
44 |
45 | public static List> getClassList(String key) {
46 | return new DexKitCacheProxy().getClassList(key);
47 | }
48 |
49 | public static String getAllMethodString() {
50 | return new DexKitCacheProxy().toString();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/dexkit/cache/DexKitCacheProxy.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.dexkit.cache;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 | import android.util.Log;
8 |
9 | import androidx.annotation.NonNull;
10 |
11 | import com.alibaba.fastjson2.JSONArray;
12 | import com.alibaba.fastjson2.JSONObject;
13 | import com.alibaba.fastjson2.TypeReference;
14 |
15 | import java.lang.reflect.Field;
16 | import java.lang.reflect.Method;
17 | import java.util.ArrayList;
18 | import java.util.List;
19 | import java.util.Set;
20 |
21 | import top.sacz.xphelper.reflect.ClassUtils;
22 | import top.sacz.xphelper.reflect.FieldUtils;
23 | import top.sacz.xphelper.reflect.MethodUtils;
24 | import top.sacz.xphelper.util.ConfigUtils;
25 |
26 | public class DexKitCacheProxy {
27 | private static final String TAG = "DexKitCacheProxy";
28 |
29 | ConfigUtils configUtils = new ConfigUtils("DexKitCache");
30 |
31 | public void checkCacheExpired(Context context) {
32 | //获取应用的版本号
33 | try {
34 | String key = "version";
35 | String packageName = context.getPackageName();
36 | PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
37 | String versionName = packageInfo.versionName;
38 | long versionCode = 0;
39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
40 | versionCode = packageInfo.getLongVersionCode();
41 | } else {
42 | versionCode = packageInfo.versionCode;
43 | }
44 | String versionFlag = versionName + "_" + versionCode;
45 | String configFlag = configUtils.getString(key, "");
46 | if (configFlag.equals(versionFlag)) {
47 | return;
48 | }
49 | clearCache();
50 | configUtils.put(key, versionFlag);
51 | Log.d(TAG, "checkCacheExpired: Host version updated Cache cleaned old:" + configFlag + " new:" + versionFlag);
52 | } catch (PackageManager.NameNotFoundException e) {
53 | Log.d(TAG, "checkCacheExpired: " + Log.getStackTraceString(e));
54 | }
55 | }
56 |
57 | public Set keys() {
58 | return configUtils.getAllKeys();
59 | }
60 |
61 | public void clearCache() {
62 | configUtils.clearAll();
63 | }
64 |
65 | public void putMethodList(String key, List methodList) {
66 | ArrayList infoList = new ArrayList<>();
67 | for (Method method : methodList) {
68 | infoList.add(getMethodInfoJSON(method));
69 | }
70 | configUtils.put(key, infoList);
71 | }
72 |
73 | public List getMethodList(String key) {
74 | if (!configUtils.containsKey(key)) {
75 | return null;
76 | }
77 | ArrayList result = new ArrayList<>();
78 | ArrayList methodInfoList = configUtils.getObject(key, new TypeReference<>() {
79 | });
80 | if (methodInfoList != null) {
81 | for (String methodInfo : methodInfoList) {
82 | result.add(findMethodByJSONString(methodInfo));
83 | }
84 | }
85 | return result;
86 | }
87 |
88 | private Method findMethodByJSONString(String methodInfoStrJSON) {
89 | JSONObject methodInfo = JSONObject.parseObject(methodInfoStrJSON);
90 | String methodName = methodInfo.getString("MethodName");
91 | String declareClass = methodInfo.getString("DeclareClass");
92 | String ReturnType = methodInfo.getString("ReturnType");
93 | JSONArray methodParams = methodInfo.getJSONArray("Params");
94 | Class>[] params = new Class[methodParams.size()];
95 | for (int i = 0; i < params.length; i++) {
96 | params[i] = ClassUtils.findClass(methodParams.getString(i));
97 | }
98 | return MethodUtils.create(declareClass)
99 | .methodName(methodName)
100 | .returnType(ClassUtils.findClass(ReturnType))
101 | .params(params)
102 | .first();
103 | }
104 |
105 | private String getMethodInfoJSON(Method method) {
106 | method.setAccessible(true);
107 | JSONObject result = new JSONObject();
108 | String methodName = method.getName();
109 | String declareClass = method.getDeclaringClass().getName();
110 | Class>[] methodParams = method.getParameterTypes();
111 | JSONArray params = new JSONArray();
112 | for (Class> type : methodParams) {
113 | params.add(type.getName());
114 | }
115 | result.put("DeclareClass", declareClass);
116 | result.put("MethodName", methodName);
117 | result.put("Params", params);
118 | result.put("ReturnType", method.getReturnType().getName());
119 | return result.toString();
120 | }
121 |
122 | public void putFieldList(String key, List fieldList) {
123 | ArrayList infoList = new ArrayList<>();
124 | for (Field field : fieldList) {
125 | infoList.add(getFieldInfoJSON(field));
126 | }
127 | configUtils.put(key, infoList);
128 | }
129 |
130 | public List getFieldList(String key) {
131 | if (!configUtils.containsKey(key)) {
132 | return null;
133 | }
134 | ArrayList result = new ArrayList<>();
135 | ArrayList fieldInfoList = configUtils.getObject(key, new TypeReference<>() {
136 | });
137 | if (fieldInfoList != null) {
138 | for (String fieldInfo : fieldInfoList) {
139 | result.add(findFieldByJSONString(fieldInfo));
140 | }
141 | }
142 | return result;
143 | }
144 |
145 | private Field findFieldByJSONString(String fieldInfoStrJSON) {
146 | JSONObject fieldInfo = JSONObject.parseObject(fieldInfoStrJSON);
147 | String fieldName = fieldInfo.getString("FieldName");
148 | String declareClass = fieldInfo.getString("DeclareClass");
149 | String fieldType = fieldInfo.getString("FieldType");
150 | return FieldUtils.create(declareClass)
151 | .fieldName(fieldName)
152 | .fieldType(ClassUtils.findClass(fieldType))
153 | .first();
154 | }
155 |
156 | private String getFieldInfoJSON(Field field) {
157 | field.setAccessible(true);
158 | JSONObject result = new JSONObject();
159 | String fieldName = field.getName();
160 | String declareClass = field.getDeclaringClass().getName();
161 | result.put("DeclareClass", declareClass);
162 | result.put("FieldName", fieldName);
163 | result.put("FieldType", field.getType().getName());
164 | return result.toString();
165 | }
166 |
167 | public void putClassList(String key, List> classList) {
168 | ArrayList infoList = new ArrayList<>();
169 | for (Class> clazz : classList) {
170 | infoList.add(getClassInfoJSON(clazz));
171 | }
172 | configUtils.put(key, infoList);
173 | }
174 |
175 | public List> getClassList(String key) {
176 | if (!configUtils.containsKey(key)) {
177 | return null;
178 | }
179 | ArrayList> result = new ArrayList<>();
180 | ArrayList classInfoList = configUtils.getObject(key, new TypeReference<>() {
181 | });
182 | if (classInfoList != null) {
183 | for (String classInfo : classInfoList) {
184 | result.add(findClassByJSONString(classInfo));
185 | }
186 | }
187 | return result;
188 | }
189 |
190 | public Class> findClassByJSONString(String classInfoJSON) {
191 | JSONObject classInfo = JSONObject.parseObject(classInfoJSON);
192 | String className = classInfo.getString("ClassName");
193 | return ClassUtils.findClass(className);
194 | }
195 |
196 | private String getClassInfoJSON(Class> clazz) {
197 | JSONObject result = new JSONObject();
198 | result.put("ClassName", clazz.getName());
199 | return result.toString();
200 | }
201 |
202 | @Override
203 | @NonNull
204 | public String toString() {
205 | StringBuilder stringBuilder = new StringBuilder();
206 | for (String key : keys()) {
207 | stringBuilder.append(key).append(":").append(getMethodList(key)).append("\n");
208 | }
209 | return stringBuilder.toString();
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/exception/ReflectException.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.exception;
2 |
3 | public class ReflectException extends RuntimeException {
4 | private Exception otherExceptions;
5 |
6 | public ReflectException() {
7 | super();
8 | }
9 |
10 | public ReflectException(String content) {
11 | super(content);
12 | }
13 |
14 | public ReflectException(String content, Exception e) {
15 | super(content);
16 | this.otherExceptions = e;
17 | }
18 |
19 | public boolean hasOtherExceptions() {
20 | return otherExceptions != null;
21 | }
22 |
23 | public Exception getOtherExceptions() {
24 | return this.otherExceptions;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/ext/MemberExt.kt:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.ext
2 |
3 | import top.sacz.xphelper.dexkit.FieldFinder
4 | import top.sacz.xphelper.dexkit.MethodFinder
5 | import top.sacz.xphelper.reflect.ClassUtils
6 | import top.sacz.xphelper.reflect.FieldUtils
7 | import top.sacz.xphelper.reflect.Ignore
8 | import top.sacz.xphelper.reflect.MethodUtils
9 | import top.sacz.xphelper.util.DexMethodDescriptor
10 | import java.lang.reflect.Constructor
11 | import java.lang.reflect.Field
12 | import java.lang.reflect.Method
13 |
14 | /**
15 | * @author suzhelan
16 | * @date 2025/03/11
17 | * xphelper拓展类
18 | */
19 |
20 | fun Method.toMethodFinder(): MethodFinder = MethodFinder.from(this)
21 |
22 | inline val Method.descriptor: String get() = MethodUtils.getDescriptor(this)
23 |
24 | fun Field.toFieldFinder(): FieldFinder = FieldFinder.from(this)
25 |
26 | inline val Field.descriptor: String get() = FieldUtils.getDescriptor(this)
27 |
28 | inline val Constructor<*>.descriptor : String get() = DexMethodDescriptor(this).descriptor
29 |
30 | /**
31 | * 通过描述符获取类
32 | */
33 | fun String.toClass(): Class<*> = ClassUtils.findClass(this)
34 |
35 | /**
36 | * 方法拓展
37 | * 通过方法签名获取方法
38 | */
39 | fun String.toMethod(): Method = MethodUtils.getMethodByDescriptor(this)
40 |
41 | fun String.toMethod(clsLoader: ClassLoader): Method = MethodUtils.getMethodByDescriptor(this, clsLoader)
42 |
43 | fun String.toConstructor(): Constructor<*> {
44 | val constructor = DexMethodDescriptor(this).getConstructorInstance(ClassUtils.getClassLoader())
45 | constructor.isAccessible = true
46 | return constructor
47 | }
48 |
49 | fun String.toConstructor(clsLoader: ClassLoader): Constructor<*> {
50 | val constructor = DexMethodDescriptor(this).getConstructorInstance(clsLoader)
51 | constructor.isAccessible = true
52 | return constructor
53 | }
54 |
55 | /**
56 | * 变量拓展
57 | * 通过变量签名获取变量
58 | */
59 | fun String.toField(): Field = FieldUtils.getFieldByDescriptor(this)
60 |
61 | fun String.toField(clsLoader: ClassLoader): Field = FieldUtils.getFieldByDescriptor(this, clsLoader)
62 |
63 | /**
64 | * 对象拓展 通过对象的方法参数调用
65 | * 不支持静态方法的调用
66 | */
67 | fun Any.callMethod(
68 | methodName: String,
69 | vararg args: Any?
70 | ): T {
71 | val paramTypes = args.map {
72 | if (it == null) {
73 | return@map Ignore::class.java
74 | } else {
75 | return@map it.javaClass
76 | }
77 | }.toTypedArray()
78 | return MethodUtils.create(this)
79 | .methodName(methodName)
80 | .params(*paramTypes)
81 | .callFirst(this, *args)
82 | }
83 |
84 | /**
85 | * 调用类的静态方法
86 | */
87 | fun Class<*>.callStaticMethod(
88 | methodName: String,
89 | vararg args: Any?
90 | ): T {
91 | val paramTypes = args.map {
92 | if (it == null) {
93 | return@map Ignore::class.java
94 | } else {
95 | return@map it.javaClass
96 | }
97 | }.toTypedArray()
98 | return MethodUtils.create(this)
99 | .methodName(methodName)
100 | .params(*paramTypes)
101 | .callFirst(null, *args)
102 | }
103 |
104 | /**
105 | * 对象拓展
106 | * 获取字段值
107 | * 传参 字段名 或者 字段类型
108 | */
109 | fun Any.getFieldValue(name: String? = null, type: Class<*>? = null): T {
110 | return FieldUtils.create(this)
111 | .fieldName(name)
112 | .fieldType(type)
113 | .firstValue(this)
114 | }
115 |
116 | /**
117 | * 获取字段值 传参字段名
118 | */
119 | fun Any.getFieldValue(name: String): T {
120 | return this.getFieldValue(name, null)
121 | }
122 |
123 | /**
124 | * 获取字段值 传参字段类型
125 | */
126 | fun Any.getFieldValue(type: Class<*>): T {
127 | return this.getFieldValue(null, type)
128 | }
129 |
130 | /**
131 | * 对象拓展
132 | * 设置字段值
133 | * 传参 字段名 或者 字段类型
134 | */
135 | fun Any.setFieldValue(name: String? = null, type: Class<*>? = null, value: T) {
136 | FieldUtils.create(this)
137 | .fieldName(name)
138 | .fieldType(type)
139 | .setFirst(this, value)
140 | }
141 |
142 | /**
143 | * 设置字段值 传参字段名
144 | */
145 | fun Any.setFieldValue(name: String, value: T) {
146 | this.setFieldValue(name, null, value)
147 | }
148 |
149 | /**
150 | * 设置字段值 传参字段类型
151 | */
152 | fun Any.setFieldValue(type: Class<*>, value: T) {
153 | this.setFieldValue(null, type, value)
154 | }
155 |
156 | /**
157 | * 类拓展
158 | * 获取静态字段值
159 | * 传参 字段名 或者 字段类型
160 | */
161 | fun Class<*>.getStaticFieldValue(name: String? = null, type: Class<*>? = null): T {
162 | return FieldUtils.create(this)
163 | .fieldName(name)
164 | .fieldType(type)
165 | .firstValue(null)
166 | }
167 |
168 | /**
169 | * 类拓展
170 | * 设置静态字段值
171 | * 传参 字段名 或者 字段类型
172 | */
173 | fun Class<*>.setStaticFieldValue(name: String? = null, type: Class<*>? = null, value: T) {
174 | FieldUtils.create(this)
175 | .fieldName(name)
176 | .fieldType(type)
177 | .setFirst(null, value)
178 | }
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/reflect/ClassUtils.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.reflect;
2 |
3 |
4 | import java.lang.reflect.Array;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import top.sacz.xphelper.exception.ReflectException;
9 |
10 | public class ClassUtils {
11 | private static final Object[][] baseTypes = {{"int", int.class}, {"boolean", boolean.class}, {"byte", byte.class}, {"long", long.class}, {"char", char.class}, {"double", double.class}, {"float", float.class}, {"short", short.class}, {"void", void.class}};
12 | private static ClassLoader classLoader;//宿主应用类加载器
13 |
14 | public static ClassLoader getModuleClassLoader() {
15 | return ClassUtils.class.getClassLoader();
16 | }
17 |
18 | public static ClassLoader getClassLoader() {
19 | return classLoader;
20 | }
21 |
22 | /**
23 | * 获取基本类型
24 | */
25 | private static Class> getBaseTypeClass(String baseTypeName) {
26 | if (baseTypeName.length() == 1) return findSimpleType(baseTypeName.charAt(0));
27 | for (Object[] baseType : baseTypes) {
28 | if (baseTypeName.equals(baseType[0])) {
29 | return (Class>) baseType[1];
30 | }
31 | }
32 | throw new ReflectException(baseTypeName + " <-不是基本的数据类型");
33 | }
34 |
35 | /**
36 | * conversion base type
37 | *
38 | * @param simpleType Smali Base Type V,Z,B,I...
39 | */
40 | private static Class> findSimpleType(char simpleType) {
41 | switch (simpleType) {
42 | case 'V':
43 | return void.class;
44 | case 'Z':
45 | return boolean.class;
46 | case 'B':
47 | return byte.class;
48 | case 'S':
49 | return short.class;
50 | case 'C':
51 | return char.class;
52 | case 'I':
53 | return int.class;
54 | case 'J':
55 | return long.class;
56 | case 'F':
57 | return float.class;
58 | case 'D':
59 | return double.class;
60 | }
61 | throw new RuntimeException("Not an underlying type");
62 | }
63 |
64 | /**
65 | * 排除常用类
66 | */
67 | public static boolean isCommonlyUsedClass(String name) {
68 | return name.startsWith("androidx.") || name.startsWith("android.") || name.startsWith("kotlin.") || name.startsWith("kotlinx.") || name.startsWith("com.tencent.mmkv.") || name.startsWith("com.android.tools.r8.") || name.startsWith("com.google.android.") || name.startsWith("com.google.gson.") || name.startsWith("com.google.common.") || name.startsWith("com.microsoft.appcenter.") || name.startsWith("org.intellij.lang.annotations.") || name.startsWith("org.jetbrains.annotations.");
69 | }
70 |
71 | public static Class> findClassOrNull(String className) {
72 | try {
73 | return findClass(className);
74 | } catch (Exception e) {
75 | return null;
76 | }
77 | }
78 |
79 | /**
80 | * 获取类
81 | */
82 | public static Class> findClass(String className) {
83 | try {
84 | return classLoader.loadClass(className);
85 | } catch (ClassNotFoundException e) {
86 | throw new RuntimeException(e);
87 | }
88 | }
89 |
90 | public static void intiClassLoader(ClassLoader loader) {
91 | if (loader == null) throw new ReflectException("类加载器为Null 无法设置");
92 | //如果我们自己重写了 就不再次继承
93 | if (loader instanceof CacheClassLoader) {
94 | classLoader = loader;
95 | return;
96 | }
97 | classLoader = new CacheClassLoader(loader);
98 | }
99 |
100 |
101 | private static class CacheClassLoader extends ClassLoader {
102 | private static final Map> CLASS_CACHE = new HashMap<>();
103 |
104 | public CacheClassLoader(ClassLoader classLoader) {
105 | super(classLoader);
106 | }
107 |
108 | @Override
109 | public Class> loadClass(String className) throws ClassNotFoundException {
110 | Class> clazz = CLASS_CACHE.get(className);
111 | if (clazz != null) {
112 | return clazz;
113 | }
114 | if (className.endsWith(";") || className.contains("/") || className.contains("L")) {
115 | className = className.replace('/', '.');
116 | // 处理所有 L 开头的情况(无论是否有分号)
117 | if (className.startsWith("L")) {
118 | int endIndex = className.endsWith(";")
119 | ? className.length() - 1
120 | : className.length();
121 | className = className.substring(1, endIndex);
122 | } else if (className.endsWith(";")) {
123 | className = className.substring(0, className.length() - 1);
124 | }
125 | }
126 | if (className.startsWith("[")) {
127 | int dimension = 0;
128 | while (className.charAt(dimension) == '[') {
129 | dimension++;
130 | }
131 | String componentType = className.substring(dimension);
132 | // 递归处理组件类型(确保组件类型被规范化)
133 | Class> componentClass = loadClass(componentType); // 改为递归调用
134 | for (int i = 0; i < dimension; i++) {
135 | componentClass = Array.newInstance(componentClass, 0).getClass();
136 | }
137 | CLASS_CACHE.put(className, componentClass);
138 | return componentClass;
139 | }
140 |
141 | //可能是基础类型
142 | try {
143 | clazz = getBaseTypeClass(className);
144 | } catch (Exception e) {
145 | //因为默认的ClassLoader.load() 不能加载"int"这种类型
146 | clazz = super.loadClass(className);
147 | }
148 | CLASS_CACHE.put(className, clazz);
149 | return clazz;
150 |
151 | }
152 |
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/reflect/ConstructorUtils.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.reflect;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.util.Arrays;
5 | import java.util.List;
6 |
7 | import top.sacz.xphelper.base.BaseFinder;
8 | import top.sacz.xphelper.util.CheckClassType;
9 |
10 | public class ConstructorUtils extends BaseFinder> {
11 |
12 | private int paramCount;
13 | private Class>[] paramTypes;
14 |
15 | public static ConstructorUtils create(Object target) {
16 | return create(target.getClass());
17 | }
18 |
19 | public static ConstructorUtils create(Class> fromClass) {
20 | ConstructorUtils constructorUtils = new ConstructorUtils();
21 | constructorUtils.setDeclaringClass(fromClass);
22 | return constructorUtils;
23 | }
24 |
25 | public static ConstructorUtils create(String fromClassName) {
26 | return create(ClassUtils.findClass(fromClassName));
27 | }
28 |
29 | public static Object newInstance(Class> fromClass, Class>[] paramTypes, Object... args) {
30 | return create(fromClass)
31 | .paramTypes(paramTypes)
32 | .newFirstInstance(args);
33 | }
34 |
35 | public static Object newInstance(Class> fromClass, Object... args) {
36 | Class>[] paramTypes = new Class[args.length];
37 | for (int i = 0; i < args.length; i++) {
38 | paramTypes[i] = args[i].getClass();
39 | }
40 | return newInstance(fromClass, paramTypes, args);
41 | }
42 |
43 | public ConstructorUtils paramCount(int paramCount) {
44 | this.paramCount = paramCount;
45 | return this;
46 | }
47 |
48 | public ConstructorUtils paramTypes(Class>... paramTypes) {
49 | this.paramTypes = paramTypes;
50 | this.paramCount = paramTypes.length;
51 | return this;
52 | }
53 |
54 | private boolean matchParentClass = false;
55 |
56 | public ConstructorUtils matchParentClass(boolean matchParentClass) {
57 | this.matchParentClass = matchParentClass;
58 | return this;
59 | }
60 |
61 | @Override
62 | public BaseFinder> find() {
63 | //查找缓存
64 | List> cache = findConstructorCache();
65 | if (cache != null && !cache.isEmpty()) {
66 | result = cache;
67 | return this;
68 | }
69 | Constructor>[] constructors = getDeclaringClass().getDeclaredConstructors();
70 | result.addAll(Arrays.asList(constructors));
71 | result.removeIf(constructor -> paramCount != 0 && constructor.getParameterCount() != paramCount);
72 | result.removeIf(constructor -> paramTypes != null && !paramEquals(constructor.getParameterTypes()));
73 | writeToConstructorCache(result);
74 | return null;
75 | }
76 |
77 | private boolean paramEquals(Class>[] methodParams) {
78 | for (int i = 0; i < methodParams.length; i++) {
79 | Class> type = methodParams[i];
80 | Class> findType = this.paramTypes[i];
81 | if (findType == Ignore.class || CheckClassType.checkType(type, findType, matchParentClass)) {
82 | continue;
83 | }
84 | return false;
85 | }
86 | return true;
87 | }
88 |
89 | public Object newFirstInstance(Object... args) {
90 | try {
91 | Constructor> firstConstructor = first();
92 | Object instance = firstConstructor.newInstance(args);
93 | return getDeclaringClass().cast(instance);
94 | } catch (Exception e) {
95 | throw new RuntimeException(e);
96 | }
97 | }
98 |
99 | @Override
100 | public String buildSign() {
101 | StringBuilder signBuilder = new StringBuilder()
102 | .append("constructor:")
103 | .append(fromClassName)
104 | .append(" ")
105 | .append(paramCount)
106 | .append(" ")
107 | .append(Arrays.toString(paramTypes));
108 | return signBuilder.toString();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/reflect/FieldUtils.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.reflect;
2 |
3 | import java.lang.reflect.Field;
4 | import java.util.Arrays;
5 | import java.util.List;
6 |
7 | import top.sacz.xphelper.base.BaseFinder;
8 | import top.sacz.xphelper.util.CheckClassType;
9 | import top.sacz.xphelper.util.DexFieldDescriptor;
10 |
11 | public class FieldUtils extends BaseFinder {
12 |
13 | private Class> fieldType;
14 | private String fieldName;
15 |
16 |
17 | public static Field getFieldByDescriptor(String desc) throws NoSuchMethodException {
18 | Field field = new DexFieldDescriptor(desc).getFieldInstance(ClassUtils.getClassLoader());
19 | field.setAccessible(true);
20 | return field;
21 | }
22 |
23 | public static Field getFieldByDescriptor(String desc, ClassLoader classLoader) throws NoSuchMethodException {
24 | Field field = new DexFieldDescriptor(desc).getFieldInstance(classLoader);
25 | field.setAccessible(true);
26 | return field;
27 | }
28 |
29 | public static String getDescriptor(Field field) {
30 | return new DexFieldDescriptor(field).getDescriptor();
31 | }
32 |
33 | /**
34 | * 获取field值
35 | *
36 | * @param targetObj 运行时对象
37 | * @param fieldName 字段名称
38 | * @param fieldType 字段类型
39 | */
40 | public static T getField(Object targetObj, String fieldName, Class> fieldType) {
41 | return create(targetObj.getClass())
42 | .fieldName(fieldName)
43 | .fieldType(fieldType)
44 | .firstValue(targetObj);
45 | }
46 |
47 | /**
48 | * 设置字段值
49 | */
50 | public static void setField(Object target, String fieldName, Object value) {
51 | FieldUtils.create(target)
52 | .fieldName(fieldName)
53 | .fieldType(value.getClass())
54 | .setFirst(target, value);
55 | }
56 |
57 | /**
58 | * 根据字段类型获取首个字段值
59 | */
60 | public static T getFirstType(Object runtimeObj, Class> fieldType) {
61 | return FieldUtils.create(runtimeObj.getClass())
62 | .fieldType(fieldType)
63 | .firstValue(runtimeObj);
64 | }
65 |
66 | /**
67 | * 设置首个为此类型的字段值
68 | */
69 | public static void setFirstType(Object target, Object value) {
70 | FieldUtils.create(target)
71 | .fieldName(value.getClass().getName())
72 | .setFirst(target, value);
73 | }
74 |
75 | public static FieldUtils create(Object target) {
76 | return create(target.getClass());
77 | }
78 |
79 | public static FieldUtils create(Class> fromClass) {
80 | FieldUtils fieldUtils = new FieldUtils();
81 | fieldUtils.setDeclaringClass(fromClass);
82 | return fieldUtils;
83 | }
84 |
85 | public static FieldUtils create(String formClassName) {
86 | return create(ClassUtils.findClass(formClassName));
87 | }
88 |
89 | public FieldUtils fieldType(Class> fieldType) {
90 | this.fieldType = fieldType;
91 | return this;
92 | }
93 |
94 | public FieldUtils fieldName(String fieldName) {
95 | this.fieldName = fieldName;
96 | return this;
97 | }
98 |
99 | private boolean matchParentClass = false;
100 |
101 | public FieldUtils matchParentClass(boolean matchParentClass) {
102 | this.matchParentClass = matchParentClass;
103 | return this;
104 | }
105 | @Override
106 | public FieldUtils find() {
107 | //查找缓存
108 | List cache = findFiledCache();
109 | //缓存不为空直接返回
110 | if (cache != null && !cache.isEmpty()) {
111 | result = cache;
112 | return this;
113 | }
114 | //查找类的所有字段
115 | Field[] declaredFields = getDeclaringClass().getDeclaredFields();
116 | result.addAll(Arrays.asList(declaredFields));
117 | //过滤类型
118 | result.removeIf(field -> fieldType != null && !CheckClassType.checkType(field.getType(), fieldType, matchParentClass));
119 | //过滤名称
120 | result.removeIf(field -> fieldName != null && !field.getName().equals(fieldName));
121 | //写入缓存
122 | writeToFieldCache(result);
123 | return this;
124 | }
125 |
126 | @Override
127 | public String buildSign() {
128 | StringBuilder signBuilder = new StringBuilder();
129 | //构建签名缓存
130 | signBuilder.append("field:")
131 | .append(fromClassName)
132 | .append(" ")
133 | .append(fieldType)
134 | .append(" ")
135 | .append(fieldName);
136 | return signBuilder.toString();
137 | }
138 |
139 | private T tryGetFieldValue(Field field, Object object) {
140 | try {
141 | return (T) field.get(object);
142 | } catch (IllegalAccessException e) {
143 | throw new RuntimeException(e);
144 | }
145 | }
146 |
147 | public T firstValue(Object object) {
148 | Field field = first();
149 | return tryGetFieldValue(field, object);
150 | }
151 |
152 | public T lastValue(Object object) {
153 | Field field = last();
154 | return tryGetFieldValue(field, object);
155 | }
156 |
157 | public FieldUtils setFirst(Object target, Object value) {
158 | Field field = first();
159 | try {
160 | field.set(target, value);
161 | return this;
162 | } catch (IllegalAccessException e) {
163 | throw new RuntimeException(e);
164 | }
165 | }
166 |
167 | public FieldUtils setLast(Object target, Object value) {
168 | Field field = last();
169 | try {
170 | field.set(target, value);
171 | return this;
172 | } catch (IllegalAccessException e) {
173 | throw new RuntimeException(e);
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/reflect/Ignore.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.reflect;
2 |
3 | /**
4 | * 当查找方法传入此类表示此参数可以被忽略
5 | */
6 | public class Ignore {
7 | }
8 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/reflect/MethodUtils.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.reflect;
2 |
3 | import android.util.Log;
4 |
5 | import java.lang.reflect.Method;
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | import top.sacz.xphelper.base.BaseFinder;
10 | import top.sacz.xphelper.util.CheckClassType;
11 | import top.sacz.xphelper.util.DexMethodDescriptor;
12 |
13 | public class MethodUtils extends BaseFinder {
14 |
15 | private String methodName;
16 | private Class> returnType;
17 | private Class>[] methodParams;
18 | private Integer paramCount;
19 | private boolean matchParentClass = false;
20 |
21 | public static Method getMethodByDescriptor(String desc) throws NoSuchMethodException {
22 | Method method = new DexMethodDescriptor(desc).getMethodInstance(ClassUtils.getClassLoader());
23 | method.setAccessible(true);
24 | return method;
25 | }
26 |
27 | public static String getDescriptor(Method method) {
28 | return new DexMethodDescriptor(method).getDescriptor();
29 | }
30 |
31 | public static Method getMethodByDescriptor(String desc, ClassLoader classLoader) throws NoSuchMethodException {
32 | Method method = new DexMethodDescriptor(desc).getMethodInstance(classLoader);
33 | method.setAccessible(true);
34 | return method;
35 | }
36 |
37 | public MethodUtils matchParentClass(boolean matchParentClass) {
38 | this.matchParentClass = matchParentClass;
39 | return this;
40 | }
41 |
42 | public static MethodUtils create(Object target) {
43 | return create(target.getClass());
44 | }
45 |
46 | public static MethodUtils create(Class> fromClass) {
47 | MethodUtils methodUtils = new MethodUtils();
48 | methodUtils.setDeclaringClass(fromClass);
49 | return methodUtils;
50 | }
51 |
52 | public static MethodUtils create(String formClassName) {
53 | return create(ClassUtils.findClass(formClassName));
54 | }
55 |
56 | public MethodUtils returnType(Class> returnType) {
57 | this.returnType = returnType;
58 | return this;
59 | }
60 |
61 | public MethodUtils methodName(String methodName) {
62 | this.methodName = methodName;
63 | return this;
64 | }
65 |
66 | public MethodUtils params(Class>... methodParams) {
67 | this.methodParams = methodParams;
68 | this.paramCount = methodParams.length;
69 | return this;
70 | }
71 |
72 | public MethodUtils paramCount(int paramCount) {
73 | this.paramCount = paramCount;
74 | return this;
75 | }
76 |
77 | @Override
78 | public BaseFinder find() {
79 | List cache = findMethodCache();
80 | if (cache != null && !cache.isEmpty()) {
81 | result = cache;
82 | return this;
83 | }
84 | Method[] methods = getDeclaringClass().getDeclaredMethods();
85 | result.addAll(Arrays.asList(methods));
86 | result.removeIf(method -> methodName != null && !method.getName().equals(methodName));
87 | result.removeIf(method -> returnType != null && !CheckClassType.checkType(method.getReturnType(), returnType, matchParentClass));
88 | result.removeIf(method -> paramCount != null && method.getParameterCount() != paramCount);
89 | result.removeIf(method -> methodParams != null && !paramEquals(method.getParameterTypes()));
90 | writeToMethodCache(result);
91 | return this;
92 | }
93 |
94 | private boolean paramEquals(Class>[] methodParams) {
95 | for (int i = 0; i < methodParams.length; i++) {
96 | Class> type = methodParams[i];
97 | Class> findType = this.methodParams[i];
98 | if (findType == Ignore.class || CheckClassType.checkType(type, findType, matchParentClass)) {
99 | continue;
100 | }
101 | return false;
102 | }
103 | return true;
104 | }
105 |
106 | @Override
107 | public String buildSign() {
108 | String build = "method:" +
109 | fromClassName +
110 | " " +
111 | returnType +
112 | " " +
113 | methodName +
114 | "(" +
115 | paramCount +
116 | Arrays.toString(methodParams) +
117 | ")";
118 | return build;
119 | }
120 |
121 | private T tryCall(Method method, Object object, Object... args) {
122 | try {
123 | Log.d("MethodTool", "tryCall: " + method + " obj=" + object + " args " + Arrays.toString(args));
124 | return (T) method.invoke(object, args);
125 | } catch (Exception e) {
126 | throw new RuntimeException(e);
127 | }
128 | }
129 |
130 | public T callFirstStatic(Object... args) {
131 | Method method = first();
132 | return tryCall(method, null, args);
133 | }
134 |
135 | public T callFirst(Object runTimeObj, Object... args) {
136 | Method method = first();
137 | return tryCall(method, runTimeObj, args);
138 | }
139 |
140 | public T callLast(Object object, Object... args) {
141 | Method method = last();
142 | return tryCall(method, object, args);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/util/AESHelper.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.util;
2 |
3 | import java.nio.charset.Charset;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | import javax.crypto.Cipher;
7 | import javax.crypto.spec.SecretKeySpec;
8 |
9 | /**
10 | * AES对称加密解密类
11 | **/
12 | public class AESHelper {
13 |
14 | /**
15 | * 加密算法/模式/填充模式
16 | */
17 | private static final String CipherMode = "AES/ECB/PKCS5Padding";
18 |
19 | private static final Charset UTF_8 = StandardCharsets.UTF_8;
20 |
21 | /**
22 | * 加密字节数据
23 | **/
24 | public static byte[] encrypt(byte[] content, String password) {
25 | try {
26 | SecretKeySpec key = createKey(password);
27 | Cipher cipher = Cipher.getInstance(CipherMode);
28 | cipher.init(Cipher.ENCRYPT_MODE, key);
29 | return cipher.doFinal(content);
30 | } catch (Exception e) {
31 | e.printStackTrace();
32 | }
33 | return null;
34 | }
35 |
36 | /**
37 | * 加密字符串
38 | *
39 | * @param content 内容
40 | * @param password 密钥
41 | * @return 密文
42 | */
43 | public static String encrypt(String content, String password) {
44 | byte[] data = null;
45 | try {
46 | data = content.getBytes(UTF_8);
47 | } catch (Exception e) {
48 | e.printStackTrace();
49 | }
50 | data = encrypt(data, password);
51 | String result = null;
52 | if (data != null) {
53 | result = byte2hex(data);
54 | }
55 | return result;
56 | }
57 |
58 |
59 | /**
60 | * 解密字节数据
61 | */
62 | public static byte[] decrypt(byte[] content, String password) {
63 | try {
64 | SecretKeySpec key = createKey(password);
65 | Cipher cipher = Cipher.getInstance(CipherMode);
66 | cipher.init(Cipher.DECRYPT_MODE, key);
67 | return cipher.doFinal(content);
68 | } catch (Exception e) {
69 | e.printStackTrace();
70 | }
71 | return null;
72 | }
73 |
74 | /**
75 | * 解密字符串
76 | */
77 | public static String decrypt(String content, String password) {
78 | byte[] data = null;
79 | try {
80 | data = hex2byte(content);
81 | } catch (Exception e) {
82 | e.printStackTrace();
83 | }
84 | data = decrypt(data, password);
85 | if (data == null) return null;
86 | String result;
87 | result = new String(data, UTF_8);
88 | return result;
89 | }
90 |
91 |
92 | private static SecretKeySpec createKey(String password) {
93 | if (password == null) {
94 | password = "";
95 | }
96 | StringBuilder sb = new StringBuilder(32);
97 | sb.append(password);
98 | //不足32位补满32位
99 | while (sb.length() < 32) {
100 | sb.append("0");
101 | }
102 | //超过32位删除32位之后的
103 | if (sb.length() > 32) {
104 | sb.setLength(32);
105 | }
106 | byte[] data = sb.toString().getBytes(UTF_8);
107 | return new SecretKeySpec(data, "AES");
108 | }
109 |
110 | private static String byte2hex(byte[] b) { // 一个字节的数,
111 | StringBuilder sb = new StringBuilder(b.length * 2);
112 | String tmp;
113 | for (byte value : b) {
114 | // 整数转成十六进制表示
115 | tmp = (Integer.toHexString(value & 0XFF));
116 | if (tmp.length() == 1) {
117 | sb.append("0");
118 | }
119 | sb.append(tmp);
120 | }
121 | return sb.toString().toUpperCase(); // 转成大写
122 | }
123 |
124 |
125 | private static byte[] hex2byte(String inputString) {
126 | if (inputString == null || inputString.length() < 2) {
127 | return new byte[0];
128 | }
129 | inputString = inputString.toLowerCase();
130 | int l = inputString.length() / 2;
131 | byte[] result = new byte[l];
132 | for (int i = 0; i < l; ++i) {
133 | String tmp = inputString.substring(2 * i, 2 * i + 2);
134 | result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF);
135 | }
136 | return result;
137 | }
138 | }
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/util/ActivityTools.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.util;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.content.pm.ActivityInfo;
7 | import android.content.pm.PackageInfo;
8 | import android.content.pm.PackageManager;
9 | import android.content.res.AssetManager;
10 | import android.content.res.Resources;
11 | import android.content.res.loader.ResourcesLoader;
12 | import android.content.res.loader.ResourcesProvider;
13 | import android.os.Build;
14 | import android.os.Handler;
15 | import android.os.Looper;
16 | import android.os.ParcelFileDescriptor;
17 | import android.util.Log;
18 | import android.view.View;
19 | import android.view.ViewGroup;
20 |
21 | import java.io.File;
22 | import java.io.IOException;
23 | import java.lang.reflect.Field;
24 | import java.lang.reflect.Method;
25 | import java.util.ArrayList;
26 | import java.util.List;
27 | import java.util.Map;
28 |
29 | public class ActivityTools {
30 | private static ResourcesLoader resourcesLoader = null;
31 |
32 | /**
33 | * 获取所有声明在AndroidManifest中已经有注册过的activityInfo
34 | *
35 | * @param context 上下文
36 | */
37 | public static ActivityInfo[] getAllActivity(Context context) {
38 | PackageManager packageManager = context.getPackageManager();
39 | PackageInfo packageInfo;
40 | try {
41 | packageInfo = packageManager.getPackageInfo(
42 | context.getPackageName(), PackageManager.GET_ACTIVITIES);
43 | //所有的Activity
44 | ActivityInfo[] activities = packageInfo.activities;
45 | return activities;
46 |
47 | } catch (PackageManager.NameNotFoundException e) {
48 | throw new RuntimeException(e);
49 | }
50 | }
51 | public static List getAllChildViews(Activity activity) {
52 | View view = activity.getWindow().getDecorView();
53 | return getAllChildViews(view);
54 |
55 | }
56 |
57 | public static List getAllChildViews(View view) {
58 | List allChildren = new ArrayList<>();
59 | if (view instanceof ViewGroup) {
60 | ViewGroup vp = (ViewGroup) view;
61 | for (int i = 0; i < vp.getChildCount(); i++) {
62 | View views = vp.getChildAt(i);
63 | allChildren.add(views);
64 | //递归调用
65 | allChildren.addAll(getAllChildViews(views));
66 | }
67 | }
68 | return allChildren;
69 | }
70 | public static void injectResourcesToContext(Context context, String moduleApkPath) {
71 | Resources res = context.getResources();
72 | if (Build.VERSION.SDK_INT >= 30) {
73 | ActivityTools.injectResourcesAboveApi30(res, moduleApkPath);
74 | } else {
75 | ActivityTools.injectResourcesBelowApi30(res, moduleApkPath);
76 | }
77 | }
78 |
79 | /**
80 | * 获取当前正在运行的Activity
81 | */
82 | @SuppressLint("PrivateApi")
83 | public static Activity getTopActivity() {
84 | Class> activityThreadClass;
85 | try {
86 | activityThreadClass = Class.forName("android.app.ActivityThread");
87 | //获取当前活动线程
88 | Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null);
89 | @SuppressLint("DiscouragedPrivateApi")
90 | Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
91 | activitiesField.setAccessible(true);
92 | //获取线程Map
93 | Map, ?> activities = (Map, ?>) activitiesField.get(activityThread);
94 | if (activities == null) return null;
95 | for (Object activityRecord : activities.values()) {
96 | Class> activityRecordClass = activityRecord.getClass();
97 | //获取暂停状态
98 | Field pausedField = activityRecordClass.getDeclaredField("paused");
99 | pausedField.setAccessible(true);
100 | //不是暂停状态的话那就是当前正在运行的Activity
101 | if (!pausedField.getBoolean(activityRecord)) {
102 | Field activityField = activityRecordClass.getDeclaredField("activity");
103 | activityField.setAccessible(true);
104 | return (Activity) activityField.get(activityRecord);
105 | }
106 | }
107 | } catch (Exception e) {
108 |
109 | }
110 | return null;
111 | }
112 |
113 | public static void runOnUiThread(Runnable task) {
114 | if (Looper.myLooper() == Looper.getMainLooper()) {
115 | task.run();
116 | } else {
117 | Handler handler = new Handler(Looper.getMainLooper());
118 | handler.postDelayed(task, 0L);
119 | }
120 | }
121 |
122 | @SuppressLint("NewApi")
123 | private static void injectResourcesAboveApi30(Resources res, String path) {
124 | if (resourcesLoader == null) {
125 | try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(path),
126 | ParcelFileDescriptor.MODE_READ_ONLY)) {
127 | ResourcesProvider provider = ResourcesProvider.loadFromApk(pfd);
128 | ResourcesLoader loader = new ResourcesLoader();
129 | loader.addProvider(provider);
130 | resourcesLoader = loader;
131 | } catch (IOException e) {
132 | return;
133 | }
134 | }
135 | runOnUiThread(() -> {
136 | try {
137 | res.addLoaders(resourcesLoader);
138 | injectResourcesBelowApi30(res, path);
139 | } catch (IllegalArgumentException e) {
140 | String expected1 = "Cannot modify resource loaders of ResourcesImpl not registered with ResourcesManager";
141 | if (expected1.equals(e.getMessage())) {
142 | Log.e("ActivityProxy", Log.getStackTraceString(e));
143 | // fallback to below API 30
144 | injectResourcesBelowApi30(res, path);
145 | } else {
146 | throw e;
147 | }
148 | }
149 | });
150 | }
151 |
152 | private static void injectResourcesBelowApi30(Resources res, String path) {
153 | try {
154 | AssetManager assetManager = res.getAssets();
155 | @SuppressLint("DiscouragedPrivateApi")
156 | Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
157 | method.setAccessible(true);
158 | method.invoke(assetManager, path);
159 | } catch (Exception ignored) {
160 | }
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/util/CheckClassType.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.util;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class CheckClassType {
7 |
8 | /* private final static Class>[][] typeArray = {
9 | {boolean.class, Boolean.class},
10 | {int.class, Integer.class},
11 | {long.class, Long.class},
12 | {byte.class, Byte.class},
13 | {short.class, Short.class},
14 | {float.class, Float.class},
15 | {double.class, Double.class},
16 | {char.class, Character.class},
17 | {boolean[].class, Boolean[].class},
18 | {int[].class, Integer[].class},
19 | {long[].class, Long[].class},
20 | };*/
21 | private final static Map, Class>> TYPE_MAP = new HashMap<>();
22 |
23 | static {
24 | TYPE_MAP.put(boolean.class, Boolean.class);
25 | TYPE_MAP.put(int.class, Integer.class);
26 | TYPE_MAP.put(long.class, Long.class);
27 | TYPE_MAP.put(byte.class, Byte.class);
28 | TYPE_MAP.put(short.class, Short.class);
29 | TYPE_MAP.put(float.class, Float.class);
30 | TYPE_MAP.put(double.class, Double.class);
31 | TYPE_MAP.put(char.class, Character.class);
32 |
33 | TYPE_MAP.put(boolean[].class, Boolean[].class);
34 | TYPE_MAP.put(int[].class, Integer[].class);
35 | TYPE_MAP.put(long[].class, Long[].class);
36 | TYPE_MAP.put(char[].class, Character[].class);
37 | TYPE_MAP.put(short[].class, Short[].class);
38 | TYPE_MAP.put(byte[].class, Byte[].class);
39 | TYPE_MAP.put(float[].class, Float[].class);
40 | TYPE_MAP.put(double[].class, Double[].class);
41 | }
42 |
43 | //缩小范围匹配字节引用类型
44 |
45 | /**
46 | * @param methodParamClz 方法参数
47 | * @param convert 目标匹配参数 可传入实际参数的父类增加匹配范围,传入子类不会命中
48 | */
49 | public static boolean checkType(Class> methodParamClz, Class> convert, boolean matchParentClass) {
50 | if (methodParamClz.equals(convert)) return true;
51 | if (methodParamClz.equals(hasWarpClass(convert))) return true;
52 | if (methodParamClz.equals(hasBaseClass(convert))) return true;
53 | //左边是不是右边的父类 在TextViw.class.isAssignableFrom(View.class)时为false , 可以避免一些问题
54 | return matchParentClass && (convert.isAssignableFrom(methodParamClz) || methodParamClz.isAssignableFrom(convert));
55 | }
56 |
57 | private static Class> hasWarpClass(Class> targetClz) {
58 | if (TYPE_MAP.containsKey(targetClz)) {
59 | return TYPE_MAP.get(targetClz);
60 | }
61 | return null;
62 | }
63 |
64 | private static Class> hasBaseClass(Class> target) {
65 | if (TYPE_MAP.containsValue(target)) {
66 | //获取指定key
67 | for (Map.Entry, Class>> entry : TYPE_MAP.entrySet()) {
68 | if (entry.getValue().equals(target)) {
69 | return entry.getKey();
70 | }
71 | }
72 | }
73 | return null;
74 | }
75 |
76 | private static Class> hasWarpType(Class> baseType) {
77 | if (baseType.equals(boolean.class)) return Boolean.class;
78 | if (baseType.equals(int.class)) return Integer.class;
79 | if (baseType.equals(long.class)) return Long.class;
80 | if (baseType.equals(byte.class)) return Byte.class;
81 | if (baseType.equals(short.class)) return Short.class;
82 | if (baseType.equals(float.class)) return Float.class;
83 | if (baseType.equals(double.class)) return Double.class;
84 | if (baseType.equals(char.class)) return Character.class;
85 | return null;
86 | }
87 |
88 | private static Class> hasType(Class> clz) {
89 | if (clz.equals(Boolean.class)) return boolean.class;
90 | if (clz.equals(Integer.class)) return int.class;
91 | if (clz.equals(Long.class)) return long.class;
92 | if (clz.equals(Byte.class)) return byte.class;
93 | if (clz.equals(Short.class)) return short.class;
94 | if (clz.equals(Float.class)) return float.class;
95 | if (clz.equals(Double.class)) return double.class;
96 | if (clz.equals(Character.class)) return char.class;
97 | return null;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/util/ConfigUtils.kt:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.util
2 |
3 | import android.content.Context
4 | import com.alibaba.fastjson2.JSON
5 | import com.alibaba.fastjson2.TypeReference
6 | import io.fastkv.FastKV
7 | import io.fastkv.interfaces.FastCipher
8 |
9 |
10 | /**
11 | * 简单数据存储类
12 | * 完整构造方法 默认会生成无密码文件名为default的数据库
13 | * 如果需要加密传入密码
14 | */
15 | class ConfigUtils @JvmOverloads constructor(
16 | key: String = "default",
17 | password: String = globalPassword
18 | ) {
19 |
20 | private var id: String = key
21 |
22 | private var kv: FastKV
23 |
24 |
25 | init {
26 | if (storePath.isEmpty()) {
27 | throw RuntimeException("storePath is empty(请使用KvHelper.initialize(String path)初始化")
28 | }
29 | kv = if (globalPassword.isEmpty()) {
30 | FastKV.Builder(storePath, id)
31 | .build()
32 | } else {
33 | FastKV.Builder(storePath, id)
34 | .cipher(Cipher(password))
35 | .build()
36 | }
37 | }
38 |
39 |
40 | companion object {
41 | private var storePath = ""
42 |
43 | private var globalPassword = ""
44 |
45 | /**
46 | * 初始化 传入文件夹路径
47 | */
48 | @JvmStatic
49 | fun initialize(path: String) {
50 | storePath = path
51 | }
52 |
53 | @JvmStatic
54 | fun setGlobalPassword(password: String) {
55 | globalPassword = password
56 | }
57 |
58 | @JvmStatic
59 | fun initialize(context: Context) {
60 | storePath = context.filesDir.absolutePath + "/XpHelper"
61 | }
62 | }
63 |
64 | /**
65 | * 保存数据的方法
66 | * @param key
67 | * @param value
68 | */
69 | fun put(key: String, value: Any) {
70 | when (value) {
71 | is String -> {
72 | kv.putString(key, value)
73 | }
74 |
75 | is Int -> {
76 | kv.putInt(key, value)
77 | }
78 |
79 | is Boolean -> {
80 | kv.putBoolean(key, value)
81 | }
82 |
83 | is Float -> {
84 | kv.putFloat(key, value)
85 | }
86 |
87 | is Long -> {
88 | kv.putLong(key, value)
89 | }
90 |
91 | is Double -> {
92 | kv.putDouble(key, value)
93 | }
94 |
95 | is ByteArray -> {
96 | kv.putArray(key, value)
97 | }
98 |
99 | else -> {
100 | kv.putString(key, JSON.toJSONString(value))
101 | }
102 | }
103 | }
104 |
105 |
106 | /**
107 | * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
108 | */
109 | fun getInt(key: String, def: Int = 0): Int {
110 | return kv.getInt(key, def)
111 | }
112 |
113 | fun getDouble(key: String, def: Double = 0.00): Double {
114 | return kv.getDouble(key, def)
115 | }
116 |
117 | fun getLong(key: String, def: Long = 0L): Long {
118 | return kv.getLong(key, def)
119 | }
120 |
121 | fun getBoolean(key: String, def: Boolean = false): Boolean {
122 | return kv.getBoolean(key, def)
123 | }
124 |
125 | fun getFloat(key: String, def: Float = 0f): Float {
126 | return kv.getFloat(key, def)
127 | }
128 |
129 | fun getBytes(key: String, def: ByteArray = byteArrayOf()): ByteArray {
130 | return kv.getArray(key, def)
131 | }
132 |
133 | fun getString(key: String, def: String = ""): String {
134 | return kv.getString(key, def) ?: ""
135 | }
136 |
137 |
138 | fun getObject(key: String, clz: Class): T? {
139 | val data = kv.getString(key)
140 | if (data.isNullOrEmpty()) {
141 | return null
142 | }
143 | return JSON.parseObject(data, clz)
144 | }
145 |
146 |
147 | fun getObject(key: String, type: TypeReference): T? {
148 | val data = kv.getString(key)
149 | if (data.isNullOrEmpty()) {
150 | return null
151 | }
152 | return JSON.parseObject(data, type)
153 | }
154 |
155 |
156 | fun getList(key: String, clazz: Class): MutableList {
157 | val data = kv.getString(key)
158 | if (data.isNullOrEmpty()) {
159 | return ArrayList()
160 | }
161 | return JSON.parseArray(data, clazz)
162 | }
163 |
164 | /**
165 | * 批量保存数据
166 | */
167 | fun putAll(map: Map) {
168 | kv.putAll(map)
169 | }
170 |
171 | /**
172 | * 转Map
173 | */
174 | fun toMap(): Map {
175 | return kv.all
176 | }
177 |
178 | /**
179 | * 清除所有key
180 | */
181 | fun clearAll() {
182 | kv.clear()
183 | }
184 |
185 | fun remove(key: String) {
186 | kv.remove(key)
187 | }
188 |
189 | /**
190 | * 获取所有key
191 | */
192 | fun getAllKeys(): MutableSet {
193 | return kv.all.keys
194 | }
195 |
196 | /**
197 | * 是否包含某个key
198 | */
199 | fun containsKey(key: String): Boolean {
200 | return kv.contains(key)
201 | }
202 |
203 |
204 | /**
205 | * fast kv的加密实现接口
206 | */
207 | private class Cipher(val key: String) : FastCipher {
208 | override fun encrypt(src: ByteArray): ByteArray {
209 | return AESHelper.encrypt(src, key)
210 | }
211 |
212 | override fun decrypt(dst: ByteArray): ByteArray {
213 | return AESHelper.decrypt(dst, key)
214 | }
215 |
216 | override fun encrypt(src: Int): Int {
217 | return src
218 | }
219 |
220 | override fun encrypt(src: Long): Long {
221 | return src
222 | }
223 |
224 | override fun decrypt(dst: Int): Int {
225 | return dst
226 | }
227 |
228 | override fun decrypt(dst: Long): Long {
229 | return dst
230 | }
231 |
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/util/DexFieldDescriptor.java:
--------------------------------------------------------------------------------
1 | package top.sacz.xphelper.util;
2 |
3 |
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.lang.reflect.Field;
7 | import java.lang.reflect.Modifier;
8 |
9 | public class DexFieldDescriptor {
10 |
11 | /**
12 | * Ljava/lang/Object;
13 | */
14 | public final String declaringClass;
15 | /**
16 | * shadow$_klass_
17 | */
18 | public final String name;
19 | /**
20 | * Ljava/lang/Class;
21 | */
22 | public final String type;
23 |
24 | public DexFieldDescriptor(String desc) {
25 | if (desc == null) {
26 | throw new NullPointerException();
27 | }
28 | int a = desc.indexOf("->");
29 | int b = desc.indexOf(':', a);
30 | declaringClass = desc.substring(0, a);
31 | name = desc.substring(a + 2, b);
32 | type = desc.substring(b + 1);
33 | }
34 |
35 | public DexFieldDescriptor(Field field) {
36 | if (field == null) {
37 | throw new NullPointerException();
38 | }
39 | declaringClass = getTypeSig(field.getDeclaringClass());
40 | name = field.getName();
41 | type = getTypeSig(field.getType());
42 | }
43 |
44 | public DexFieldDescriptor(String clz, String n, String t) {
45 | if (clz == null || n == null || t == null) {
46 | throw new NullPointerException();
47 | }
48 | declaringClass = clz;
49 | name = n;
50 | type = t;
51 | }
52 |
53 | public static String getTypeSig(final Class> type) {
54 | if (type.isPrimitive()) {
55 | if (Integer.TYPE.equals(type)) {
56 | return "I";
57 | }
58 | if (Void.TYPE.equals(type)) {
59 | return "V";
60 | }
61 | if (Boolean.TYPE.equals(type)) {
62 | return "Z";
63 | }
64 | if (Character.TYPE.equals(type)) {
65 | return "C";
66 | }
67 | if (Byte.TYPE.equals(type)) {
68 | return "B";
69 | }
70 | if (Short.TYPE.equals(type)) {
71 | return "S";
72 | }
73 | if (Float.TYPE.equals(type)) {
74 | return "F";
75 | }
76 | if (Long.TYPE.equals(type)) {
77 | return "J";
78 | }
79 | if (Double.TYPE.equals(type)) {
80 | return "D";
81 | }
82 | throw new IllegalStateException("Type: " + type.getName() + " is not a primitive type");
83 | }
84 | if (type.isArray()) {
85 | return "[" + getTypeSig(type.getComponentType());
86 | }
87 | return "L" + type.getName().replace('.', '/') + ";";
88 | }
89 |
90 | public String getDeclaringClassName() {
91 | return declaringClass.substring(1, declaringClass.length() - 1).replace('/', '.');
92 | }
93 |
94 | public String getDescriptor() {
95 | return declaringClass + "->" + name + ":" + type;
96 | }
97 |
98 | @Override
99 | public String toString() {
100 | return getDescriptor();
101 | }
102 |
103 | @Override
104 | public boolean equals(Object o) {
105 | if (this == o) {
106 | return true;
107 | }
108 | if (o == null || getClass() != o.getClass()) {
109 | return false;
110 | }
111 | return toString().equals(o.toString());
112 | }
113 |
114 | @Override
115 | public int hashCode() {
116 | return toString().hashCode();
117 | }
118 |
119 | public Field getFieldInstance(ClassLoader classLoader) throws NoSuchMethodException {
120 | try {
121 | Class> clz = classLoader.loadClass(
122 | declaringClass.substring(1, declaringClass.length() - 1).replace('/', '.'));
123 | for (Field f : clz.getDeclaredFields()) {
124 | if (f.getName().equals(name) && getTypeSig(f.getType()).equals(type)) {
125 | return f;
126 | }
127 | }
128 | while ((clz = clz.getSuperclass()) != null) {
129 | for (Field f : clz.getDeclaredFields()) {
130 | if (Modifier.isPrivate(f.getModifiers()) || Modifier
131 | .isStatic(f.getModifiers())) {
132 | continue;
133 | }
134 | if (f.getName().equals(name) && getTypeSig(f.getType()).equals(type)) {
135 | return f;
136 | }
137 | }
138 | }
139 | throw new NoSuchMethodException(declaringClass + "->" + name + ":" + type);
140 | } catch (ClassNotFoundException e) {
141 | throw (NoSuchMethodException) new NoSuchMethodException(
142 | declaringClass + "->" + name + ":" + type).initCause(e);
143 | }
144 | }
145 |
146 | }
147 |
148 |
--------------------------------------------------------------------------------
/xphelper/src/main/java/top/sacz/xphelper/util/DexMethodDescriptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * QAuxiliary - An Xposed module for QQ/TIM
3 | * Copyright (C) 2019-2022 qwq233@qwq2333.top
4 | * https://github.com/cinit/QAuxiliary
5 | *
6 | * This software is non-free but opensource software: you can redistribute it
7 | * and/or modify it under the terms of the GNU Affero General Public License
8 | * as published by the Free Software Foundation; either
9 | * version 3 of the License, or any later version and our eula as published
10 | * by QAuxiliary contributors.
11 | *
12 | * This software is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 | * Affero General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU Affero General Public License
18 | * and eula along with this software. If not, see
19 | *
20 | * .
21 | */
22 | package top.sacz.xphelper.util;
23 |
24 |
25 | import java.io.Serializable;
26 | import java.lang.reflect.Constructor;
27 | import java.lang.reflect.Method;
28 | import java.lang.reflect.Modifier;
29 | import java.util.ArrayList;
30 | import java.util.List;
31 |
32 | public class DexMethodDescriptor implements Serializable {
33 |
34 | /**
35 | * Ljava/lang/Object;
36 | */
37 | public final String declaringClass;
38 | /**
39 | * toString
40 | */
41 | public final String name;
42 | /**
43 | * ()Ljava/lang/String;
44 | */
45 | public final String signature;
46 |
47 | public DexMethodDescriptor(Method method) {
48 | if (method == null) {
49 | throw new NullPointerException();
50 | }
51 | declaringClass = getTypeSig(method.getDeclaringClass());
52 | name = method.getName();
53 | signature = getMethodTypeSig(method);
54 | }
55 |
56 | public DexMethodDescriptor(Constructor> ctor) {
57 | if (ctor == null) {
58 | throw new NullPointerException();
59 | }
60 | declaringClass = getTypeSig(ctor.getDeclaringClass());
61 | name = "";
62 | signature = getConstructorTypeSig(ctor);
63 | }
64 |
65 | public DexMethodDescriptor(String desc) {
66 | if (desc == null) {
67 | throw new NullPointerException();
68 | }
69 | int a = desc.indexOf("->");
70 | int b = desc.indexOf('(', a);
71 | if (a < 0 || b < 0) {
72 | throw new IllegalArgumentException(desc);
73 | }
74 | declaringClass = desc.substring(0, a);
75 | name = desc.substring(a + 2, b);
76 | signature = desc.substring(b);
77 | }
78 |
79 | public DexMethodDescriptor(String clz, String n, String s) {
80 | if (clz == null || n == null || s == null) {
81 | throw new NullPointerException();
82 | }
83 | declaringClass = clz;
84 | name = n;
85 | signature = s;
86 | }
87 |
88 | public DexMethodDescriptor(Class> clz, String n, String s) {
89 | if (clz == null || n == null || s == null) {
90 | throw new NullPointerException();
91 | }
92 | declaringClass = getTypeSig(clz);
93 | name = n;
94 | signature = s;
95 | }
96 |
97 | public static String getMethodTypeSig(final Method method) {
98 | final StringBuilder buf = new StringBuilder();
99 | buf.append("(");
100 | final Class>[] types = method.getParameterTypes();
101 | for (Class> type : types) {
102 | buf.append(getTypeSig(type));
103 | }
104 | buf.append(")");
105 | buf.append(getTypeSig(method.getReturnType()));
106 | return buf.toString();
107 | }
108 |
109 | public static String getConstructorTypeSig(final Constructor> ctor) {
110 | final StringBuilder buf = new StringBuilder();
111 | buf.append("(");
112 | final Class>[] types = ctor.getParameterTypes();
113 | for (Class> type : types) {
114 | buf.append(getTypeSig(type));
115 | }
116 | buf.append(")");
117 | buf.append("V");
118 | return buf.toString();
119 | }
120 |
121 | public static String getTypeSig(final Class> type) {
122 | if (type.isPrimitive()) {
123 | if (Integer.TYPE.equals(type)) {
124 | return "I";
125 | }
126 | if (Void.TYPE.equals(type)) {
127 | return "V";
128 | }
129 | if (Boolean.TYPE.equals(type)) {
130 | return "Z";
131 | }
132 | if (Character.TYPE.equals(type)) {
133 | return "C";
134 | }
135 | if (Byte.TYPE.equals(type)) {
136 | return "B";
137 | }
138 | if (Short.TYPE.equals(type)) {
139 | return "S";
140 | }
141 | if (Float.TYPE.equals(type)) {
142 | return "F";
143 | }
144 | if (Long.TYPE.equals(type)) {
145 | return "J";
146 | }
147 | if (Double.TYPE.equals(type)) {
148 | return "D";
149 | }
150 | throw new IllegalStateException("Type: " + type.getName() + " is not a primitive type");
151 | }
152 | if (type.isArray()) {
153 | return "[" + getTypeSig(type.getComponentType());
154 | }
155 | return "L" + type.getName().replace('.', '/') + ";";
156 | }
157 |
158 | public static List splitParameterTypes(String s) {
159 | int i = 0;
160 | ArrayList list = new ArrayList<>();
161 | while (i < s.length()) {
162 | char c = s.charAt(i);
163 | if (c == 'L') {
164 | int j = s.indexOf(';', i);
165 | list.add(s.substring(i, j + 1));
166 | i = j + 1;
167 | } else if (c == '[') {
168 | int j = i;
169 | while (s.charAt(j) == '[') {
170 | j++;
171 | }
172 | if (s.charAt(j) == 'L') {
173 | j = s.indexOf(';', j);
174 | }
175 | list.add(s.substring(i, j + 1));
176 | i = j + 1;
177 | } else {
178 | list.add(String.valueOf(c));
179 | }
180 | i++;
181 | }
182 | return list;
183 | }
184 |
185 | public String getDeclaringClassName() {
186 | return declaringClass.substring(1, declaringClass.length() - 1).replace('/', '.');
187 | }
188 |
189 | @Override
190 | public String toString() {
191 | return declaringClass + "->" + name + signature;
192 | }
193 |
194 | public String getDescriptor() {
195 | return declaringClass + "->" + name + signature;
196 | }
197 |
198 | @Override
199 | public boolean equals(Object o) {
200 | if (this == o) {
201 | return true;
202 | }
203 | if (o == null || getClass() != o.getClass()) {
204 | return false;
205 | }
206 | return toString().equals(o.toString());
207 | }
208 |
209 | @Override
210 | public int hashCode() {
211 | return toString().hashCode();
212 | }
213 |
214 |
215 | public Constructor> getConstructorInstance(ClassLoader classLoader) throws NoSuchMethodException {
216 | try {
217 | String className = declaringClass.substring(1, declaringClass.length() - 1).replace('/', '.');
218 | Class> clz = classLoader.loadClass(className);
219 | // 检查当前类的构造函数
220 | for (Constructor> m : clz.getDeclaredConstructors()) {
221 | if (getConstructorTypeSig(m).equals(signature)) {
222 | return m;
223 | }
224 | }
225 | // 遍历父类
226 | Class> superClass = clz.getSuperclass();
227 | while (superClass != null) {
228 | for (Constructor> m : superClass.getDeclaredConstructors()) {
229 | int modifiers = m.getModifiers();
230 | if (Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers)) {
231 | continue;
232 | }
233 | if (getConstructorTypeSig(m).equals(signature)) {
234 | return m;
235 | }
236 | }
237 | superClass = superClass.getSuperclass();
238 | }
239 | throw new NoSuchMethodException(declaringClass + "->" + name + signature);
240 | } catch (ClassNotFoundException e) {
241 | NoSuchMethodException exception = new NoSuchMethodException(declaringClass + "->" + name + signature);
242 | exception.initCause(e);
243 | throw exception;
244 | }
245 | }
246 |
247 |
248 | public Method getMethodInstance(ClassLoader classLoader) throws NoSuchMethodException {
249 | try {
250 | Class> clz = classLoader.loadClass(
251 | declaringClass.substring(1, declaringClass.length() - 1).replace('/', '.'));
252 | for (Method m : clz.getDeclaredMethods()) {
253 | if (m.getName().equals(name) && getMethodTypeSig(m).equals(signature)) {
254 | return m;
255 | }
256 | }
257 | while ((clz = clz.getSuperclass()) != null) {
258 | for (Method m : clz.getDeclaredMethods()) {
259 | if (Modifier.isPrivate(m.getModifiers()) || Modifier
260 | .isStatic(m.getModifiers())) {
261 | continue;
262 | }
263 | if (m.getName().equals(name) && getMethodTypeSig(m).equals(signature)) {
264 | return m;
265 | }
266 | }
267 | }
268 | throw new NoSuchMethodException(declaringClass + "->" + name + signature);
269 | } catch (ClassNotFoundException e) {
270 | throw (NoSuchMethodException) new NoSuchMethodException(
271 | declaringClass + "->" + name + signature).initCause(e);
272 | }
273 | }
274 |
275 | public List getParameterTypes() {
276 | String params = signature.substring(1, signature.indexOf(')'));
277 | return splitParameterTypes(params);
278 | }
279 |
280 | public String getReturnType() {
281 | int index = signature.indexOf(')');
282 | return signature.substring(index + 1);
283 | }
284 |
285 | }
286 |
--------------------------------------------------------------------------------