├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.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 │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── yutiantina │ │ │ │ └── transformdemo │ │ │ │ ├── MainActivity.kt │ │ │ │ └── ModuleRegister.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── yutiantina │ │ │ └── transformdemo │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── yutiantina │ │ └── transformdemo │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── modulea ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── yutiantina │ │ └── modulea │ │ └── ModuleA.java ├── proguard-rules.pro └── build.gradle ├── moduleb ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── yutiantina │ │ │ └── moduleb │ │ │ └── ModuleB.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── yutiantina │ │ │ └── moduleb │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── yutiantina │ │ └── moduleb │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── basemodule ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── yutiantina │ │ │ └── basemodule │ │ │ ├── IModule.java │ │ │ └── RouteModule.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── yutiantina │ │ │ └── basemodule │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── yutiantina │ │ └── basemodule │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /modulea/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /moduleb/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /basemodule/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app',':buildSrc', ':modulea', ':moduleb', ':basemodule' 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TransformDemo 3 | 4 | -------------------------------------------------------------------------------- /modulea/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ModuleA 3 | 4 | -------------------------------------------------------------------------------- /moduleb/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ModuleB 3 | 4 | -------------------------------------------------------------------------------- /basemodule/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BaseModule 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /modulea/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /moduleb/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuTianTina/TransformDemo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /basemodule/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | gradle 10 | gradlew 11 | gradlew.bat 12 | app/libs/ 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 24 16:19:14 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 | -------------------------------------------------------------------------------- /basemodule/src/main/java/com/yutiantina/basemodule/IModule.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.basemodule; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author yutiantian email: yutiantina@gmail.com 7 | * @since 2019/4/11 8 | */ 9 | public interface IModule { 10 | public List getRoute(); 11 | } 12 | -------------------------------------------------------------------------------- /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/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/yutiantina/transformdemo/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.yutiantina.transformdemo 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /basemodule/src/main/java/com/yutiantina/basemodule/RouteModule.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.basemodule; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author yutiantian email: yutiantina@gmail.com 10 | * @since 2019/4/11 11 | */ 12 | @Target({ElementType.TYPE}) 13 | @Retention(RetentionPolicy.CLASS) 14 | public @interface RouteModule { 15 | } 16 | -------------------------------------------------------------------------------- /moduleb/src/test/java/com/yutiantina/moduleb/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.moduleb; 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 | } -------------------------------------------------------------------------------- /basemodule/src/test/java/com/yutiantina/basemodule/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.basemodule; 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 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yutiantina/transformdemo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yutiantina.transformdemo 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import kotlinx.android.synthetic.main.activity_main.* 6 | 7 | class MainActivity : AppCompatActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_main) 12 | 13 | tv_routes.text = ModuleRegister.getAllRoutes().joinToString { "\n" } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /modulea/src/main/java/com/yutiantina/modulea/ModuleA.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.modulea; 2 | 3 | import com.yutiantina.basemodule.IModule; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @author yutiantian email: yutiantina@gmail.com 10 | * @since 2019/4/11 11 | */ 12 | public class ModuleA implements IModule { 13 | 14 | @Override 15 | public List getRoute() { 16 | List routes = new ArrayList<>(); 17 | routes.add("moduleA-page1"); 18 | routes.add("moduleA-page2"); 19 | return routes; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /moduleb/src/main/java/com/yutiantina/moduleb/ModuleB.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.moduleb; 2 | 3 | import com.yutiantina.basemodule.IModule; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @author yutiantian email: yutiantina@gmail.com 10 | * @since 2019/4/11 11 | */ 12 | public class ModuleB implements IModule { 13 | 14 | @Override 15 | public List getRoute() { 16 | List routes = new ArrayList<>(); 17 | routes.add("moduleB-page1"); 18 | routes.add("moduleB-page2"); 19 | return routes; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/yutiantina/transformdemo/ModuleRegister.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.transformdemo; 2 | 3 | import com.yutiantina.modulea.ModuleA; 4 | import com.yutiantina.moduleb.ModuleB; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * @author yutiantian email: yutiantina@gmail.com 11 | * @since 2019/4/11 12 | */ 13 | public class ModuleRegister { 14 | public static List getAllRoutes(){ 15 | List list = new ArrayList<>(); 16 | list.addAll(new ModuleA().getRoute()); 17 | list.addAll(new ModuleB().getRoute()); 18 | return list; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yutiantina/transformdemo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.yutiantina.transformdemo 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.yutiantina.transformdemo", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /modulea/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 | -------------------------------------------------------------------------------- /moduleb/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 | -------------------------------------------------------------------------------- /basemodule/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 | -------------------------------------------------------------------------------- /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 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | -------------------------------------------------------------------------------- /moduleb/src/androidTest/java/com/yutiantina/moduleb/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.moduleb; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.yutiantina.moduleb.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /basemodule/src/androidTest/java/com/yutiantina/basemodule/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.yutiantina.basemodule; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.yutiantina.basemodule.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | -------------------------------------------------------------------------------- /basemodule/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'com.android.support:appcompat-v7:28.0.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | } 35 | -------------------------------------------------------------------------------- /modulea/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'com.android.support:appcompat-v7:28.0.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | 35 | implementation project(':basemodule') 36 | } 37 | -------------------------------------------------------------------------------- /moduleb/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'com.android.support:appcompat-v7:28.0.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | 35 | implementation project(':basemodule') 36 | } 37 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | apply plugin: 'com.transform.demo.DemoPlugin' 7 | 8 | android { 9 | compileSdkVersion 28 10 | defaultConfig { 11 | applicationId "com.yutiantina.transformdemo" 12 | minSdkVersion 14 13 | targetSdkVersion 28 14 | versionCode 1 15 | versionName "1.0" 16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 29 | implementation 'com.android.support:appcompat-v7:28.0.0' 30 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | 35 | implementation project(':basemodule') 36 | implementation project(':modulea') 37 | implementation project(':moduleb') 38 | } 39 | -------------------------------------------------------------------------------- /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 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 其实`Transform API`在一个android工程的打包流程中作用非常大, 像是我们熟知的混淆处理, 类文件转dex文件的处理, 都是通过`Transform API`去完成的. 3 | 本篇内容主要围绕`Transform`做展开: 4 | 1. `Transform API`的使用及原理 5 | 2. 字节码处理框架`ASM`使用技巧 6 | 3. `Transform API`在应用工程上的使用摸索 7 | 8 | ## Transform的使用及原理 9 | ### 什么是Transform 10 | 自从`1.5.0-beta1`版本开始, android gradle插件就包含了一个`Transform API`, 它允许第三方插件在编译后的类文件转换为dex文件之前做处理操作. 11 | 而使用`Transform API`, 我们完全可以不用去关注相关task的生成与执行流程, 它让我们可以只聚焦在如何对输入的类文件进行处理 12 | ### Transform的使用 13 | Transform的注册和使用非常易懂, 在我们自定义的plugin内, 我们可以通过`android.registerTransform(theTransform) `或者` android.registerTransform(theTransform, dependencies).`就可以进行注册. 14 | ``` kotlin 15 | class DemoPlugin: Plugin { 16 | override fun apply(target: Project) { 17 | val android = target.extensions.findByType(BaseExtension::class.java) 18 | android?.registerTransform(DemoTransform()) 19 | } 20 | } 21 | ``` 22 | 而我们自定义的`Transform`继承于`com.android.build.api.transform.Transform`, 具体我们可以看[javaDoc](http://google.github.io/android-gradle-dsl/javadoc/1.5/com/android/build/api/transform/Transform.html#getSecondaryFileOutputs--), 以下代码是比较常见的transform处理模板 23 | ``` kotlin 24 | class DemoTransform: Transform() { 25 | /** 26 | * transform 名字 27 | */ 28 | override fun getName(): String = "DemoTransform" 29 | 30 | /** 31 | * 输入文件的类型 32 | * 可供我们去处理的有两种类型, 分别是编译后的java代码, 以及资源文件(非res下文件, 而是assests内的资源) 33 | */ 34 | override fun getInputTypes(): MutableSet = TransformManager.CONTENT_CLASS 35 | 36 | /** 37 | * 是否支持增量 38 | * 如果支持增量执行, 则变化输入内容可能包含 修改/删除/添加 文件的列表 39 | */ 40 | override fun isIncremental(): Boolean = false 41 | 42 | /** 43 | * 指定作用范围 44 | */ 45 | override fun getScopes(): MutableSet = TransformManager.SCOPE_FULL_PROJECT 46 | 47 | /** 48 | * transform的执行主函数 49 | */ 50 | override fun transform(transformInvocation: TransformInvocation?) { 51 | transformInvocation?.inputs?.forEach { 52 | // 输入源为文件夹类型 53 | it.directoryInputs.forEach {directoryInput-> 54 | with(directoryInput){ 55 | // TODO 针对文件夹进行字节码操作 56 | val dest = transformInvocation.outputProvider.getContentLocation( 57 | name, 58 | contentTypes, 59 | scopes, 60 | Format.DIRECTORY 61 | ) 62 | file.copyTo(dest) 63 | } 64 | } 65 | 66 | // 输入源为jar包类型 67 | it.jarInputs.forEach { jarInput-> 68 | with(jarInput){ 69 | // TODO 针对Jar文件进行相关处理 70 | val dest = transformInvocation.outputProvider.getContentLocation( 71 | name, 72 | contentTypes, 73 | scopes, 74 | Format.JAR 75 | ) 76 | file.copyTo(dest) 77 | } 78 | } 79 | } 80 | } 81 | } 82 | ``` 83 | 每一个`Transform`都声明它的作用域, 作用对象以及具体的操作以及操作后输出的内容. 84 | #### 作用域 85 | 通过`Transform#getScopes`方法我们可以声明自定义的transform的作用域, 指定作用域包括如下几种 86 | 87 | QualifiedContent.Scope|| 88 | ---|---| 89 | EXTERNAL_LIBRARIES|只包含外部库 90 | PROJECT|只作用于project本身内容 91 | PROVIDED_ONLY|支持compileOnly的远程依赖 92 | SUB_PROJECTS|子模块内容 93 | TESTED_CODE|当前变体测试的代码以及包括测试的依赖项 94 | 95 | #### 作用对象 96 | 通过`Transform#getInputTypes`我们可以声明其的作用对象, 我们可以指定的作用对象只包括两种 97 | 98 | QualifiedContent.ContentType|| 99 | ---|---| 100 | CLASSES|Java代码编译后的内容, 包括文件夹以及Jar包内的编译后的类文件 101 | RESOURCES|基于资源获取到的内容 102 | 103 | `TransformManager`整合了部分常用的Scope以及Content集合, 104 | 如果是`application`注册的transform, 通常情况下, 我们一般指定`TransformManager.SCOPE_FULL_PROJECT`;如果是`library`注册的transform, 我们只能指定`TransformManager.PROJECT_ONLY` , 我们可以在`LibraryTaskManager#createTasksForVariantScope`中看到相关的限制报错代码 105 | ``` java 106 | Sets.SetView difference = 107 | Sets.difference(transform.getScopes(), TransformManager.PROJECT_ONLY); 108 | if (!difference.isEmpty()) { 109 | String scopes = difference.toString(); 110 | globalScope 111 | .getAndroidBuilder() 112 | .getIssueReporter() 113 | .reportError( 114 | Type.GENERIC, 115 | new EvalIssueException( 116 | String.format( 117 | "Transforms with scopes '%s' cannot be applied to library projects.", 118 | scopes))); 119 | } 120 | ``` 121 | 而作用对象我们主要常用到的是`TransformManager.CONTENT_CLASS` 122 | #### TransformInvocation 123 | 我们通过实现`Transform#transform`方法来处理我们的中间转换过程, 而中间相关信息都是通过`TransformInvocation`对象来传递 124 | ``` java 125 | public interface TransformInvocation { 126 | 127 | /** 128 | * transform的上下文 129 | */ 130 | @NonNull 131 | Context getContext(); 132 | 133 | /** 134 | * 返回transform的输入源 135 | */ 136 | @NonNull 137 | Collection getInputs(); 138 | 139 | /** 140 | * 返回引用型输入源 141 | */ 142 | @NonNull Collection getReferencedInputs(); 143 | /** 144 | * 额外输入源 145 | */ 146 | @NonNull Collection getSecondaryInputs(); 147 | 148 | /** 149 | * 输出源 150 | */ 151 | @Nullable 152 | TransformOutputProvider getOutputProvider(); 153 | 154 | 155 | /** 156 | * 是否增量 157 | */ 158 | boolean isIncremental(); 159 | } 160 | ``` 161 | 关于输入源, 我们可以大致分为消费型和引用型和额外的输入源 162 | 1. `消费型`就是我们需要进行transform操作的, 这类对象在处理后我们必须指定输出传给下一级, 163 | 我们主要通过`getInputs()`获取进行消费的输入源, 而在进行变换后, 我们也必须通过设置`getInputTypes()`和`getScopes()`来指定输出源传输给下个transform. 164 | 2. 引用型输入源是指我们不进行transform操作, 但可能存在查看时候使用, 所以这类我们也不需要输出给下一级, 在通过覆写`getReferencedScopes()`指定我们的引用型输入源的作用域后, 我们可以通过`TransformInvocation#getReferencedInputs()`获取引用型输入源 165 | 3. 另外我们还可以额外定义另外的输入源供下一级使用, 正常开发中我们很少用到, 不过像是`ProGuardTransform`中, 就会指定创建mapping.txt传给下一级; 同样像是`DexMergerTransform`, 如果打开了`multiDex`功能, 则会将maindexlist.txt文件传给下一级 166 | 167 | ### Transform的原理 168 | #### Transform的执行链 169 | 我们已经大致了解它是如何使用的, 现在看下他的原理(本篇源码基于gradle插件`3.3.2`版本)在去年[AppPlugin源码解析](https://yutiantina.github.io/2018/07/06/AppPlugin%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/)中, 我们粗略了解了android的`com.android.application`以及`com.android.library`两个插件都继承于`BasePlugin`, 而他们的主要执行顺序可以分为三个步骤 170 | 1. project的配置 171 | 2. extension的配置 172 | 3. task的创建 173 | 174 | 在`BaseExtension`内部维护了一个`transforms`集合对象, 175 | `android.registerTransform(theTransform) `实际上就是将我们自定义的transform实例新增到这个列表对象中. 176 | 在`3.3.2`的源码中, 也可以这样理解. 在`BasePlugin#createAndroidTasks`中, 我们通过`VariantManager#createAndroidTasks`创建各个变体的相关编译任务, 最终通过`TaskManager#createTasksForVariantScope`(`application`插件最终实现方法在`TaskManager#createPostCompilationTasks`中, 而`library`插件最终实现方法在`LibraryTaskManager#createTasksForVariantScope`中)方法中获取`BaseExtension`中维护的`transforms`对象, 通过`TransformManager#addTransform`将对应的transform对象转换为task, 注册在`TaskFactory`中.这里关于一系列`Transform Task`的执行流程, 我们可以选择看下`application`内的相关transform流程, 由于篇幅原因, 可以自行去看相关源码, 这里的transform task流程分别是从Desugar->MergeJavaRes->自定义的transform->MergeClasses->Shrinker(包括ResourcesShrinker和DexSplitter和Proguard)->MultiDex->BundleMultiDex->Dex->ResourcesShrinker->DexSplitter, 由此调用链, 我们也可以看出在处理类文件的时候, 是不需要去考虑混淆的处理的. 177 | #### TransformManager 178 | `TransformManager`管理了项目对应变体的所有`Transform`对象, 它的内部维护了一个`TransformStream`集合对象`streams`, 每当新增一个transform, 对应的transform会消费掉对应的流, 而后将处理后的流添加会`streams`内 179 | ``` java 180 | public class TransformManager extends FilterableStreamCollection{ 181 | private final List streams = Lists.newArrayList(); 182 | } 183 | ``` 184 | 我们可以看下它的核心方法`addTransform` 185 | ``` java 186 | @NonNull 187 | public Optional> addTransform( 188 | @NonNull TaskFactory taskFactory, 189 | @NonNull TransformVariantScope scope, 190 | @NonNull T transform, 191 | @Nullable PreConfigAction preConfigAction, 192 | @Nullable TaskConfigAction configAction, 193 | @Nullable TaskProviderCallback providerCallback) { 194 | 195 | ... 196 | 197 | List inputStreams = Lists.newArrayList(); 198 | // transform task的命名规则定义 199 | String taskName = scope.getTaskName(getTaskNamePrefix(transform)); 200 | 201 | // 获取引用型流 202 | List referencedStreams = grabReferencedStreams(transform); 203 | 204 | // 找到输入流, 并计算通过transform的输出流 205 | IntermediateStream outputStream = findTransformStreams( 206 | transform, 207 | scope, 208 | inputStreams, 209 | taskName, 210 | scope.getGlobalScope().getBuildDir()); 211 | 212 | // 省略代码是用来校验输入流和引用流是否为空, 理论上不可能为空, 如果为空, 则说明中间有个transform的转换处理有问题 213 | ... 214 | 215 | transforms.add(transform); 216 | 217 | // transform task的创建 218 | return Optional.of( 219 | taskFactory.register( 220 | new TransformTask.CreationAction<>( 221 | scope.getFullVariantName(), 222 | taskName, 223 | transform, 224 | inputStreams, 225 | referencedStreams, 226 | outputStream, 227 | recorder), 228 | preConfigAction, 229 | configAction, 230 | providerCallback)); 231 | } 232 | ``` 233 | 234 | 在`TransformManager`中添加一个`Transform`管理, 流程可分为以下几步 235 | 1. 定义transform task名 236 | ``` java 237 | static String getTaskNamePrefix(@NonNull Transform transform) { 238 | StringBuilder sb = new StringBuilder(100); 239 | sb.append("transform"); 240 | 241 | sb.append( 242 | transform 243 | .getInputTypes() 244 | .stream() 245 | .map( 246 | inputType -> 247 | CaseFormat.UPPER_UNDERSCORE.to( 248 | CaseFormat.UPPER_CAMEL, inputType.name())) 249 | .sorted() // Keep the order stable. 250 | .collect(Collectors.joining("And"))); 251 | sb.append("With"); 252 | StringHelper.appendCapitalized(sb, transform.getName()); 253 | sb.append("For"); 254 | 255 | return sb.toString(); 256 | } 257 | ``` 258 | 从上面代码, 我们可以看到新建的transform task的命名规则可以理解为`transform${inputType1.name}And${inputType2.name}With${transform.name}For${variantName}`, 对应的我们也可以通过已生成的transform task来验证 259 | ![](https://user-gold-cdn.xitu.io/2019/4/24/16a4df23254b0a4f?w=818&h=650&f=png&s=148230) 260 | 2. 通过transform内部定义的引用型输入的作用域(SCOPE)和作用类型(InputTypes), 通过求取与`streams`作用域和作用类型的交集来获取对应的流, 将其定义为我们需要的引用型流 261 | ``` java 262 | private List grabReferencedStreams(@NonNull Transform transform) { 263 | Set requestedScopes = transform.getReferencedScopes(); 264 | ... 265 | 266 | List streamMatches = Lists.newArrayListWithExpectedSize(streams.size()); 267 | 268 | Set requestedTypes = transform.getInputTypes(); 269 | for (TransformStream stream : streams) { 270 | Set availableTypes = stream.getContentTypes(); 271 | Set availableScopes = stream.getScopes(); 272 | 273 | Set commonTypes = Sets.intersection(requestedTypes, 274 | availableTypes); 275 | Set commonScopes = Sets.intersection(requestedScopes, availableScopes); 276 | 277 | if (!commonTypes.isEmpty() && !commonScopes.isEmpty()) { 278 | streamMatches.add(stream); 279 | } 280 | } 281 | 282 | return streamMatches; 283 | } 284 | ``` 285 | 3. 根据transform内定义的SCOPE和INPUT_TYPE, 获取对应的消费型输入流, 在streams内移除掉这一部分消费性的输入流, 保留无法匹配SCOPE和INPUT_TYPE的流; 构建新的输出流, 并加到streams中做管理 286 | ``` java 287 | private IntermediateStream findTransformStreams( 288 | @NonNull Transform transform, 289 | @NonNull TransformVariantScope scope, 290 | @NonNull List inputStreams, 291 | @NonNull String taskName, 292 | @NonNull File buildDir) { 293 | 294 | Set requestedScopes = transform.getScopes(); 295 | ... 296 | 297 | Set requestedTypes = transform.getInputTypes(); 298 | // 获取消费型输入流 299 | // 并将streams中移除对应的消费型输入流 300 | consumeStreams(requestedScopes, requestedTypes, inputStreams); 301 | 302 | // 创建输出流 303 | Set outputTypes = transform.getOutputTypes(); 304 | // 创建输出流转换的文件相关路径 305 | File outRootFolder = 306 | FileUtils.join( 307 | buildDir, 308 | StringHelper.toStrings( 309 | AndroidProject.FD_INTERMEDIATES, 310 | FD_TRANSFORMS, 311 | transform.getName(), 312 | scope.getDirectorySegments())); 313 | 314 | // 输出流的创建 315 | IntermediateStream outputStream = 316 | IntermediateStream.builder( 317 | project, 318 | transform.getName() + "-" + scope.getFullVariantName(), 319 | taskName) 320 | .addContentTypes(outputTypes) 321 | .addScopes(requestedScopes) 322 | .setRootLocation(outRootFolder) 323 | .build(); 324 | streams.add(outputStream); 325 | 326 | return outputStream; 327 | } 328 | ``` 329 | 4. 最后, 创建TransformTask, 注册到TaskManager中 330 | #### TransformTask 331 | 如何触发到我们实现的`Transform#transform`方法, 就在`TransformTask`对应的TaskAction中执行 332 | ``` java 333 | void transform(final IncrementalTaskInputs incrementalTaskInputs) 334 | throws IOException, TransformException, InterruptedException { 335 | 336 | final ReferenceHolder> consumedInputs = ReferenceHolder.empty(); 337 | final ReferenceHolder> referencedInputs = ReferenceHolder.empty(); 338 | final ReferenceHolder isIncremental = ReferenceHolder.empty(); 339 | final ReferenceHolder> changedSecondaryInputs = 340 | ReferenceHolder.empty(); 341 | 342 | isIncremental.setValue(transform.isIncremental() && incrementalTaskInputs.isIncremental()); 343 | 344 | GradleTransformExecution preExecutionInfo = 345 | GradleTransformExecution.newBuilder() 346 | .setType(AnalyticsUtil.getTransformType(transform.getClass()).getNumber()) 347 | .setIsIncremental(isIncremental.getValue()) 348 | .build(); 349 | 350 | // 一些增量模式下的处理, 包括在增量模式下, 判断输入流(引用型和消费型)的变化 351 | ... 352 | 353 | GradleTransformExecution executionInfo = 354 | preExecutionInfo.toBuilder().setIsIncremental(isIncremental.getValue()).build(); 355 | 356 | ... 357 | transform.transform( 358 | new TransformInvocationBuilder(TransformTask.this) 359 | .addInputs(consumedInputs.getValue()) 360 | .addReferencedInputs(referencedInputs.getValue()) 361 | .addSecondaryInputs(changedSecondaryInputs.getValue()) 362 | .addOutputProvider( 363 | outputStream != null 364 | ? outputStream.asOutput( 365 | isIncremental.getValue()) 366 | : null) 367 | .setIncrementalMode(isIncremental.getValue()) 368 | .build()); 369 | 370 | if (outputStream != null) { 371 | outputStream.save(); 372 | } 373 | } 374 | ``` 375 | 376 | 通过上文的介绍, 我们现在应该知道了自定义的Transform执行的时序, 位置, 以及相关原理. 那么, 我们现在已经拿到了编译后的所有字节码, 我们要怎么去处理呢? 我们可以了解下`ASM` 377 | ## ASM的使用 378 | 想要处理字节码, 常见的框架有AspectJ, Javasist, ASM. 关于框架的选型网上相关的文章还是比较多的, 从处理速度以及内存占用率上, ASM明显优于其他两个框架.本篇主要着眼于ASM的使用. 379 | ### 什么是ASM 380 | `ASM`是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类. `ASM`提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具. 381 | ASM库提供了两个用于生成和转换编译类的API:`Core API`提供基于事件的类表示,而`Tree API`提供基于对象的表示。由于基于事件的API(Core API)不需要在内存中存储一个表示该类的对象数, 所以从执行速度和内存占用上来说, 它比基于对象的API(Tree API)更优.然后从使用场景上来说, 基于事件的API使用会比基于对象的API使用更为困难, 譬如当我们需要针对某个对象进行调整的时候.由于一个类只能被一种API管理, 所以我们应该要区分场景选取使用对应的API 382 | ### ASM插件 383 | ASM的使用需要一定的学习成本, 我们可以通过使用`ASM Bytecode Outline`插件辅助了解, 对应插件在AS中的插件浏览器就可以找到 384 | ![](https://user-gold-cdn.xitu.io/2019/4/24/16a4df2325884e3d?w=1280&h=547&f=png&s=228391) 385 | 唯一的遗憾在于它无法转换kotlin文件为通过ASM创建的类文件 386 | 然后我们就可以通过打开一份java未编译文件, 通过右键选择Show Bytecode Outline转为对应的字节码, 并可以看到对应的通过ASM创建的类格式 387 | ![](https://user-gold-cdn.xitu.io/2019/4/24/16a4df2325ee92d4?w=604&h=668&f=png&s=71538) 388 | 譬如我们新建了一个类, 可以通过asm插件得到通过core api生成的对应方法. 389 | ``` java 390 | @RouteModule 391 | public class ASMTest { 392 | 393 | } 394 | 395 | ``` 396 | ![](https://user-gold-cdn.xitu.io/2019/4/24/16a4df233f4ddac9?w=1280&h=637&f=png&s=267972) 397 | 398 | ## `Transform API`在应用工程方面的摸索使用 399 | ### 组件通信中的作用 400 | `Transform API`在组件化工程中有很多应用方向, 目前我们项目中在自开发的路由框架中, 通过其去做了模块的自动化静态注册, 同时考虑到路由通过协议文档维护的不确定性(页面路由地址的维护不及时导致对应开发无法及时更新对应代码), 我们做了路由的常量管理, 首先通过扫描整个工程项目代码收集路由信息, 建立符合一定规则的路由原始基础信息文件, 通过`variant#registerJavaGeneratingTask`注册 通过对应原始信息文件生成对应常量Java文件下沉在基础通用组件中的task, 这样上层依赖于这个基础组件的项目都可以通过直接调用常量来使用路由.在各组件代码隔离的情况下, 可以通过由组件aar传递原始信息文件, 仍然走上面的步骤生成对应的常量表, 而存在的类重复的问题, 通过自定义`Transform`处理合并 401 | ### 业务监控中的作用 402 | 在应用工程中, 我们通常有关于网络监控,应用性能检测(包括页面加载时间, 甚至包括各个方法调用所耗时间, 可能存在超过阈值需要警告)的需求, 这些需求我们都不可能嵌入在业务代码中, 都是可以基于`Transform API`进行处理. 而针对于埋点, 我们也可以通过`Transform`实现自动化埋点的功能, 通过`ASM Core`和`ASM Tree`将尽可能多的字段信息形成记录传递, 这里有些我们项目中已经实现了, 有一些则是我们需要去优化或者去实现的. 403 | 404 | ## 其他 405 | 关于结合`Transform+ASM`的使用, 我写了个一个小[Demo](https://github.com/YuTianTina/TransformDemo), 包括了如何处理支持增量功能时的转换, 如何使用`ASM Core Api`和`ASM Tree Api`, 做了一定的封装, 可以参阅 406 | 407 | 408 | 409 | 410 | 411 | 412 | ## 相关参考 413 | - [ASM用户指南](https://asm.ow2.io/asm4-guide.pdf) 414 | - [一起玩转Android项目中的字节码](http://quinnchen.me/2018/09/13/2018-09-13-asm-transform/) 415 | - [AOP 的利器:ASM 3.0 介绍](https://www.ibm.com/developerworks/cn/java/j-lo-asm30/) 416 | - [ASM官网](https://asm.ow2.io/) 417 | --------------------------------------------------------------------------------