├── .github └── workflows │ └── android.yml ├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── wzc │ │ └── gradle │ │ ├── plugin │ │ ├── MainActivity.java │ │ └── Test.java │ │ └── test │ │ └── Test.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-night │ └── themes.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── plugin_libs ├── StringObfuscatePlugin-1.0.0.jar ├── StringObfuscatePlugin-2.0.0.jar └── StringObfuscatePlugin-2.0.1.jar ├── resources └── diff.png ├── settings.gradle ├── string_obfuscated ├── .gitignore ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── wzc │ │ └── gradle │ │ └── plugin │ │ ├── CreateTestClass.java │ │ ├── MethodAdapter.java │ │ ├── ScanClassVisitor.java │ │ └── utils │ │ └── StringEncryptionTestUtil.java │ └── kotlin │ └── com │ └── wzc │ └── gradle │ └── plugin │ ├── PluginLaunch.kt │ ├── model │ └── StringObfuscateExtensions.kt │ └── utils │ └── Logger.kt └── test_library ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── wzc │ └── test_library │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml └── java │ └── com │ └── wzc │ ├── test │ └── Test1.java │ └── test_library │ └── Test2.java └── test └── java └── com └── wzc └── test_library └── ExampleUnitTest.java /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: set up JDK 11 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '11' 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | 26 | - name: Build library 27 | run: ./gradlew string_obfuscated:assemble 28 | 29 | - name: Save library to artifacts 30 | uses: actions/upload-artifact@v2 31 | with: 32 | path: string_obfuscated/build/libs/* 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /.idea 4 | /local.properties 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 字符串混淆插件 2 | 3 | 1. 插件只能混淆java层代码 4 | 2. 插件只能在application类型的module中使用,插件会混淆application module所依赖的module和jar。所以请添加你要混淆的包名,否则所有第三方SDK都会被混淆 5 | 6 | ### 使用 7 | 8 | #### 方式一 9 | 10 | 在项目跟目录下的`build.gradle`文件中添加以下代码 11 | 12 | ```groovy 13 | buildscript { 14 | repositories { 15 | ... 16 | maven { url 'https://jitpack.io' } 17 | } 18 | dependencies { 19 | ... 20 | classpath 'com.github.wuzuchang:StringObfuscatedPlugin:2.1.0' 21 | } 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | ... 27 | maven { url 'https://jitpack.io' } 28 | } 29 | } 30 | ``` 31 | 32 | 在app module下的`build.gradle`中添加以下代码 33 | 34 | ```groovy 35 | plugins { 36 | id 'com.android.application' 37 | // 字符串混淆插件 38 | id 'com.wzc.string.obfuscate' 39 | } 40 | stringObfuscate{ 41 | openLog = true 42 | // 添加需要混淆的包名 43 | packageName = ["com.wzc.gradle.plugin","com.wzc.test"] 44 | } 45 | ``` 46 | 47 | #### 方式二 48 | 49 | 下载[插件jar](https://github.com/wuzuchang/StringObfuscatedPlugin/raw/master/plugin_libs/StringObfuscatePlugin-1.0.0.jar)文件,复制到项目中的plugin_libs目录下(没有就新增目录) 50 | 51 | ```java 52 | ├── plugin_libs 53 | │ └── StringObfuscatePlugin-1.0.0.jar 54 | ├── app 55 | │ ├── .. 56 | │ └── .. 57 | └── module 58 | │ ├── .. 59 | │ └── .. 60 | ``` 61 | 62 | 在项目根目录`build.gradle`中添加以下代码 63 | 64 | ```groovy 65 | buildscript { 66 | repositories { 67 | ... 68 | flatDir { 69 | dirs 'plugin_libs' 70 | } 71 | } 72 | dependencies { 73 | ... 74 | classpath "com.wzc.string.obfuscate:StringObfuscatePlugin:1.0.0" 75 | } 76 | } 77 | 78 | allprojects { 79 | repositories { 80 | ... 81 | flatDir { 82 | dirs 'plugin_libs' 83 | } 84 | } 85 | } 86 | ``` 87 | 88 | 在app module下的`build.gradle`中添加以下代码 89 | 90 | ```groovy 91 | plugins { 92 | id 'com.android.application' 93 | // 字符串混淆插件 94 | id 'com.wzc.string.obfuscate' 95 | } 96 | stringObfuscate{ 97 | openLog = true 98 | // 添加需要混淆的包名 99 | packageName = ["com.wzc.gradle.plugin","com.wzc.test"] 100 | } 101 | ``` 102 | 103 | 混淆前后对比 104 | 105 | diff 106 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | // 字符串混淆插件 4 | id 'com.wzc.string.obfuscate' 5 | } 6 | stringObfuscate{ 7 | openLog = true 8 | packageName = ["com.wzc.gradle.plugin","com.wzc.test"] 9 | } 10 | 11 | android { 12 | compileSdkVersion 33 13 | buildToolsVersion "29.0.3" 14 | 15 | defaultConfig { 16 | applicationId "com.wzc.gradle.plugin" 17 | minSdkVersion 21 18 | targetSdkVersion 33 19 | versionCode 1 20 | versionName "1.0" 21 | 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | } 36 | 37 | dependencies { 38 | 39 | implementation 'androidx.appcompat:appcompat:1.4.1' 40 | implementation 'com.google.android.material:material:1.5.0' 41 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 42 | implementation project(path: ':test_library') 43 | testImplementation 'junit:junit:' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 46 | } -------------------------------------------------------------------------------- /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 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzc/gradle/plugin/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin; 2 | 3 | import android.os.Bundle; 4 | import android.widget.TextView; 5 | 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | import com.wzc.test.Test1; 9 | import com.wzc.test_library.Test2; 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | Test test = new Test(); 19 | Test1 test1 = new Test1(); 20 | Test2 test2 = new Test2(); 21 | test.test(); 22 | TextView textView = findViewById(R.id.text); 23 | textView.setText(" text=" +test + " text1=" + test1 + " test2=" + test2); 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzc/gradle/plugin/Test.java: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin; 2 | 3 | 4 | import android.util.Log; 5 | 6 | public class Test { 7 | 8 | private String string1 = "测试中文"; 9 | private final String string2 = "test English"; 10 | private static String string3 = "测试特殊字符@!#¥%……&*()~+——()_="; 11 | private static final String string4 = "测试换行\n换行了"; 12 | private static final String string6 = "测试\r\n换行了"; 13 | private static final String string5; 14 | private boolean test = false; 15 | 16 | static { 17 | string5 = "测试静态代码块"; //会生成 18 | } 19 | 20 | public String test() { 21 | Log.d("wzc==", "switch is open"); 22 | return string1 + string2 + string3 +string4 +string5 ; 23 | } 24 | 25 | public void test(String a) { 26 | if (test) { 27 | Log.d("wzc==", "switch is open"); 28 | } 29 | } 30 | 31 | class InnerClassTest { 32 | private int i; 33 | private String s; 34 | 35 | private void doSth(String p) { 36 | p = "hahah"; 37 | Log.d("wzc==", p); 38 | } 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "Test{" + 44 | "string1='" + string1 + '\'' + 45 | ", string2='" + string2 + '\'' + 46 | ", string3='" + string3 + '\'' + 47 | ", string4='" + string4 + '\'' + 48 | ", string5='" + string5 + '\'' + 49 | ", string6='" + string6 + '\'' + 50 | ", test=" + test + 51 | '}'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzc/gradle/test/Test.java: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.test; 2 | 3 | 4 | import android.util.Log; 5 | 6 | public class Test { 7 | 8 | private String string1 = "测试中文"; 9 | private final String string2 = "test English"; 10 | private static String string3 = "测试特殊字符@!#¥%……&*()~+——()_="; //会生成 11 | private static final String string4 = "测试换行\n换行了"; 12 | private static final String string6 = "测试\r\n换行了"; 13 | private static final String string5; 14 | private boolean test = false; 15 | 16 | static { 17 | string5 = "测试静态代码块"; //会生成 18 | } 19 | 20 | public String test() { 21 | Log.d("wzc==", "switch is open"); 22 | return string1 + string2 + string3 +string4 +string5 ; 23 | } 24 | 25 | public void test(String a) { 26 | if (test) { 27 | Log.d("wzc==", "switch is open"); 28 | } 29 | } 30 | 31 | class InnerClassTest { 32 | private int i; 33 | private String s; 34 | 35 | private void doSth(String p) { 36 | p = "hahah"; 37 | Log.d("wzc==", p); 38 | } 39 | } 40 | 41 | // @RequiresApi(api = Build.VERSION_CODES.O) 42 | // private static String stringDecrypt(String value, int key) { 43 | // if (value == null) { 44 | // return null; 45 | // } 46 | // return new String(Base64.getDecoder().decode(value)); 47 | // } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /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/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /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 | MyGradlePlugin 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories{ 3 | flatDir { 4 | dirs 'plugin_libs' 5 | } 6 | } 7 | dependencies { 8 | // classpath 'com.github.wuzuchang:StringObfuscatePlugin:1.0.0' 9 | classpath 'com.wzc.string.obfuscate:StringObfuscatePlugin:2.0.0' 10 | } 11 | } 12 | 13 | plugins { 14 | id 'com.android.application' version '7.4.1' apply false 15 | id 'com.android.library' version '7.4.1' apply false 16 | id 'org.jetbrains.kotlin.android' version '1.8.0' apply false 17 | } -------------------------------------------------------------------------------- /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. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec: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 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 06 14:20:31 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 -------------------------------------------------------------------------------- /plugin_libs/StringObfuscatePlugin-1.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/plugin_libs/StringObfuscatePlugin-1.0.0.jar -------------------------------------------------------------------------------- /plugin_libs/StringObfuscatePlugin-2.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/plugin_libs/StringObfuscatePlugin-2.0.0.jar -------------------------------------------------------------------------------- /plugin_libs/StringObfuscatePlugin-2.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/plugin_libs/StringObfuscatePlugin-2.0.1.jar -------------------------------------------------------------------------------- /resources/diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/resources/diff.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | maven { url 'https://jitpack.io' } 7 | } 8 | } 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven { url 'https://jitpack.io' } 15 | } 16 | } 17 | rootProject.name = "StringObfuscatedPlugin" 18 | include ':string_obfuscated' 19 | include ':app' 20 | include ':test_library' 21 | -------------------------------------------------------------------------------- /string_obfuscated/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /string_obfuscated/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-gradle-plugin' 3 | id 'org.jetbrains.kotlin.jvm' 4 | id 'maven-publish' 5 | } 6 | 7 | def group = 'com.wzc.string.obfuscate' 8 | def versionCode = '2.0.1' //版本 9 | def pluginName = 'StringObfuscatePlugin' //唯一标示 10 | 11 | //插件配置,这样就不需要创建META-INF/gradle-plugins文件目录了 12 | gradlePlugin { 13 | plugins { 14 | //定义的信息可以和发布信息不相同 15 | MethodTracer { 16 | id = group //定义插件id 17 | version = versionCode 18 | implementationClass = 'com.wzc.gradle.plugin.PluginLaunch' //定义插件实现类 19 | } 20 | } 21 | } 22 | 23 | publishing { 24 | publications { 25 | //PubName 26 | plugin(MavenPublication) { 27 | groupId = group 28 | artifactId = pluginName 29 | version = versionCode 30 | artifact("../plugin_libs/StringObfuscatePlugin-${version}.jar") 31 | } 32 | } 33 | repositories { 34 | maven { 35 | // change to point to your repo, e.g. http://my.org/repo 36 | url = "$buildDir/repo" 37 | } 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation gradleApi() 43 | // 实现Transform的时候会用到,gradle插件版本:https://developer.android.com/studio/releases/gradle-plugin?hl=zh-cn#updating-gradle 44 | // 这里注意使用的gradle插件(AGP)版本不要超过你Android studio 最大支持的版本:https://developer.android.com/studio/releases?hl=zh-cn#android_gradle_plugin_and_android_studio_compatibility 45 | // gradle/wrapper 中设置你gradle版本 46 | implementation("com.android.tools.build:gradle:7.3.1") { 47 | exclude group: "org.ow2.asm" 48 | } 49 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10") { 50 | exclude group: "org.ow2.asm" 51 | } 52 | implementation("org.ow2.asm:asm:9.3") 53 | implementation("org.ow2.asm:asm-commons:9.3") 54 | implementation("org.ow2.asm:asm-util:9.3") 55 | } 56 | 57 | java { 58 | sourceCompatibility = JavaVersion.VERSION_1_8 59 | targetCompatibility = JavaVersion.VERSION_1_8 60 | } 61 | 62 | task copyPluginFile { 63 | doLast { 64 | copy { 65 | from '../string_obfuscated/build/libs/' 66 | into '../plugin_libs' 67 | rename "string_obfuscated-${versionCode}.jar", "${pluginName}-${versionCode}.jar" 68 | } 69 | } 70 | } 71 | 72 | assemble.finalizedBy(copyPluginFile) 73 | build.finalizedBy(copyPluginFile) 74 | jar.finalizedBy(copyPluginFile) -------------------------------------------------------------------------------- /string_obfuscated/src/main/java/com/wzc/gradle/plugin/CreateTestClass.java: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin; 2 | 3 | import org.gradle.internal.impldep.org.apache.commons.io.FileUtils; 4 | import org.objectweb.asm.AnnotationVisitor; 5 | import org.objectweb.asm.ClassWriter; 6 | import org.objectweb.asm.FieldVisitor; 7 | import org.objectweb.asm.MethodVisitor; 8 | import org.objectweb.asm.Opcodes; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | 13 | /** 14 | * 通过ASM创建class,但是ASM创建的class不会自动导包。建议使用javapoet生成class文件 15 | */ 16 | public class CreateTestClass { 17 | 18 | 19 | public void create(String javac, String transforms, String packageName) throws IOException { 20 | //定义一个叫做Example的类 21 | ClassWriter cw = new ClassWriter(0); 22 | cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Example", null, "java/lang/Object", null); 23 | 24 | //生成默认的构造方法 25 | MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, 26 | "", 27 | "()V", 28 | null, 29 | null); 30 | 31 | //生成构造方法的字节码指令 32 | mw.visitVarInsn(Opcodes.ALOAD, 0); 33 | mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); 34 | mw.visitInsn(Opcodes.RETURN); 35 | mw.visitMaxs(1, 1); 36 | mw.visitEnd(); 37 | 38 | 39 | //生成String name字段 40 | FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC, "name", "Ljava/lang/String;", null, null); 41 | AnnotationVisitor av = fv.visitAnnotation("LNotNull;", true); 42 | av.visit("value", "abc"); 43 | av.visitEnd(); 44 | fv.visitEnd(); 45 | 46 | 47 | //生成main方法 48 | mw = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, 49 | "main", 50 | "([Ljava/lang/String;)V", 51 | null, 52 | null); 53 | 54 | //生成main方法中的字节码指令 55 | mw.visitFieldInsn(Opcodes.GETSTATIC, 56 | "java/lang/System", 57 | "out", 58 | "Ljava/io/PrintStream;"); 59 | 60 | mw.visitLdcInsn("Hello world!"); 61 | mw.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 62 | "java/io/PrintStream", 63 | "println", 64 | "(Ljava/lang/String;)V", 65 | false); 66 | mw.visitInsn(Opcodes.RETURN); 67 | mw.visitMaxs(2, 2); 68 | 69 | //字节码生成完成 70 | mw.visitEnd(); 71 | 72 | // 获取生成的class文件对应的二进制流 73 | byte[] code = cw.toByteArray(); 74 | 75 | //拷贝到javac 76 | FileUtils.writeByteArrayToFile( 77 | new File(javac + packageName + "Example.class"), code); 78 | //将生成类拷贝到transforms,用于dex 79 | FileUtils.writeByteArrayToFile( 80 | new File(transforms + packageName + "Example.class"), code); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /string_obfuscated/src/main/java/com/wzc/gradle/plugin/MethodAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin; 2 | 3 | import com.wzc.gradle.plugin.utils.StringEncryptionTestUtil; 4 | 5 | import org.objectweb.asm.MethodVisitor; 6 | import org.objectweb.asm.Opcodes; 7 | import org.objectweb.asm.commons.AdviceAdapter; 8 | 9 | import java.util.HashMap; 10 | import java.util.Set; 11 | 12 | /** 13 | * 方法修改 14 | * AdviceAdapter API:https://asm.ow2.io/javadoc/org/objectweb/asm/commons/AdviceAdapter.html 15 | * AdviceAdapter:实现了MethodVisitor接口,主要访问方法的信息。用来对具体方法进行字节码操作; 16 | * FieldVisitor:访问具体的类成员; 17 | * AnnotationVisitor:访问具体的注解信息 18 | */ 19 | public class MethodAdapter extends AdviceAdapter { 20 | 21 | private final HashMap mStaticFinalField; 22 | private final String mMethodName; 23 | private final String mOwner; 24 | private final int key1; 25 | private final int key2; 26 | private final int key3; 27 | 28 | protected MethodAdapter(int api, MethodVisitor methodVisitor, int access, String name, String descriptor, HashMap staticFinalField, String owner, int key1, int key2, int key3) { 29 | super(api, methodVisitor, access, name, descriptor); 30 | mStaticFinalField = staticFinalField; 31 | mMethodName = name; 32 | mOwner = owner; 33 | this.key1 = key1; 34 | this.key2 = key2; 35 | this.key3 = key3; 36 | } 37 | 38 | @Override 39 | public void visitCode() { 40 | super.visitCode(); 41 | if ("".equals(mMethodName)) { 42 | Set strings = mStaticFinalField.keySet(); 43 | for (String field : strings) { 44 | if ("".equals(field)){ 45 | super.visitLdcInsn(field); 46 | continue; 47 | } 48 | String value = mStaticFinalField.get(field); 49 | String encryption = StringEncryptionTestUtil.encryption(value, key1, key2, key3); 50 | mv.visitLdcInsn(encryption); 51 | mv.visitIntInsn(BIPUSH, key1); 52 | mv.visitMethodInsn(INVOKESTATIC, mOwner, "stringDecrypt", "(Ljava/lang/String;I)Ljava/lang/String;", false); 53 | mv.visitFieldInsn(Opcodes.PUTSTATIC, mOwner, field, "Ljava/lang/String;"); 54 | ScanClassVisitor.hasString = true; 55 | } 56 | } 57 | } 58 | 59 | @Override 60 | public void visitLdcInsn(Object value) { 61 | if (value instanceof String && !"".equals(value)) { 62 | String encryption = StringEncryptionTestUtil.encryption((String) value, key1, key2, key3); 63 | mv.visitLdcInsn(encryption); 64 | mv.visitIntInsn(BIPUSH, key1); 65 | mv.visitMethodInsn(INVOKESTATIC, mOwner, "stringDecrypt", "(Ljava/lang/String;I)Ljava/lang/String;", false); 66 | ScanClassVisitor.hasString = true; 67 | } else { 68 | super.visitLdcInsn(value); 69 | } 70 | } 71 | 72 | 73 | @Override 74 | public void visitVarInsn(int opcode, int var) { 75 | super.visitVarInsn(opcode, var); 76 | } 77 | 78 | @Override 79 | public void endMethod() { 80 | super.endMethod(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /string_obfuscated/src/main/java/com/wzc/gradle/plugin/ScanClassVisitor.java: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin; 2 | 3 | 4 | import static org.gradle.internal.impldep.bsh.org.objectweb.asm.Constants.*; 5 | 6 | import com.wzc.gradle.plugin.utils.Logger; 7 | 8 | import org.objectweb.asm.ClassVisitor; 9 | import org.objectweb.asm.FieldVisitor; 10 | import org.objectweb.asm.Label; 11 | import org.objectweb.asm.MethodVisitor; 12 | import org.objectweb.asm.Opcodes; 13 | 14 | import java.util.HashMap; 15 | import java.util.Random; 16 | 17 | /** 18 | * ClassVisitor API: https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html 19 | */ 20 | public class ScanClassVisitor extends ClassVisitor { 21 | 22 | private static final int ACC_STATIC_FINAL = Opcodes.ACC_STATIC | Opcodes.ACC_FINAL; 23 | private static final String STRING_DESCRIPTOR = "Ljava/lang/String;"; 24 | private final HashMap mStaticFinalField = new HashMap<>(); 25 | private boolean hasClinit; 26 | public static boolean hasString; 27 | private boolean hasStringDecrypt; 28 | private String mOwner; 29 | private final int key1; 30 | // 同一个类里面 key2和key3是固定的,不同类key2和key3不一样 31 | private final int key2; 32 | private final int key3; 33 | 34 | 35 | public ScanClassVisitor(int api, ClassVisitor classVisitor) { 36 | super(api, classVisitor); 37 | hasString = false; 38 | hasStringDecrypt = false; 39 | key1 = getRandomKey(); 40 | key2 = getRandomKey(); 41 | key3 = getRandomKey(); 42 | } 43 | 44 | private int getRandomKey() { 45 | Random rand = new Random(); 46 | return rand.nextInt(127); 47 | } 48 | 49 | /** 50 | * 可以拿到类的详细信息,然后对满足条件的类进行过滤 51 | * 52 | * @param version JDK版本 53 | * @param access 类的修饰符 54 | * @param name 类的名称,通常使用完整包名+类名 55 | * @param signature 泛型信息,如果没有定义泛型该参数为null 56 | * @param superName 当前类的父类,所有类的父类java.lang.Object 57 | * @param interfaces 实现的接口列表 58 | */ 59 | @Override 60 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 61 | super.visit(version, access, name, signature, superName, interfaces); 62 | mOwner = name; 63 | } 64 | 65 | /** 66 | * 访问内部类信息 67 | * 68 | * @param name 内部类名称 69 | * @param outerName 内部类所属的类的名称 70 | * @param innerName 内部类在其封闭类中的(简单)名称。对于匿名内部类,可能为 null。 71 | * @param access 内部类的修饰符 72 | */ 73 | @Override 74 | public void visitInnerClass(String name, String outerName, String innerName, int access) { 75 | super.visitInnerClass(name, outerName, innerName, access); 76 | } 77 | 78 | /** 79 | * 类中字段 80 | * 81 | * @param access 修饰符 82 | * @param name 字段名 83 | * @param descriptor 字段类型 84 | * @param signature 泛型描述 85 | * @param value 默认值 86 | */ 87 | @Override 88 | public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { 89 | 90 | boolean isStaticFinal = (access & ACC_STATIC_FINAL) == ACC_STATIC_FINAL; 91 | if (value instanceof String && !"".equals(value)) { 92 | hasString = true; 93 | // 将 final + static 修饰的变量置空(不然无法在静态块中初始化),之后再在中赋值 94 | if (STRING_DESCRIPTOR.equals(descriptor) && isStaticFinal) { 95 | mStaticFinalField.put(name, (String) value); 96 | return super.visitField(access, name, descriptor, signature, null); 97 | } 98 | } 99 | return super.visitField(access, name, descriptor, signature, value); 100 | } 101 | 102 | /** 103 | * 拿到要修改的方法,然后进行修改 104 | * 105 | * @param access 方法的修饰符 106 | * @param name 方法名 107 | * @param descriptor 方法签名,返回值 108 | * @param signature 泛型相关信息 109 | * @param exceptions 抛出的异常,没有异常抛出该参数为null 110 | * @return 111 | */ 112 | @Override 113 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 114 | MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions); 115 | if ("stringDecrypt".equals(name)) { 116 | hasStringDecrypt = true; 117 | return methodVisitor; 118 | } 119 | if ("".equals(name)) { 120 | hasClinit = true; 121 | } 122 | return new MethodAdapter(Opcodes.ASM9, methodVisitor, access, name, descriptor 123 | , mStaticFinalField, mOwner, key1, key2, key3); 124 | } 125 | 126 | /** 127 | * 该方法是最后一个被调用的方法,用于通知访问者该类的所有字段和方法都已访问。 128 | */ 129 | @Override 130 | public void visitEnd() { 131 | 132 | // 如果没有扫描方法,则说明全是final + static;需要插入static ()方法 133 | if (!hasClinit && mStaticFinalField.size() > 0) { 134 | Logger.INSTANCE.d(mOwner + " add method"); 135 | MethodVisitor mv = visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); 136 | mv.visitCode(); 137 | mv.visitInsn(Opcodes.RETURN); 138 | mv.visitMaxs(1, 0); 139 | mv.visitEnd(); 140 | } 141 | if (hasString && !hasStringDecrypt) { 142 | Logger.INSTANCE.d(mOwner + " add stringDecrypt method"); 143 | addMethod(key2, key3); 144 | } 145 | super.visitEnd(); 146 | } 147 | 148 | private void addMethod(int key2, int key3) { 149 | MethodVisitor methodVisitor = this.visitMethod(ACC_PUBLIC | ACC_STATIC, "stringDecrypt", "(Ljava/lang/String;I)Ljava/lang/String;", null, null); 150 | methodVisitor.visitCode(); 151 | Label label0 = new Label(); 152 | Label label1 = new Label(); 153 | Label label2 = new Label(); 154 | methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Exception"); 155 | methodVisitor.visitLabel(label0); 156 | methodVisitor.visitLineNumber(54, label0); 157 | methodVisitor.visitVarInsn(ALOAD, 0); 158 | methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); 159 | methodVisitor.visitInsn(ICONST_2); 160 | methodVisitor.visitInsn(IDIV); 161 | methodVisitor.visitVarInsn(ISTORE, 2); 162 | Label label3 = new Label(); 163 | methodVisitor.visitLabel(label3); 164 | methodVisitor.visitLineNumber(55, label3); 165 | methodVisitor.visitVarInsn(ALOAD, 0); 166 | methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "toCharArray", "()[C", false); 167 | methodVisitor.visitVarInsn(ASTORE, 3); 168 | Label label4 = new Label(); 169 | methodVisitor.visitLabel(label4); 170 | methodVisitor.visitLineNumber(56, label4); 171 | methodVisitor.visitVarInsn(ILOAD, 2); 172 | methodVisitor.visitIntInsn(NEWARRAY, T_BYTE); 173 | methodVisitor.visitVarInsn(ASTORE, 4); 174 | Label label5 = new Label(); 175 | methodVisitor.visitLabel(label5); 176 | methodVisitor.visitLineNumber(57, label5); 177 | methodVisitor.visitInsn(ICONST_0); 178 | methodVisitor.visitVarInsn(ISTORE, 5); 179 | Label label6 = new Label(); 180 | methodVisitor.visitLabel(label6); 181 | methodVisitor.visitFrame(Opcodes.F_FULL, 6, new Object[]{"java/lang/String", Opcodes.INTEGER, Opcodes.INTEGER, "[C", "[B", Opcodes.INTEGER}, 0, new Object[]{}); 182 | methodVisitor.visitVarInsn(ILOAD, 5); 183 | methodVisitor.visitVarInsn(ILOAD, 2); 184 | Label label7 = new Label(); 185 | methodVisitor.visitJumpInsn(IF_ICMPGE, label7); 186 | Label label8 = new Label(); 187 | methodVisitor.visitLabel(label8); 188 | methodVisitor.visitLineNumber(58, label8); 189 | methodVisitor.visitVarInsn(ILOAD, 5); 190 | methodVisitor.visitInsn(ICONST_2); 191 | methodVisitor.visitInsn(IMUL); 192 | methodVisitor.visitVarInsn(ISTORE, 6); 193 | Label label9 = new Label(); 194 | methodVisitor.visitLabel(label9); 195 | methodVisitor.visitLineNumber(59, label9); 196 | methodVisitor.visitVarInsn(ALOAD, 4); 197 | methodVisitor.visitVarInsn(ILOAD, 5); 198 | methodVisitor.visitLdcInsn("0123456789abcdef"); 199 | methodVisitor.visitVarInsn(ALOAD, 3); 200 | methodVisitor.visitVarInsn(ILOAD, 6); 201 | methodVisitor.visitInsn(CALOAD); 202 | methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "indexOf", "(I)I", false); 203 | methodVisitor.visitInsn(ICONST_4); 204 | methodVisitor.visitInsn(ISHL); 205 | methodVisitor.visitLdcInsn("0123456789abcdef"); 206 | methodVisitor.visitVarInsn(ALOAD, 3); 207 | methodVisitor.visitVarInsn(ILOAD, 6); 208 | methodVisitor.visitInsn(ICONST_1); 209 | methodVisitor.visitInsn(IADD); 210 | methodVisitor.visitInsn(CALOAD); 211 | methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "indexOf", "(I)I", false); 212 | methodVisitor.visitInsn(IOR); 213 | methodVisitor.visitInsn(I2B); 214 | methodVisitor.visitInsn(BASTORE); 215 | Label label10 = new Label(); 216 | methodVisitor.visitLabel(label10); 217 | methodVisitor.visitLineNumber(57, label10); 218 | methodVisitor.visitIincInsn(5, 1); 219 | methodVisitor.visitJumpInsn(GOTO, label6); 220 | methodVisitor.visitLabel(label7); 221 | methodVisitor.visitLineNumber(61, label7); 222 | methodVisitor.visitFrame(Opcodes.F_CHOP, 1, null, 0, null); 223 | methodVisitor.visitVarInsn(ILOAD, 1); 224 | methodVisitor.visitIntInsn(SIPUSH, key2); 225 | methodVisitor.visitInsn(IXOR); 226 | methodVisitor.visitInsn(I2B); 227 | methodVisitor.visitVarInsn(ISTORE, 5); 228 | Label label11 = new Label(); 229 | methodVisitor.visitLabel(label11); 230 | methodVisitor.visitLineNumber(63, label11); 231 | methodVisitor.visitVarInsn(ALOAD, 4); 232 | methodVisitor.visitInsn(ICONST_0); 233 | methodVisitor.visitVarInsn(ALOAD, 4); 234 | methodVisitor.visitInsn(ICONST_0); 235 | methodVisitor.visitInsn(BALOAD); 236 | methodVisitor.visitIntInsn(SIPUSH, key3); 237 | methodVisitor.visitInsn(IXOR); 238 | methodVisitor.visitInsn(I2B); 239 | methodVisitor.visitInsn(BASTORE); 240 | Label label12 = new Label(); 241 | methodVisitor.visitLabel(label12); 242 | methodVisitor.visitLineNumber(64, label12); 243 | methodVisitor.visitVarInsn(ALOAD, 4); 244 | methodVisitor.visitInsn(ICONST_0); 245 | methodVisitor.visitInsn(BALOAD); 246 | methodVisitor.visitVarInsn(ISTORE, 6); 247 | Label label13 = new Label(); 248 | methodVisitor.visitLabel(label13); 249 | methodVisitor.visitLineNumber(65, label13); 250 | methodVisitor.visitInsn(ICONST_1); 251 | methodVisitor.visitVarInsn(ISTORE, 7); 252 | Label label14 = new Label(); 253 | methodVisitor.visitLabel(label14); 254 | methodVisitor.visitFrame(Opcodes.F_APPEND, 3, new Object[]{Opcodes.INTEGER, Opcodes.INTEGER, Opcodes.INTEGER}, 0, null); 255 | methodVisitor.visitVarInsn(ILOAD, 7); 256 | methodVisitor.visitVarInsn(ALOAD, 4); 257 | methodVisitor.visitInsn(ARRAYLENGTH); 258 | Label label15 = new Label(); 259 | methodVisitor.visitJumpInsn(IF_ICMPGE, label15); 260 | Label label16 = new Label(); 261 | methodVisitor.visitLabel(label16); 262 | methodVisitor.visitLineNumber(66, label16); 263 | methodVisitor.visitVarInsn(ALOAD, 4); 264 | methodVisitor.visitVarInsn(ILOAD, 7); 265 | methodVisitor.visitVarInsn(ALOAD, 4); 266 | methodVisitor.visitVarInsn(ILOAD, 7); 267 | methodVisitor.visitInsn(BALOAD); 268 | methodVisitor.visitVarInsn(ILOAD, 6); 269 | methodVisitor.visitInsn(IXOR); 270 | methodVisitor.visitVarInsn(ILOAD, 5); 271 | methodVisitor.visitInsn(IXOR); 272 | methodVisitor.visitInsn(I2B); 273 | methodVisitor.visitInsn(BASTORE); 274 | Label label17 = new Label(); 275 | methodVisitor.visitLabel(label17); 276 | methodVisitor.visitLineNumber(67, label17); 277 | methodVisitor.visitVarInsn(ALOAD, 4); 278 | methodVisitor.visitVarInsn(ILOAD, 7); 279 | methodVisitor.visitInsn(BALOAD); 280 | methodVisitor.visitVarInsn(ISTORE, 6); 281 | Label label18 = new Label(); 282 | methodVisitor.visitLabel(label18); 283 | methodVisitor.visitLineNumber(65, label18); 284 | methodVisitor.visitIincInsn(7, 1); 285 | methodVisitor.visitJumpInsn(GOTO, label14); 286 | methodVisitor.visitLabel(label15); 287 | methodVisitor.visitLineNumber(70, label15); 288 | methodVisitor.visitFrame(Opcodes.F_CHOP, 1, null, 0, null); 289 | methodVisitor.visitTypeInsn(NEW, "java/lang/String"); 290 | methodVisitor.visitInsn(DUP); 291 | methodVisitor.visitVarInsn(ALOAD, 4); 292 | methodVisitor.visitLdcInsn("UTF-8"); 293 | methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/String", "", "([BLjava/lang/String;)V", false); 294 | methodVisitor.visitLabel(label1); 295 | methodVisitor.visitInsn(ARETURN); 296 | methodVisitor.visitLabel(label2); 297 | methodVisitor.visitLineNumber(71, label2); 298 | methodVisitor.visitFrame(Opcodes.F_FULL, 2, new Object[]{"java/lang/String", Opcodes.INTEGER}, 1, new Object[]{"java/lang/Exception"}); 299 | methodVisitor.visitVarInsn(ASTORE, 2); 300 | Label label19 = new Label(); 301 | methodVisitor.visitLabel(label19); 302 | methodVisitor.visitLineNumber(72, label19); 303 | methodVisitor.visitVarInsn(ALOAD, 2); 304 | methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false); 305 | Label label20 = new Label(); 306 | methodVisitor.visitLabel(label20); 307 | methodVisitor.visitLineNumber(74, label20); 308 | methodVisitor.visitLdcInsn(""); 309 | methodVisitor.visitInsn(ARETURN); 310 | Label label21 = new Label(); 311 | methodVisitor.visitLabel(label21); 312 | methodVisitor.visitLocalVariable("pos", "I", null, label9, label10, 6); 313 | methodVisitor.visitLocalVariable("i", "I", null, label6, label7, 5); 314 | methodVisitor.visitLocalVariable("i", "I", null, label14, label15, 7); 315 | methodVisitor.visitLocalVariable("length", "I", null, label3, label2, 2); 316 | methodVisitor.visitLocalVariable("hexChars", "[C", null, label4, label2, 3); 317 | methodVisitor.visitLocalVariable("stringByte", "[B", null, label5, label2, 4); 318 | methodVisitor.visitLocalVariable("keyXor", "B", null, label11, label2, 5); 319 | methodVisitor.visitLocalVariable("temp", "B", null, label13, label2, 6); 320 | methodVisitor.visitLocalVariable("e", "Ljava/lang/Exception;", null, label19, label20, 2); 321 | methodVisitor.visitLocalVariable("ciphertext", "Ljava/lang/String;", null, label0, label21, 0); 322 | methodVisitor.visitLocalVariable("key1", "I", null, label0, label21, 1); 323 | methodVisitor.visitMaxs(7, 8); 324 | methodVisitor.visitEnd(); 325 | } 326 | 327 | } 328 | -------------------------------------------------------------------------------- /string_obfuscated/src/main/java/com/wzc/gradle/plugin/utils/StringEncryptionTestUtil.java: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin.utils; 2 | 3 | 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Arrays; 6 | import java.util.Random; 7 | 8 | public class StringEncryptionTestUtil { 9 | 10 | 11 | private static int getRandomKey() { 12 | Random rand = new Random(); 13 | return rand.nextInt(127); 14 | } 15 | 16 | public static void main(String[] args) { 17 | int key1 = getRandomKey(); 18 | int key2 = getRandomKey(); 19 | int key3 = getRandomKey(); 20 | int key4 = getRandomKey(); 21 | // String s0 = encryption("", key1, key2, key3); 22 | // String s1 = encryption("wzc@123哈哈哈", key1, key2, key3); 23 | // String s2 = encryption("english\n中文@#¥……%……&*()——+!@#$%^&*()123哈哈哈", key4, key2, key3); 24 | // System.out.println("加密后:s1=" + s1 + " s2=" + s2); 25 | // String d1 = decrypt(s1, key1, key2, key3); 26 | // String d2 = decrypt("aa1974290d703b165f013a5b", 11, 1, 76); 27 | // System.out.println("解密后:d1=" + d1 + " d2=" + d2); 28 | } 29 | 30 | 31 | public static String decrypt(String ciphertext, int key1, int key2, int key3) { 32 | 33 | try { 34 | int length = ciphertext.length() / 2; 35 | char[] hexChars = ciphertext.toCharArray(); 36 | byte[] stringByte = new byte[length]; 37 | for (int i = 0; i < length; i++) { 38 | int pos = i * 2; 39 | stringByte[i] = (byte) ("0123456789abcdef".indexOf(hexChars[pos]) << 4 | "0123456789abcdef".indexOf(hexChars[pos + 1])); 40 | } 41 | byte keyXor = (byte) (key1 ^ key2); 42 | System.out.println("解密前:" + Arrays.toString(stringByte)); 43 | stringByte[0] = (byte) (stringByte[0] ^ key3); 44 | byte temp = stringByte[0]; //解密之后的数据 45 | for (int i = 1; i < stringByte.length; i++) { 46 | stringByte[i] = (byte) (stringByte[i] ^ temp ^ keyXor); 47 | temp = stringByte[i]; 48 | } 49 | System.out.println("解密后:" + Arrays.toString(stringByte)); 50 | return new String(stringByte, "UTF-8"); 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | } 54 | return ""; 55 | } 56 | 57 | /** 58 | * 字符串加密 59 | * String -> byte 60 | * 按byte加密,加密之后参与下一个字节的加密 61 | * 62 | * @param string 原始字符串 63 | * @param key1 key1 64 | * @param key2 key2 65 | * @param key3 key3 66 | * @return 加密后的字符串 67 | */ 68 | public static String encryption(String string, int key1, int key2, int key3) { 69 | byte[] stringByte = string.getBytes(StandardCharsets.UTF_8); 70 | // System.out.println("string加密前:" + Arrays.toString(stringByte)); 71 | byte keyXor = (byte) (key1 ^ key2); 72 | StringBuilder result = new StringBuilder(); 73 | byte c = (byte) (stringByte[0] ^ key3); 74 | result.append(String.format("%02x", c)); 75 | byte key4 = stringByte[0]; 76 | for (int i = 1; i < stringByte.length; i++) { 77 | byte temp = stringByte[i]; 78 | c = (byte) (stringByte[i] ^ key4 ^ keyXor); 79 | key4 = temp; 80 | // System.out.println("string[" + i + "]加密后:" + c + " 转16进制" + String.format("%02x", c)); 81 | result.append(String.format("%02x", c)); 82 | } 83 | return result.toString(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /string_obfuscated/src/main/kotlin/com/wzc/gradle/plugin/PluginLaunch.kt: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin 2 | 3 | import com.android.build.api.instrumentation.* 4 | import com.android.build.api.variant.AndroidComponentsExtension 5 | import com.android.build.gradle.AppPlugin 6 | import com.android.build.gradle.LibraryPlugin 7 | import com.wzc.gradle.plugin.model.StringObfuscateExtensions 8 | import com.wzc.gradle.plugin.utils.Logger 9 | import org.gradle.api.Plugin 10 | import org.gradle.api.Project 11 | import org.gradle.api.tasks.Internal 12 | import org.objectweb.asm.ClassVisitor 13 | import org.objectweb.asm.Opcodes 14 | 15 | class PluginLaunch : Plugin { 16 | 17 | private val EXTENSIONS_NAME = "stringObfuscate" 18 | override fun apply(project: Project) { 19 | Logger.d("###############################################") 20 | Logger.d("######### 欢迎使用字符串混淆插件!!! #########") 21 | Logger.d("###############################################") 22 | val app = project.plugins.hasPlugin(AppPlugin::class.java) 23 | val library = project.plugins.hasPlugin(LibraryPlugin::class.java) 24 | project.extensions.create(EXTENSIONS_NAME, StringObfuscateExtensions::class.java) 25 | val androidComponentsExtension = 26 | project.extensions.getByType(AndroidComponentsExtension::class.java) 27 | androidComponentsExtension.onVariants { variant -> 28 | val extensions = 29 | project.extensions.getByName(EXTENSIONS_NAME) as StringObfuscateExtensions 30 | Logger.openLog = extensions.openLog 31 | variant.instrumentation.apply { 32 | if (app) { 33 | // ALL检测当前项目的类及其库依赖项。 34 | transformClassesWith( 35 | ObfuscateAsmClassVisitorFactory::class.java, InstrumentationScope.ALL 36 | ) { params -> 37 | // parameters configuration 38 | params.packageList = extensions.packageName 39 | } 40 | } else if (library) { 41 | Logger.d("请在app module中使用插件!!!") 42 | } 43 | setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES) 44 | } 45 | } 46 | } 47 | 48 | interface ParametersImpl : InstrumentationParameters { 49 | @get:Internal 50 | var packageList: List 51 | } 52 | 53 | abstract class ObfuscateAsmClassVisitorFactory : AsmClassVisitorFactory { 54 | 55 | override fun createClassVisitor( 56 | classContext: ClassContext, 57 | nextClassVisitor: ClassVisitor 58 | ): ClassVisitor { 59 | return ScanClassVisitor(Opcodes.ASM9, nextClassVisitor) 60 | } 61 | 62 | override fun isInstrumentable(classData: ClassData): Boolean { 63 | val packageList = parameters.get().packageList 64 | for (packageName in packageList) { 65 | if (classData.className.startsWith(packageName)) { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /string_obfuscated/src/main/kotlin/com/wzc/gradle/plugin/model/StringObfuscateExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin.model 2 | 3 | open class StringObfuscateExtensions { 4 | 5 | var packageName: List = ArrayList() 6 | var openLog: Boolean = true 7 | } 8 | -------------------------------------------------------------------------------- /string_obfuscated/src/main/kotlin/com/wzc/gradle/plugin/utils/Logger.kt: -------------------------------------------------------------------------------- 1 | package com.wzc.gradle.plugin.utils 2 | 3 | object Logger { 4 | 5 | var openLog = true 6 | 7 | fun d(message: String) { 8 | if (openLog) 9 | println("######### $message") 10 | } 11 | } -------------------------------------------------------------------------------- /test_library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /test_library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | compileSdkVersion 32 7 | 8 | defaultConfig { 9 | minSdkVersion 21 10 | targetSdkVersion 32 11 | 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles "consumer-rules.pro" 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | 26 | implementation 'androidx.appcompat:appcompat:1.4.1' 27 | implementation 'com.google.android.material:material:1.5.0' 28 | testImplementation 'junit:junit:4.13.2' 29 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 30 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 31 | } -------------------------------------------------------------------------------- /test_library/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuzuchang/StringObfuscatedPlugin/1aa1b3ea2bdfcc4011617775708ef5be2ba69fb2/test_library/consumer-rules.pro -------------------------------------------------------------------------------- /test_library/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 -------------------------------------------------------------------------------- /test_library/src/androidTest/java/com/wzc/test_library/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.wzc.test_library; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.wzc.test_library.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /test_library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /test_library/src/main/java/com/wzc/test/Test1.java: -------------------------------------------------------------------------------- 1 | package com.wzc.test; 2 | 3 | public class Test1 { 4 | private String string1 = "测试中文"; 5 | private final String string2 = "test English"; 6 | private static String string3 = "测试特殊字符@!#¥%……&*()~+——()_="; //会生成 7 | private static final String string4 = "测试换行\n换行了"; 8 | private static final String string6 = "测试\r\n换行了"; 9 | private static final String string5; 10 | 11 | static { 12 | string5 = "测试静态代码块"; //会生成 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "Test1{" + "string1='" + string1 + '\'' + ", string2='" + string2 + '\'' + ", string3='" + string3 + '\'' + ", string4='" + string4 + '\'' + ", string5='" + string5 + '\'' + ", string6='" + string6 + '\'' + '}'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test_library/src/main/java/com/wzc/test_library/Test2.java: -------------------------------------------------------------------------------- 1 | package com.wzc.test_library; 2 | 3 | public class Test2 { 4 | private String string1 = "测试中文"; 5 | private final String string2 = "test English"; 6 | private static String string3 = "测试特殊字符@!#¥%……&*()~+——()_="; //会生成 7 | private static final String string4 = "测试换行\n换行了"; 8 | private static final String string6 = "测试\r\n换行了"; 9 | private static final String string5; 10 | 11 | static { 12 | string5 = "测试静态代码块"; //会生成 13 | } 14 | @Override 15 | public String toString() { 16 | return "Test1{" + "string1='" + string1 + '\'' + ", string2='" + string2 + '\'' + ", string3='" + string3 + '\'' + ", string4='" + string4 + '\'' + ", string5='" + string5 + '\'' + ", string6='" + string6 + '\'' + '}'; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test_library/src/test/java/com/wzc/test_library/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.wzc.test_library; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } --------------------------------------------------------------------------------