├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── key.keystore ├── maindex-keep.txt ├── maindex-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── joker │ │ └── maindexkeep │ │ ├── App.kt │ │ ├── MainActivity.kt │ │ ├── MultiProcessService.kt │ │ ├── SecondActivity.kt │ │ ├── annotations │ │ ├── AnnotationWrapper.kt │ │ ├── ClassAnn.kt │ │ ├── RuntimeAnn.kt │ │ └── Type.kt │ │ ├── model │ │ ├── AnnotationWrapperReference.kt │ │ ├── AnnotationWrapperReferenceBase.kt │ │ ├── AppReference.kt │ │ └── Person.kt │ │ ├── shrink │ │ ├── First.kt │ │ └── Second.kt │ │ └── shrink2 │ │ ├── Fourth.kt │ │ └── Third.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_main.xml │ └── activity_second.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 │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── buildSrc ├── .gitignore ├── build.gradle ├── local.properties └── src │ └── main │ ├── kotlin │ └── com │ │ └── joker │ │ └── buildsrc │ │ ├── ThinAnnotationPlugin.kt │ │ ├── ThinAnnotationTask.kt │ │ ├── core │ │ ├── AnnotationClassVisitor.kt │ │ ├── ClassFile.kt │ │ └── ClzClassVisitor.kt │ │ ├── extension │ │ ├── ShrinkPkgConfig.kt │ │ └── ThinAnnotationExtension.kt │ │ └── util │ │ ├── FileUtil.kt │ │ ├── LogFile.kt │ │ └── MappingUtil.kt │ └── resources │ └── META-INF │ └── gradle-plugins │ └── thinAnnotation.properties ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 相关文章链接:[MainDex 优化记](https://mp.weixin.qq.com/s?__biz=MzUyMDg2ODgwOQ==&mid=2247483679&idx=1&sn=4520ae38f703ee3b2303c02b47a80639&chksm=f9e287b9ce950eafa38deb9d8bf898a3abcaa0234adcc08ad017d8fd89bfc612a8d6e6a1cefe&token=1165484979&lang=zh_CN#rd) 2 | 3 | ### 原理 4 | 5 | 此插件只作用于打包过程,编码过程无感知、无影响。 6 | 7 | 混淆之后将会产生一个包含应用中所有 class 的 jar,通过 ASM 扫描所有的类、类的方法、类的字段等一切可能会出现注解的地方,扫描到开发者配置的注解则将其删除;而如果当前类是注解类且是开发者所配置的类的话,该注解类将会被删除。 8 | 9 | ### 配置 10 | 11 | thinAnnotation 默认删除**所有**的 SOURCE 时期注解,因为使用 SOURCE 时期注解的地方实际上会在 .java -> .class 过程中被擦除,所以所有的 SOURCE 注解类实际上都是无用类,例如 `android/support/design/widget/TabLayout$Mode` 类;thinAnnotation 默认删除了 `butterknife/` 包、 `android/support/annotation/` 包、`androidx/annotation/` 包的所有注解,原因是这些 CLASS 时期的类在运行时是无用的(除非特殊要求,CLASS 文件实际上也是可以全部删除的);开发者只需要配置想要删除的 CLASS 和 RUNTIME 时期的注解类,配置方式如下—— 12 | 13 | 在 project/build.gradle 中: 14 | 15 | ``` 16 | buildscript { 17 | repositories { 18 | maven { url 'https://jitpack.io' } 19 | } 20 | dependencies { 21 | classpath 'com.github.jokermonn:thinAnnotation:0.0.2' 22 | } 23 | } 24 | ``` 25 | 26 | 在 app/build.gradle 中: 27 | 28 | ``` 29 | apply plugin: 'thinAnnotation' 30 | 31 | thinAnnotation { 32 | enable true 33 | shrinkClass 'com/joker/maindexkeep/annotations/RuntimeAnn' 34 | // 删除 com/joker/maindexkeep/shrink/ 下所有注解 35 | shrinkPackage('com/joker/maindexkeep/shrink/', { true }) 36 | // 删除 com/joker/maindexkeep/shrink2/ 下除 RUNTIME 之外的所有注解 37 | shrinkPackage 'com/joker/maindexkeep/shrink2/' 38 | } 39 | ``` 40 | 41 | 日志在:`app/build/outputs/thinAnnotation` 路径下。 42 | 43 | ## 示例 44 | 45 | 以 butterknife 为例: 46 | 47 | 使用前:butterknife 包注解类存在,`@BindView` 等注解存在于 .class 文件中 48 | 49 | ![](http://imglf6.nosdn0.126.net/img/UnlRcDgySWkxbnZUbjBCSXdnUFoza3RCR0R1TGY0M0x6dkxwQksxaFdJS3FJUHhmT2FCK21RPT0.png?imageView&thumbnail=2490y1632&type=png&quality=96&stripmeta=0) 50 | 51 | 使用后:butterknife 包注解类全部删除,所有使用该注解的地方也都会被清除注解 52 | 53 | ![](http://imglf5.nosdn0.126.net/img/UnlRcDgySWkxbnZUbjBCSXdnUFozanN2dzFqaU4xREZZalNtc2JrSGw0WXNQWEQ5NlpQNUlnPT0.png?imageView&thumbnail=2238y1484&type=png&quality=96&stripmeta=0) 54 | 55 | ## CHANGELOG 56 | 57 | 0.0.3: 58 | 59 | - fix [issue#1](https://github.com/jokermonn/thinAnnotation/issues/1) 60 | - 使用方式稍作改变 61 | - 丰富 thinAnnotation log 62 | - 使用 kotlin 63 | - 增加对 `org/intellij/lang/annotations/`、`org/jetbrains/annotations/` 的注解类删除 64 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'com.getkeepsafe.dexcount' 5 | apply plugin: 'thinAnnotation' 6 | 7 | android { 8 | compileSdkVersion 28 9 | 10 | defaultConfig { 11 | applicationId "com.joker.maindexkeep" 12 | minSdkVersion 14 13 | targetSdkVersion 28 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | multiDexEnabled true 18 | multiDexKeepProguard file('./maindex-rules.pro') 19 | multiDexKeepFile file('./maindex-keep.txt') 20 | } 21 | 22 | signingConfigs { 23 | release { 24 | keyAlias 'key0' 25 | storeFile file("${project.projectDir}/key.keystore") 26 | keyPassword 'thinann' 27 | storePassword 'thinann' 28 | } 29 | } 30 | 31 | buildTypes { 32 | release { 33 | signingConfig signingConfigs.release 34 | minifyEnabled true 35 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 36 | } 37 | } 38 | 39 | dexOptions { 40 | // keepRuntimeAnnotatedClasses false 41 | } 42 | } 43 | 44 | thinAnnotation { 45 | enable true 46 | shrinkClass 'com/joker/maindexkeep/annotations/RuntimeAnn' 47 | // com/joker/maindexkeep/shrink/ 删除所有注解 48 | shrinkPackage('com/joker/maindexkeep/shrink/', { true }) 49 | // com/joker/maindexkeep/shrink2/ 删除除 RUNTIME 之外的所有注解 50 | shrinkPackage 'com/joker/maindexkeep/shrink2/' 51 | } 52 | 53 | dependencies { 54 | implementation fileTree(dir: 'libs') 55 | implementation 'com.squareup.wire:wire-runtime:2.2.0' 56 | implementation 'com.google.code.gson:gson:2.8.5' 57 | implementation 'com.android.support:appcompat-v7:28.0.0' 58 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 59 | implementation 'com.android.support:multidex:1.0.3' 60 | implementation 'com.google.guava:guava:27.0.1-android' 61 | implementation "io.reactivex.rxjava2:rxjava:2.2.6" 62 | implementation 'com.squareup.retrofit2:retrofit:2.4.0' 63 | implementation 'com.squareup.okhttp3:okhttp:3.12.1' 64 | implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0' 65 | implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' 66 | implementation 'com.squareup.retrofit2:converter-gson:2.4.0' 67 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' 68 | implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1' 69 | implementation 'com.android.support:exifinterface:28.0.0' 70 | implementation 'com.github.bumptech.glide:glide:4.8.0' 71 | implementation 'com.miguelcatalan:materialsearchview:1.0.1' 72 | implementation 'com.sdsmdg.harjot:vectormaster:1.0.5' 73 | implementation 'com.amap.api:3dmap:5.7.0' 74 | implementation 'com.amap.api:location:3.5.0' 75 | implementation 'com.amap.api:search:5.7.0' 76 | implementation 'com.xyz.step:step:1.0.4' 77 | implementation 'com.rengwuxian.materialedittext:library:2.1.4' 78 | implementation 'info.hoang8f:fbutton:1.0.5' 79 | implementation 'me.drakeet.materialdialog:library:1.3.1' 80 | implementation 'com.daimajia.swipelayout:library:1.2.0@aar' 81 | implementation 'com.squareup.leakcanary:leakcanary-android:1.6.1' 82 | implementation 'com.android.support:cardview-v7:28.0.0' 83 | implementation 'com.android.support:support-v4:28.0.0' 84 | implementation 'com.android.support:appcompat-v7:28.0.0' 85 | implementation 'com.android.support:design:28.0.0' 86 | implementation 'com.facebook.fresco:fresco:1.11.0' 87 | implementation 'com.facebook.fresco:animated-gif:1.11.0' 88 | implementation 'com.facebook.fresco:animated-webp:1.11.0' 89 | implementation 'com.facebook.fresco:webpsupport:1.11.0' 90 | implementation 'com.facebook.fresco:webpsupport:1.11.0' 91 | implementation 'com.android.support:preference-v7:28.0.0' 92 | implementation 'com.jakewharton:butterknife:8.4.0' 93 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' 94 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41" 95 | } 96 | -------------------------------------------------------------------------------- /app/key.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/key.keystore -------------------------------------------------------------------------------- /app/maindex-keep.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/maindex-keep.txt -------------------------------------------------------------------------------- /app/maindex-rules.pro: -------------------------------------------------------------------------------- 1 | #-keep class com.joker.maindexkeep.model.AppReferencerence { 2 | # public void test(); 3 | #} -------------------------------------------------------------------------------- /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 22 | -dontshrink # 为了超过 66536 23 | -dontusemixedcaseclassnames 24 | -dontskipnonpubliclibraryclasses 25 | -verbose 26 | -dontpreverify 27 | -dontoptimize 28 | -dontwarn 29 | -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase 30 | -dontwarn org.codehaus.mojo.animal_sniffer.* 31 | -dontwarn okhttp3.internal.platform.ConscryptPlatform 32 | -keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation* 33 | -keepclassmembers,allowshrinking,allowobfuscation interface * { 34 | @retrofit2.http.* ; 35 | } 36 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 37 | -dontwarn javax.annotation.** 38 | -dontwarn kotlin.Unit 39 | -dontwarn retrofit2.-KotlinExtensions 40 | -keep class sun.misc.Unsafe { *; } 41 | -dontwarn java.nio.file.* 42 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 43 | -dontwarn okio.** 44 | -keep class com.google.common.io.Resources { 45 | public static ; 46 | } 47 | -keep class com.google.common.collect.Lists { 48 | public static ** reverse(**); 49 | } 50 | -keep class com.google.common.base.Charsets { 51 | public static ; 52 | } 53 | 54 | -keep class com.google.common.base.Joiner { 55 | public static com.google.common.base.Joiner on(java.lang.String); 56 | public ** join(...); 57 | } 58 | 59 | -keep class com.google.common.collect.MapMakerInternalMap$ReferenceEntry 60 | -keep class com.google.common.cache.LocalCache$ReferenceEntry 61 | -dontwarn javax.annotation.** 62 | -dontwarn javax.inject.** 63 | -dontwarn sun.misc.Unsafe 64 | -dontwarn java.lang.ClassValue 65 | -dontwarn com.google.j2objc.annotations.Weak 66 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 67 | -dontwarn javax.lang.model.element.Modifier 68 | 69 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 70 | 71 | -keep @com.facebook.common.internal.DoNotStrip class * 72 | -keepclassmembers class * { 73 | @com.facebook.common.internal.DoNotStrip *; 74 | } 75 | -keepclassmembers class * { 76 | native ; 77 | } 78 | -dontwarn okio.** 79 | -dontwarn javax.annotation.** 80 | -dontwarn com.android.volley.toolbox.** 81 | 82 | # app 83 | -keep class com.joker.maindexkeep.model.AppReference { 84 | public void test(); 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/App.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep 2 | 3 | import android.content.Context 4 | import android.support.multidex.MultiDex 5 | import android.support.multidex.MultiDexApplication 6 | import android.util.Log 7 | import com.joker.maindexkeep.model.AppReference 8 | 9 | class App : MultiDexApplication() { 10 | override fun attachBaseContext(base: Context) { 11 | super.attachBaseContext(base) 12 | val useless = AppReference() 13 | } 14 | 15 | override fun onCreate() { 16 | MultiDex.install(this) 17 | super.onCreate() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.joker.maindexkeep.annotations.RuntimeAnn 6 | 7 | /** 8 | * 方法/类被运行时注解所修饰,所以当前类将会被打入 maindex 9 | */ 10 | @RuntimeAnn 11 | class MainActivity : AppCompatActivity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(R.layout.activity_main) 16 | } 17 | 18 | @RuntimeAnn 19 | internal fun mainRuntime() { 20 | println("hello, world") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/MultiProcessService.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.os.IBinder 6 | 7 | /** 8 | * manifest 中有注释 9 | */ 10 | class MultiProcessService : Service() { 11 | 12 | override fun onBind(intent: Intent): IBinder? { 13 | // TODO: Return the communication channel to the service. 14 | throw UnsupportedOperationException("Not yet implemented") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/SecondActivity.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.widget.TextView 6 | import butterknife.BindView 7 | import com.joker.maindexkeep.annotations.RuntimeAnn 8 | 9 | /** 10 | * [BindView] 是运行时注解,所以当前类将会被打入 maindex 11 | */ 12 | class SecondActivity : AppCompatActivity() { 13 | 14 | @BindView(R.id.tv) 15 | internal var tv: TextView? = null 16 | 17 | @RuntimeAnn 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_second) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/annotations/AnnotationWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.annotations 2 | 3 | import android.support.annotation.StringDef 4 | import com.joker.maindexkeep.annotations.AnnotationWrapper.Nothing.Companion.A 5 | import com.joker.maindexkeep.annotations.AnnotationWrapper.Nothing.Companion.B 6 | import com.joker.maindexkeep.model.AnnotationWrapperReference 7 | import kotlin.annotation.AnnotationRetention.SOURCE 8 | 9 | /** 10 | * 由于包含注解内部类,所以本身及本身引用的 [AnnotationWrapperReference]及 [AnnotationWrapperReference] 11 | * 的引用类都将会被打入 maindex 12 | */ 13 | class AnnotationWrapper { 14 | 15 | fun test() { 16 | val reference = AnnotationWrapperReference() 17 | } 18 | 19 | @kotlin.annotation.Retention(SOURCE) 20 | @StringDef(A, B) 21 | annotation class Nothing { 22 | companion object { 23 | const val A = "a" 24 | const val B = "b" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/annotations/ClassAnn.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.annotations 2 | 3 | import kotlin.annotation.AnnotationRetention.BINARY 4 | 5 | @kotlin.annotation.Retention(BINARY) 6 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, 7 | AnnotationTarget.PROPERTY_SETTER) 8 | annotation class ClassAnn 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/annotations/RuntimeAnn.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.annotations 2 | 3 | import kotlin.annotation.AnnotationRetention.RUNTIME 4 | 5 | @kotlin.annotation.Retention(RUNTIME) 6 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, 7 | AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CLASS, AnnotationTarget.FILE, 8 | AnnotationTarget.ANNOTATION_CLASS) 9 | annotation class RuntimeAnn 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/annotations/Type.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.annotations 2 | 3 | import android.support.annotation.StringDef 4 | import kotlin.annotation.AnnotationRetention.RUNTIME 5 | 6 | @RuntimeAnn 7 | @kotlin.annotation.Retention(RUNTIME) 8 | @StringDef(Type.A, Type.B) 9 | annotation class Type { 10 | companion object { 11 | const val A = "a" 12 | const val B = "b" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/model/AnnotationWrapperReference.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.model 2 | 3 | class AnnotationWrapperReference : AnnotationWrapperReferenceBase() 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/model/AnnotationWrapperReferenceBase.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.model 2 | 3 | open class AnnotationWrapperReferenceBase 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/model/AppReference.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.model 2 | 3 | class AppReference { 4 | fun test() { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/model/Person.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * 由于 [SerializedName] 是运行时注解,所以当前类将会被打入 maindex 7 | */ 8 | class Person { 9 | @SerializedName("i") 10 | private val i: Int = 0 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/shrink/First.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.shrink 2 | 3 | import kotlin.annotation.AnnotationRetention.BINARY 4 | 5 | @kotlin.annotation.Retention(BINARY) 6 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, 7 | AnnotationTarget.PROPERTY_SETTER) 8 | annotation class First 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/shrink/Second.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.shrink 2 | 3 | import kotlin.annotation.AnnotationRetention.RUNTIME 4 | 5 | @kotlin.annotation.Retention(RUNTIME) 6 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, 7 | AnnotationTarget.PROPERTY_SETTER) 8 | annotation class Second 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/shrink2/Fourth.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.shrink2 2 | 3 | import kotlin.annotation.AnnotationRetention.BINARY 4 | 5 | @kotlin.annotation.Retention(BINARY) 6 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, 7 | AnnotationTarget.PROPERTY_SETTER) 8 | annotation class Fourth -------------------------------------------------------------------------------- /app/src/main/java/com/joker/maindexkeep/shrink2/Third.kt: -------------------------------------------------------------------------------- 1 | package com.joker.maindexkeep.shrink2 2 | 3 | import kotlin.annotation.AnnotationRetention.RUNTIME 4 | 5 | @kotlin.annotation.Retention(RUNTIME) 6 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, 7 | AnnotationTarget.PROPERTY_SETTER) 8 | annotation class Third -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_second.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /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/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MainDexKeep 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | maven { url 'https://jitpack.io' } 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.5' 10 | classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41" 12 | // classpath 'com.github.jokermonn:thinAnnotation:0.0.3' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } 27 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | 3 | dependencies { 4 | implementation gradleApi() 5 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41" 6 | implementation 'com.android.tools.build:gradle:3.0.1' 7 | } 8 | 9 | allprojects { 10 | repositories { 11 | google() 12 | jcenter() 13 | } 14 | } 15 | 16 | buildscript { 17 | repositories { 18 | google() 19 | jcenter() 20 | mavenCentral() 21 | } 22 | dependencies { 23 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 24 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41" 25 | } 26 | } 27 | 28 | apply plugin: 'com.github.dcendents.android-maven' 29 | group = 'com.github.jokermonn' -------------------------------------------------------------------------------- /buildSrc/local.properties: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file should *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | sdk.dir=/Users/baolongzhang/Library/Android/sdk 11 | 12 | nexusUsername=eleme.mobile.android 13 | nexusPassword=8c-3r1l*kI2Z3{ -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/ThinAnnotationPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc 2 | 3 | import com.android.build.gradle.AppExtension 4 | import com.android.build.gradle.internal.api.ApplicationVariantImpl 5 | import com.joker.buildsrc.extension.ThinAnnotationExtension 6 | import org.gradle.api.Plugin 7 | import org.gradle.api.Project 8 | import org.gradle.api.logging.LogLevel.DEBUG 9 | 10 | class ThinAnnotationPlugin : Plugin { 11 | 12 | override fun apply(project: Project) { 13 | val extension = project.extensions.create( 14 | ThinAnnotationExtension.NAME, ThinAnnotationExtension::class.java) 15 | 16 | project.afterEvaluate { 17 | if (project.plugins.hasPlugin("com.android.application")) { 18 | project.extensions.getByType(AppExtension::class.java).applicationVariants.all { variant -> 19 | if (extension.enable) { 20 | project.logger.log(DEBUG, "ThinAnnotation: ThinAnnotation is enabled") 21 | 22 | val varNameCap = variant.name.capitalize() 23 | val proguardTask = project.tasks.findByName( 24 | "transformClassesAndResourcesWithProguardFor$varNameCap") 25 | if (proguardTask?.enabled == true) { 26 | val thinAnnTask = project.tasks.create( 27 | "thinAnnotationFor$varNameCap", 28 | ThinAnnotationTask::class.java) { 29 | it.variantImpl = variant as ApplicationVariantImpl 30 | it.shrinkClass = extension.shrinkClass 31 | it.shrinkPackage = extension.shrinkPackage 32 | } 33 | 34 | proguardTask.finalizedBy(thinAnnTask) 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/ThinAnnotationTask.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc 2 | 3 | import com.android.build.gradle.internal.api.ApplicationVariantImpl 4 | import com.joker.buildsrc.core.ClassFile 5 | import com.joker.buildsrc.extension.ShrinkPkgConfig 6 | import com.joker.buildsrc.util.LogFile 7 | import com.joker.buildsrc.util.MappingUtil 8 | import com.joker.buildsrc.util.FileUtil 9 | import org.gradle.api.DefaultTask 10 | import org.gradle.api.logging.LogLevel.DEBUG 11 | import org.gradle.api.tasks.Input 12 | import org.gradle.api.tasks.TaskAction 13 | import java.io.File 14 | import java.io.FileOutputStream 15 | import java.io.IOException 16 | import java.util.jar.JarFile 17 | import java.util.jar.JarOutputStream 18 | import java.util.zip.ZipEntry 19 | 20 | open class ThinAnnotationTask : DefaultTask() { 21 | 22 | @Input 23 | lateinit var variantImpl: ApplicationVariantImpl 24 | 25 | @Input 26 | lateinit var shrinkClass: MutableSet 27 | 28 | @Input 29 | lateinit var shrinkPackage: MutableSet 30 | 31 | @Throws(IOException::class) 32 | @TaskAction 33 | fun action() { 34 | val logger = project.logger 35 | val variantScope = variantImpl.variantData.scope 36 | 37 | logger.log(DEBUG, "ThinAnnotation: ThinAnnotationTask start executing...") 38 | 39 | // create log file 40 | LogFile.init(variantScope) 41 | 42 | // get mapping file 43 | MappingUtil.init(variantImpl.mappingFile) 44 | 45 | // remove annotations 46 | val proguardTask = project.tasks.findByName( 47 | "transformClassesAndResourcesWithProguardFor${variantImpl.name.capitalize()}") 48 | val file = proguardTask!!.outputs.files.files.find { s -> s.isDirectory }!!.walkTopDown() 49 | .maxDepth(1).filter { it.name.endsWith(".jar") }.first() 50 | process(file) 51 | 52 | LogFile.print() 53 | 54 | logger.log(DEBUG, "ThinAnnotation: ThinAnnotationTask is finishing...") 55 | } 56 | 57 | private fun process(file: File) { 58 | // scan annotation 59 | var jf = JarFile(file) 60 | 61 | var tempJar = File(file.parentFile, "temp.jar") 62 | var jos = JarOutputStream(FileOutputStream(tempJar)) 63 | var je = jf.entries() 64 | 65 | for (jarEntry in je.iterator()) { 66 | val zipEntry = ZipEntry(jarEntry.name) 67 | val originIns = jf.getInputStream(jarEntry) 68 | var annBytes: ByteArray? = FileUtil.toByteArray(originIns) 69 | originIns.close() 70 | if (jarEntry.name.endsWith(".class")) { 71 | val classFile = ClassFile(annBytes) 72 | if (classFile.isAnnotation) { 73 | annBytes = classFile.process(shrinkClass, shrinkPackage) 74 | } 75 | } 76 | annBytes?.let { 77 | jos.putNextEntry(zipEntry) 78 | jos.write(annBytes) 79 | jos.closeEntry() 80 | } 81 | } 82 | jos.close() 83 | jf.close() 84 | 85 | // scan class 86 | jf = JarFile(tempJar) 87 | tempJar = File(file.parentFile, "temp2.jar") 88 | jos = JarOutputStream(FileOutputStream(tempJar)) 89 | je = jf.entries() 90 | 91 | for (jarEntry in je.iterator()) { 92 | val zipEntry = ZipEntry(jarEntry.name) 93 | val originIns = jf.getInputStream(jarEntry) 94 | var clzBytes: ByteArray? = FileUtil.toByteArray(originIns) 95 | originIns.close() 96 | if (jarEntry.name.endsWith(".class")) { 97 | val classFile = ClassFile(clzBytes) 98 | if (!classFile.isAnnotation) { 99 | clzBytes = classFile.process(shrinkClass, shrinkPackage) 100 | } 101 | } 102 | clzBytes?.let { 103 | jos.putNextEntry(zipEntry) 104 | jos.write(clzBytes) 105 | jos.closeEntry() 106 | } 107 | } 108 | 109 | jos.close() 110 | jf.close() 111 | file.delete() 112 | FileUtil.renameFile(tempJar, file) 113 | } 114 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/core/AnnotationClassVisitor.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc.core 2 | 3 | import com.joker.buildsrc.extension.ShrinkPkgConfig 4 | import com.joker.buildsrc.util.MappingUtil 5 | import org.objectweb.asm.AnnotationVisitor 6 | import org.objectweb.asm.ClassVisitor 7 | import org.objectweb.asm.ClassWriter 8 | import org.objectweb.asm.Opcodes 9 | 10 | class AnnotationClassVisitor(classWriter: ClassWriter, val clzName: String, 11 | private val shrinkClass: MutableSet, 12 | private val shrinkPackage: Set) : 13 | ClassVisitor(Opcodes.ASM4, classWriter) { 14 | 15 | private companion object { 16 | private const val RETENTION_POLICY = "Ljava/lang/annotation/RetentionPolicy;" 17 | private const val SOURCE = "SOURCE" 18 | private const val RUNTIME = "RUNTIME" 19 | } 20 | 21 | var canRemoved: Boolean = false 22 | 23 | override fun visitAnnotation(ann: String, visible: Boolean): AnnotationVisitor? { 24 | return object : AnnotationVisitor(Opcodes.ASM4, super.visitAnnotation(ann, visible)) { 25 | override fun visitEnum(name: String?, desc1: String?, value: String?) { 26 | // RetentionPolicy 为 SOURCE 27 | if (desc1 == RETENTION_POLICY) { 28 | if (value == SOURCE) { 29 | canRemoved = true 30 | } else if (needRemove(clzName, value == RUNTIME)) { 31 | canRemoved = true 32 | } 33 | } 34 | super.visitEnum(name, desc1, value) 35 | } 36 | } 37 | } 38 | 39 | private fun needRemove(className: String, isRuntime: Boolean): Boolean { 40 | val newName = MappingUtil.decodeProguard(className) 41 | 42 | val any = shrinkPackage.find { 43 | newName.startsWith(it.pkgName) && if (isRuntime) it.ignoreRuntime else true 44 | } 45 | any?.let { shrinkClass.add(newName) } 46 | return any != null || shrinkClass.contains(newName) 47 | } 48 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/core/ClassFile.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc.core 2 | 3 | import com.joker.buildsrc.extension.ShrinkPkgConfig 4 | import com.joker.buildsrc.util.LogFile 5 | import com.joker.buildsrc.util.MappingUtil 6 | import org.objectweb.asm.ClassReader 7 | import org.objectweb.asm.ClassWriter 8 | import org.objectweb.asm.Opcodes 9 | 10 | class ClassFile(private val bytes: ByteArray?) { 11 | 12 | internal val isAnnotation: Boolean 13 | 14 | init { 15 | val cr = ClassReader(bytes) 16 | isAnnotation = cr.access and Opcodes.ACC_ANNOTATION != 0 17 | } 18 | 19 | fun process(shrinkClasses: MutableSet, shrinkPackage: Set): ByteArray? { 20 | val cr = ClassReader(bytes) 21 | val cw = ClassWriter(cr, 0) 22 | return if (isAnnotation) { 23 | LogFile.setAnnotation(MappingUtil.decodeProguard(cr.className)) 24 | 25 | val cv = AnnotationClassVisitor(cw, cr.className, shrinkClasses, shrinkPackage) 26 | cr.accept(cv, 0) 27 | if (cv.canRemoved) null else cw.toByteArray() 28 | } else { 29 | val cv = ClzClassVisitor(cw, cr.className, shrinkClasses) 30 | cr.accept(cv, 0) 31 | cw.toByteArray() 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/core/ClzClassVisitor.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc.core 2 | 3 | import com.joker.buildsrc.util.LogFile 4 | import com.joker.buildsrc.util.MappingUtil 5 | import org.objectweb.asm.AnnotationVisitor 6 | import org.objectweb.asm.ClassVisitor 7 | import org.objectweb.asm.ClassWriter 8 | import org.objectweb.asm.FieldVisitor 9 | import org.objectweb.asm.MethodVisitor 10 | import org.objectweb.asm.Opcodes 11 | import java.lang.annotation.ElementType 12 | import java.lang.annotation.ElementType.ANNOTATION_TYPE 13 | import java.lang.annotation.ElementType.LOCAL_VARIABLE 14 | import java.lang.annotation.ElementType.PARAMETER 15 | import java.util.Collections 16 | 17 | class ClzClassVisitor(classWriter: ClassWriter, 18 | private val clzName: String, 19 | private val shrinkClass: MutableSet) : ClassVisitor(Opcodes.ASM4, classWriter) { 20 | 21 | override fun visitMethod(access: Int, name: String, desc: String, signature: String?, 22 | exceptions: Array?): MethodVisitor { 23 | return object : MethodVisitor(Opcodes.ASM4, 24 | super.visitMethod(access, name, desc, signature, exceptions)) { 25 | 26 | // ElementType.METHOD / ElementType.CONSTRUCTOR 27 | override fun visitAnnotation(ann: String, visible: Boolean): AnnotationVisitor? { 28 | if (needRemove(desc2ClassPath(ann))) { 29 | LogFile.setAnnotated( 30 | MappingUtil.decodeProguard(desc2ClassPath(ann)), 31 | listOf(ElementType.METHOD, ElementType.CONSTRUCTOR), 32 | "${decodeProguard(clzName)}#${decodeProguardMethod(clzName, name)}" 33 | ) 34 | return null 35 | } 36 | return super.visitAnnotation(ann, visible) 37 | } 38 | 39 | // ElementType.PARAMETER 40 | override fun visitParameterAnnotation(parameter: Int, ann: String, 41 | visible: Boolean): AnnotationVisitor? { 42 | if (needRemove(desc2ClassPath(ann))) { 43 | LogFile.setAnnotated( 44 | decodeProguard(desc2ClassPath(ann)), 45 | Collections.singletonList(PARAMETER), 46 | "${decodeProguard(clzName)}#${decodeProguardMethod(clzName, name)}#$parameter" 47 | ) 48 | return null 49 | } 50 | return super.visitParameterAnnotation(parameter, ann, visible) 51 | } 52 | } 53 | } 54 | 55 | // ElementType.FIELD / LOCAL_VARIABLE 56 | override fun visitField(access: Int, name: String?, desc: String?, signature: String?, 57 | value: Any?): FieldVisitor { 58 | return object : FieldVisitor(Opcodes.ASM4, 59 | super.visitField(access, name, desc, signature, value)) { 60 | override fun visitAnnotation(ann: String, visible: Boolean): AnnotationVisitor? { 61 | if (needRemove(desc2ClassPath(ann))) { 62 | LogFile.setAnnotated( 63 | decodeProguard(desc2ClassPath(ann)), 64 | listOf(ElementType.FIELD, LOCAL_VARIABLE), 65 | "${decodeProguard(clzName)}#${decodeProguardField(decodeProguard(clzName), name!!)}" 66 | ) 67 | return null 68 | } 69 | return super.visitAnnotation(ann, visible) 70 | } 71 | } 72 | } 73 | 74 | override fun visitAnnotation(ann: String, visible: Boolean): AnnotationVisitor? { 75 | // ElementType.TYPE / ElementType.ANNOTATION_TYPE 76 | if (needRemove(desc2ClassPath(ann))) { 77 | LogFile.setAnnotated( 78 | decodeProguard(desc2ClassPath(ann)), 79 | listOf(ANNOTATION_TYPE), 80 | decodeProguard(clzName) 81 | ) 82 | return null 83 | } 84 | return super.visitAnnotation(ann, visible) 85 | } 86 | 87 | private fun desc2ClassPath(desc: String): String { 88 | when { 89 | desc[0] == 'L' && desc[desc.length - 1] == ';' -> return desc.substring(1, desc.length - 1) 90 | else -> throw IllegalStateException("desc $desc is not a descriptor") 91 | } 92 | } 93 | 94 | private fun needRemove(className: String): Boolean { 95 | val newName = decodeProguard(className) 96 | return shrinkClass.contains(newName) 97 | } 98 | 99 | private fun decodeProguard(className: String) = MappingUtil.decodeProguard(className) 100 | 101 | private fun decodeProguardField(className: String, filedName: String) = MappingUtil.decodeProguardField(className, filedName!!) 102 | 103 | private fun decodeProguardMethod(className: String, methodName: String) = MappingUtil.decodeProguardMethod(className, methodName!!) 104 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/extension/ShrinkPkgConfig.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc.extension 2 | 3 | data class ShrinkPkgConfig(val pkgName: String, val ignoreRuntime: Boolean = false) -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/extension/ThinAnnotationExtension.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc.extension 2 | 3 | import groovy.lang.Closure 4 | 5 | open class ThinAnnotationExtension { 6 | 7 | companion object { 8 | const val NAME = "thinAnnotation" 9 | } 10 | 11 | var enable = true 12 | val shrinkClass = mutableSetOf() 13 | val shrinkPackage = mutableSetOf( 14 | ShrinkPkgConfig("butterknife/", true), 15 | ShrinkPkgConfig("android/support/annotation/", false), 16 | ShrinkPkgConfig("androidx/annotation/", false), 17 | ShrinkPkgConfig("org/intellij/lang/annotations/", false), 18 | ShrinkPkgConfig("org/jetbrains/annotations/", false) 19 | ) 20 | 21 | fun enable(enable: Boolean) { 22 | this.enable = enable 23 | } 24 | 25 | fun shrinkClass(shrinkClassName: String) { 26 | shrinkClass.add(shrinkClassName) 27 | } 28 | 29 | fun shrinkPackage(shrinkPackageName: String) { 30 | shrinkPackage(shrinkPackageName, null) 31 | } 32 | 33 | fun shrinkPackage(shrinkPackageName: String, closure: Closure?) { 34 | shrinkPackage.add(ShrinkPkgConfig(shrinkPackageName, closure?.call() ?: false)) 35 | } 36 | 37 | override fun toString(): String { 38 | return "ThinAnnotationExtension{" + "enable=" + 39 | enable + 40 | ", shrinkClass=" + 41 | shrinkClass + 42 | ", shrinkPackage=" + 43 | shrinkPackage + 44 | '}' 45 | } 46 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/util/FileUtil.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc.util 2 | 3 | import java.io.ByteArrayOutputStream 4 | import java.io.File 5 | import java.io.IOException 6 | import java.io.InputStream 7 | 8 | object FileUtil { 9 | @Throws(IOException::class) 10 | internal fun toByteArray(input: InputStream): ByteArray { 11 | val output = ByteArrayOutputStream() 12 | val buffer = ByteArray(8024) 13 | var n = 0 14 | var count: Long = 0 15 | while (-1 != n) { 16 | output.write(buffer, 0, n) 17 | count += n.toLong() 18 | n = input.read(buffer) 19 | } 20 | return output.toByteArray() 21 | } 22 | 23 | internal fun renameFile(originFile: File, targetFile: File) { 24 | if (targetFile.exists()) { 25 | targetFile.delete() 26 | } 27 | targetFile.parentFile.mkdirs() 28 | if (!originFile.renameTo(targetFile)) { 29 | throw RuntimeException("\${originFile} rename to \${targetFile} failed ") 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/util/LogFile.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc.util 2 | 3 | import com.android.build.gradle.internal.scope.VariantScope 4 | import com.android.builder.model.AndroidProject 5 | import com.google.common.base.Joiner 6 | import java.io.File 7 | import java.lang.annotation.ElementType 8 | 9 | object LogFile { 10 | 11 | private val map = mutableMapOf, MutableList>>() 12 | private lateinit var logFile: File 13 | 14 | fun init(variantScope: VariantScope) { 15 | val output = File( 16 | Joiner.on(File.separatorChar).join(variantScope.globalScope.buildDir.toString(), 17 | AndroidProject.FD_OUTPUTS, 18 | "thinAnnotation", 19 | variantScope.variantConfiguration.dirName)) 20 | logFile = File(output, "thinAnnotation.txt") 21 | if (logFile.exists()) { 22 | logFile.delete() 23 | } 24 | output.mkdirs() 25 | logFile.createNewFile() 26 | } 27 | 28 | fun setAnnotation(annotationName: String) { 29 | map[annotationName] = mutableMapOf, MutableList>() 30 | } 31 | 32 | fun setAnnotated(annotationName: String, type: List, message: String) { 33 | if (!map.containsKey(annotationName)) { 34 | throw RuntimeException("something error") 35 | } 36 | val typeMap = map[annotationName] 37 | if (!typeMap!!.containsKey(type)) { 38 | typeMap[type] = mutableListOf() 39 | } 40 | val messages = typeMap[type] 41 | messages!!.add(message) 42 | } 43 | 44 | fun print() { 45 | for (entry in map) { 46 | logFile.appendText("${entry.key}: ") 47 | for (mutableEntry in entry.value) { 48 | logFile.appendText("\n ${mutableEntry.key}: ") 49 | for (message in mutableEntry.value) { 50 | logFile.appendText("\n $message") 51 | } 52 | } 53 | logFile.appendText("\n") 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/joker/buildsrc/util/MappingUtil.kt: -------------------------------------------------------------------------------- 1 | package com.joker.buildsrc.util 2 | 3 | import proguard.obfuscate.MappingProcessor 4 | import proguard.obfuscate.MappingReader 5 | import java.io.File 6 | 7 | object MappingUtil { 8 | private val classMapping = mutableMapOf() 9 | private val fieldMapping = mutableMapOf() 10 | private val methodMapping = mutableMapOf() 11 | 12 | /** 13 | * return class name before proguard 14 | * @param name: the class name after proguard , like android.arch.core.a.a 15 | * @return return origin class name according mapping.txt, return {@param name} if null 16 | */ 17 | fun decodeProguard(name: String): String = classMapping.getOrDefault(name, "") 18 | 19 | fun decodeProguardField(className: String, fieldName: String) = fieldMapping.getOrDefault( 20 | "$className|$fieldName", "") 21 | 22 | fun decodeProguardMethod(className: String, methodName: String) = methodMapping.getOrDefault( 23 | "${decodeProguard(className)}|$methodName", "") 24 | 25 | fun init(file: File) { 26 | val reader = MappingReader(file) 27 | reader.pump(object : MappingProcessor { 28 | override fun processClassMapping(className: String?, 29 | newClassName: String?): Boolean { 30 | classMapping[newClassName!!.replace('.', '/')] = className!!.replace('.', '/') 31 | return true 32 | } 33 | 34 | // BUG: oldClassName same with newClassName, both oldClassName 35 | override fun processFieldMapping(oldClassName: String?, filedType: String?, 36 | fieldName: String?, 37 | newClassName: String?, 38 | newFieldName: String?) { 39 | fieldMapping["${newClassName!!.replace('.', '/')}|$newFieldName"] = fieldName!! 40 | } 41 | 42 | // BUG: oldClassName same with newClassName, both newClassName 43 | override fun processMethodMapping(oldClassName: String?, firstLineNumber: Int, 44 | lastLineNumber: Int, methodReturnType: String?, oldMethodName: String?, 45 | methodArguments: String?, newClassName: String?, newFirstLineNumber: Int, 46 | newLastLineNumber: Int, newMethodName: String?) { 47 | methodMapping["${newClassName!!.replace('.', '/')}|$newMethodName"] = oldMethodName!! 48 | } 49 | }) 50 | } 51 | } -------------------------------------------------------------------------------- /buildSrc/src/main/resources/META-INF/gradle-plugins/thinAnnotation.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.joker.buildsrc.ThinAnnotationPlugin -------------------------------------------------------------------------------- /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=-Xmx1536m 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 | android.enableD8=false 15 | 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jokermonn/thinAnnotation/eeffd8e178dad80221644430eeec79c0e73d2040/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 29 10:19:23 CST 2019 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-4.10.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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':buildSrc' 3 | --------------------------------------------------------------------------------