├── .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/v/suzhelan/XpHelper.svg)](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 | --------------------------------------------------------------------------------