├── spi-api ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── SPI.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── afirez │ │ └── spi │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── spi-app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── 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 │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── fragment_main.xml │ │ │ │ └── activity_main.xml │ │ │ ├── drawable │ │ │ │ ├── selector_green_round_5dp.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── afirez │ │ │ │ └── spi │ │ │ │ └── app │ │ │ │ ├── PrivateUtil.java │ │ │ │ ├── App.java │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── afirez │ │ └── spi │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── spi-applike ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── applike │ │ │ ├── AppLike.java │ │ │ ├── App.java │ │ │ └── AppDelegate.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── applike │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── afirez │ │ └── applike │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── spi-plugin ├── .gitignore ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── gradle-plugins │ │ │ ├── spi.properties │ │ │ └── com.afirez.spi.properties │ │ └── java │ │ └── com │ │ └── afirez │ │ └── spi │ │ ├── SpiExtension.kt │ │ ├── SpiContext.kt │ │ ├── SpiPlugin.kt │ │ └── weave │ │ ├── SpiWeaveCodeClassVisitor.kt │ │ └── SpiScanClassVisitor.kt └── build.gradle ├── knight-transform ├── .gitignore ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── gradle-plugins │ │ │ └── com.knight.transform.properties │ │ └── kotlin │ │ └── com │ │ └── knight │ │ └── transform │ │ ├── BaseExtension.kt │ │ ├── MemberEntity.kt │ │ ├── Interceptor │ │ ├── IClassVIsitorInterceptor.kt │ │ ├── ClassVisitorChain.kt │ │ └── impl │ │ │ └── BaseClassVisitorInterceptor.kt │ │ ├── Utils │ │ ├── Timer.kt │ │ ├── PrintAllTaskUtil.kt │ │ ├── TypeUtil.kt │ │ └── ASMUtils.kt │ │ ├── task │ │ ├── WeavedClass.kt │ │ └── OutPutMappingTask.kt │ │ ├── IPlugin.kt │ │ ├── asm │ │ ├── IWeaver.kt │ │ ├── ScanWeaver.kt │ │ └── CodeWeaver.kt │ │ ├── weave │ │ ├── BaseClassVisitor.kt │ │ └── ExtendClassWriter.kt │ │ ├── transform │ │ ├── TransformContext.kt │ │ └── Transform.kt │ │ ├── BaseContext.kt │ │ ├── KnightTaskPlugin.kt │ │ ├── KnightTransform.kt │ │ └── KnightPlugin.kt └── build.gradle ├── spi-component1 ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── component1 │ │ │ ├── SpiJavaProvider.java │ │ │ └── SpiRouter1Provider.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── component1 │ │ │ └── api │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── afirez │ │ └── spi │ │ └── component1 │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── spi-component2 ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── component2 │ │ │ ├── SpiKotlinProvider.kt │ │ │ └── SpiRouter2Provider.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── component2 │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── afirez │ │ └── spi │ │ └── component2 │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── spi-plugin-booster ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── com │ │ └── afirez │ │ └── spi │ │ ├── dummy │ │ └── TestPrivateTransformer.kt │ │ ├── SpiSupervisor.kt │ │ └── SpiTransformer.kt └── build.gradle ├── spi-component1-impl ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ └── layout │ │ │ │ ├── activity_spi_fragment.xml │ │ │ │ └── activity_spi.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── component1 │ │ │ └── impl │ │ │ ├── App.kt │ │ │ ├── SpiJavaProviderImpl.java │ │ │ ├── SpiActivity.java │ │ │ ├── SpiRouter1ProviderImpl.java │ │ │ └── SpiFragmentActivity.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── component1 │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── afirez │ │ └── spi │ │ └── component1 │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── spi-component2-impl ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ └── layout │ │ │ │ └── fragment_spi.xml │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── component2 │ │ │ └── impl │ │ │ ├── SpiKotlinProviderImpl.kt │ │ │ ├── App.kt │ │ │ ├── SpiRouter2ProviderImpl.kt │ │ │ └── SpiFragment.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── afirez │ │ │ └── spi │ │ │ └── component2 │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── afirez │ │ └── spi │ │ └── component2 │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── app-afirez.jks ├── repo └── com │ ├── afirez │ ├── spi │ │ ├── spi │ │ │ ├── maven-metadata.xml.md5 │ │ │ ├── 1.0.0 │ │ │ │ ├── spi-1.0.0.aar.md5 │ │ │ │ ├── spi-1.0.0.pom.md5 │ │ │ │ ├── spi-1.0.0.aar.sha1 │ │ │ │ ├── spi-1.0.0.pom.sha1 │ │ │ │ ├── spi-1.0.0.aar │ │ │ │ └── spi-1.0.0.pom │ │ │ ├── 1.0.1 │ │ │ │ ├── spi-1.0.1.aar.md5 │ │ │ │ ├── spi-1.0.1.pom.md5 │ │ │ │ ├── spi-1.0.1-javadoc.jar.md5 │ │ │ │ ├── spi-1.0.1-sources.jar.md5 │ │ │ │ ├── spi-1.0.1.aar.sha1 │ │ │ │ ├── spi-1.0.1.pom.sha1 │ │ │ │ ├── spi-1.0.1-javadoc.jar.sha1 │ │ │ │ ├── spi-1.0.1-sources.jar.sha1 │ │ │ │ ├── spi-1.0.1.aar │ │ │ │ ├── spi-1.0.1-javadoc.jar │ │ │ │ ├── spi-1.0.1-sources.jar │ │ │ │ └── spi-1.0.1.pom │ │ │ ├── maven-metadata.xml.sha1 │ │ │ └── maven-metadata.xml │ │ ├── spi-app-lib1 │ │ │ ├── maven-metadata.xml.md5 │ │ │ ├── 1.0.1 │ │ │ │ ├── spi-app-lib1-1.0.1.aar.md5 │ │ │ │ ├── spi-app-lib1-1.0.1.pom.md5 │ │ │ │ ├── spi-app-lib1-1.0.1.aar.sha1 │ │ │ │ ├── spi-app-lib1-1.0.1.pom.sha1 │ │ │ │ ├── spi-app-lib1-1.0.1.aar │ │ │ │ └── spi-app-lib1-1.0.1.pom │ │ │ ├── maven-metadata.xml.sha1 │ │ │ └── maven-metadata.xml │ │ └── spi-gradle-plugin │ │ │ ├── maven-metadata.xml.md5 │ │ │ ├── maven-metadata.xml.sha1 │ │ │ ├── 1.0.0 │ │ │ ├── spi-gradle-plugin-1.0.0.jar.md5 │ │ │ ├── spi-gradle-plugin-1.0.0.pom.md5 │ │ │ ├── spi-gradle-plugin-1.0.0.jar.sha1 │ │ │ ├── spi-gradle-plugin-1.0.0.pom.sha1 │ │ │ ├── spi-gradle-plugin-1.0.0.jar │ │ │ └── spi-gradle-plugin-1.0.0.pom │ │ │ ├── 1.0.1 │ │ │ ├── spi-gradle-plugin-1.0.1.jar.md5 │ │ │ ├── spi-gradle-plugin-1.0.1.pom.md5 │ │ │ ├── spi-gradle-plugin-1.0.1-javadoc.jar.md5 │ │ │ ├── spi-gradle-plugin-1.0.1-sources.jar.md5 │ │ │ ├── spi-gradle-plugin-1.0.1.jar.sha1 │ │ │ ├── spi-gradle-plugin-1.0.1.pom.sha1 │ │ │ ├── spi-gradle-plugin-1.0.1-javadoc.jar.sha1 │ │ │ ├── spi-gradle-plugin-1.0.1-sources.jar.sha1 │ │ │ ├── spi-gradle-plugin-1.0.1.jar │ │ │ ├── spi-gradle-plugin-1.0.1-javadoc.jar │ │ │ ├── spi-gradle-plugin-1.0.1-sources.jar │ │ │ └── spi-gradle-plugin-1.0.1.pom │ │ │ ├── 2.0.0 │ │ │ ├── spi-gradle-plugin-2.0.0.jar.md5 │ │ │ ├── spi-gradle-plugin-2.0.0.pom.md5 │ │ │ ├── spi-gradle-plugin-2.0.0-javadoc.jar.md5 │ │ │ ├── spi-gradle-plugin-2.0.0-sources.jar.md5 │ │ │ ├── spi-gradle-plugin-2.0.0.jar.sha1 │ │ │ ├── spi-gradle-plugin-2.0.0.pom.sha1 │ │ │ ├── spi-gradle-plugin-2.0.0-javadoc.jar.sha1 │ │ │ ├── spi-gradle-plugin-2.0.0-sources.jar.sha1 │ │ │ ├── spi-gradle-plugin-2.0.0.jar │ │ │ ├── spi-gradle-plugin-2.0.0-javadoc.jar │ │ │ ├── spi-gradle-plugin-2.0.0-sources.jar │ │ │ └── spi-gradle-plugin-2.0.0.pom │ │ │ ├── 3.0.0 │ │ │ ├── spi-gradle-plugin-3.0.0.jar.md5 │ │ │ ├── spi-gradle-plugin-3.0.0.pom.md5 │ │ │ ├── spi-gradle-plugin-3.0.0-javadoc.jar.md5 │ │ │ ├── spi-gradle-plugin-3.0.0-sources.jar.md5 │ │ │ ├── spi-gradle-plugin-3.0.0.jar.sha1 │ │ │ ├── spi-gradle-plugin-3.0.0.pom.sha1 │ │ │ ├── spi-gradle-plugin-3.0.0-javadoc.jar.sha1 │ │ │ ├── spi-gradle-plugin-3.0.0-sources.jar.sha1 │ │ │ ├── spi-gradle-plugin-3.0.0.jar │ │ │ ├── spi-gradle-plugin-3.0.0-javadoc.jar │ │ │ ├── spi-gradle-plugin-3.0.0-sources.jar │ │ │ └── spi-gradle-plugin-3.0.0.pom │ │ │ └── maven-metadata.xml │ └── applike │ │ └── applike │ │ ├── maven-metadata.xml.md5 │ │ ├── 1.0.0 │ │ ├── applike-1.0.0.aar.md5 │ │ ├── applike-1.0.0.pom.md5 │ │ ├── applike-1.0.0.aar.sha1 │ │ ├── applike-1.0.0.pom.sha1 │ │ ├── applike-1.0.0.aar │ │ └── applike-1.0.0.pom │ │ ├── 1.0.1 │ │ ├── applike-1.0.1.aar.md5 │ │ ├── applike-1.0.1.pom.md5 │ │ ├── applike-1.0.1-javadoc.jar.md5 │ │ ├── applike-1.0.1-sources.jar.md5 │ │ ├── applike-1.0.1.aar.sha1 │ │ ├── applike-1.0.1.pom.sha1 │ │ ├── applike-1.0.1-javadoc.jar.sha1 │ │ ├── applike-1.0.1-sources.jar.sha1 │ │ ├── applike-1.0.1.aar │ │ ├── applike-1.0.1-javadoc.jar │ │ ├── applike-1.0.1-sources.jar │ │ └── applike-1.0.1.pom │ │ ├── maven-metadata.xml.sha1 │ │ └── maven-metadata.xml │ └── knight │ └── transform │ ├── maven-metadata.xml.md5 │ ├── 2.0.0 │ ├── transform-2.0.0.jar.md5 │ ├── transform-2.0.0.pom.md5 │ ├── transform-2.0.0-javadoc.jar.md5 │ ├── transform-2.0.0-sources.jar.md5 │ ├── transform-2.0.0.jar.sha1 │ ├── transform-2.0.0.pom.sha1 │ ├── transform-2.0.0-javadoc.jar.sha1 │ ├── transform-2.0.0-sources.jar.sha1 │ ├── transform-2.0.0.jar │ ├── transform-2.0.0-javadoc.jar │ ├── transform-2.0.0-sources.jar │ └── transform-2.0.0.pom │ ├── 1.0.10 │ ├── transform-1.0.10.jar.md5 │ ├── transform-1.0.10.pom.md5 │ ├── transform-1.0.10-javadoc.jar.md5 │ ├── transform-1.0.10-sources.jar.md5 │ ├── transform-1.0.10.jar.sha1 │ ├── transform-1.0.10.pom.sha1 │ ├── transform-1.0.10-javadoc.jar.sha1 │ ├── transform-1.0.10-sources.jar.sha1 │ ├── transform-1.0.10.jar │ ├── transform-1.0.10-javadoc.jar │ ├── transform-1.0.10-sources.jar │ └── transform-1.0.10.pom │ ├── maven-metadata.xml.sha1 │ └── maven-metadata.xml ├── assets ├── 2019-06-01-15-54-28.png ├── 2019-06-01-16-04-00.png ├── 2019-06-01-16-16-26.png ├── 2019-06-01-16-24-31.png ├── 2019-06-01-16-41-42.png ├── 2019-06-01-16-59-26.png ├── 2019-06-01-17-04-39.png └── 2019-06-01-21-16-27.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── pom-evaluator.gradle └── pkg-src.gradle ├── settings.gradle ├── .gitignore ├── .travis.yml ├── gradle.properties ├── gradlew.bat └── README_CN.md /spi-api/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /spi-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /spi-applike/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /spi-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /knight-transform/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /spi-component1/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /spi-component2/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /spi-plugin-booster/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /spi-component1-impl/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /spi-component2-impl/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app-afirez.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/app-afirez.jks -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | e7187f0bd4acf3a4d2cd0ae1c7bbc118 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.0/spi-1.0.0.aar.md5: -------------------------------------------------------------------------------- 1 | 09b1693d0be0c1063d894ccb39925fd3 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.0/spi-1.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | 9a25bbfd1c1ac1d3e2df8a734ac135eb -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1.aar.md5: -------------------------------------------------------------------------------- 1 | e6bee482dac6d5bbbd185f2aae462f1a -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1.pom.md5: -------------------------------------------------------------------------------- 1 | bf3db2f07f9a5f9f9d0e7a8cea2e3d8f -------------------------------------------------------------------------------- /repo/com/knight/transform/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | ab1d120e808f7d993582e70f68d572c0 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | 7b2fc88c7a72a46abccb222625fd9aff -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | 4e6892fa9d79e87034c9a302faa1260d -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.0/spi-1.0.0.aar.sha1: -------------------------------------------------------------------------------- 1 | 20cdbd46633e5cf09b18d2734e1d06b8c08cafed -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.0/spi-1.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | efa646fb92ac86006d50f3e98f1c23c4d3bd4ac1 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | c284fd3b2f2f5b411afb184b60fef5fd -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1-sources.jar.md5: -------------------------------------------------------------------------------- 1 | 18dad9b9fed3bcb734886c857b976348 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1.aar.sha1: -------------------------------------------------------------------------------- 1 | 41613d865522d5c93c022b0cb9e0731c87437e71 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1.pom.sha1: -------------------------------------------------------------------------------- 1 | d7efd845dd257e8930fb4c155daab07f57ef09d8 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | 8bc752c07e085e2f58a2bcd01eab25e068fa0044 -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0.jar.md5: -------------------------------------------------------------------------------- 1 | 2cecdca4ef6992b06900247c027e9224 -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | 1db53efd306abe0ad7f32797179b38a1 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.0/applike-1.0.0.aar.md5: -------------------------------------------------------------------------------- 1 | 0979ad92c011bd4c6a57dcf33eca0ca9 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.0/applike-1.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | bceec30a3683b3db58e2ede3fc0f98a3 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1.aar.md5: -------------------------------------------------------------------------------- 1 | 7a6d17189d6a08697e1bcccda9b4ae5f -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1.pom.md5: -------------------------------------------------------------------------------- 1 | 703622f383bf2349f390999543c64ee7 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | 3cc7369ac318ae7805c8c02419c98465 -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10.jar.md5: -------------------------------------------------------------------------------- 1 | fcd3034a549962749c535eb004774a05 -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10.pom.md5: -------------------------------------------------------------------------------- 1 | 9b84cd6a23d7e23b2040c0263330436f -------------------------------------------------------------------------------- /repo/com/knight/transform/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | c8452399537f1f900eb4c950d29b634a43d3c845 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | f6e57fabeb5856668ee95cd3a651943d6d472acf -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/1.0.1/spi-app-lib1-1.0.1.aar.md5: -------------------------------------------------------------------------------- 1 | f18d99e114a48e8917e599d0a4f50cbc -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/1.0.1/spi-app-lib1-1.0.1.pom.md5: -------------------------------------------------------------------------------- 1 | b7389c2ce9a62c32a7f41c691b86b4c3 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | e63aae948622f4dfeb6c2262018e4fa874d85d90 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | b0ba666565651dbeb9cc4e03e31a9dae91efb3e2 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | 5ea49014d224e46b1ef6d0d53f0f0472aa9898fb -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | 89052a37c7220d50bfa9c78e7a5b96a1 -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10-sources.jar.md5: -------------------------------------------------------------------------------- 1 | 29c769c289c8315c8a79f3c0a88d88d3 -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10.jar.sha1: -------------------------------------------------------------------------------- 1 | f062163e3423768579d7269f4f7530002591f0f4 -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10.pom.sha1: -------------------------------------------------------------------------------- 1 | e57210f42b9ba5e1fc18a70550b7f91c2e9fc84d -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | 110157761f6e5c7ac561779ffeeb7897 -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0-sources.jar.md5: -------------------------------------------------------------------------------- 1 | b8f75bfef94c7520aa9612448249a665 -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0.jar.sha1: -------------------------------------------------------------------------------- 1 | d0c5d6635bf220a423874dc95350dac84eb97047 -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 867ba285aa2d1c6bdcc2fb304eb84ad1e2a5a689 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.0/applike-1.0.0.aar.sha1: -------------------------------------------------------------------------------- 1 | 16d18ca2f6f3fdf0a03e1d1d660928fa10fe7f0a -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.0/applike-1.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 2309513929115f16a3436c5d92b6cdf5b6d17c5e -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | 87324f47e373f73b9b1fa842d1b6ffe2 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1-sources.jar.md5: -------------------------------------------------------------------------------- 1 | 9bede3cccaca47360937a9af3b7ae775 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1.aar.sha1: -------------------------------------------------------------------------------- 1 | a8c4a99d99f891f586d4410fb9c4d4b837bf2408 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1.pom.sha1: -------------------------------------------------------------------------------- 1 | cf070d47e758a8d2733c64c747ee24f706ba6059 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | 65d50594dd33b659186c30eb6af093b21608ddb8 -------------------------------------------------------------------------------- /assets/2019-06-01-15-54-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/assets/2019-06-01-15-54-28.png -------------------------------------------------------------------------------- /assets/2019-06-01-16-04-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/assets/2019-06-01-16-04-00.png -------------------------------------------------------------------------------- /assets/2019-06-01-16-16-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/assets/2019-06-01-16-16-26.png -------------------------------------------------------------------------------- /assets/2019-06-01-16-24-31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/assets/2019-06-01-16-24-31.png -------------------------------------------------------------------------------- /assets/2019-06-01-16-41-42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/assets/2019-06-01-16-41-42.png -------------------------------------------------------------------------------- /assets/2019-06-01-16-59-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/assets/2019-06-01-16-59-26.png -------------------------------------------------------------------------------- /assets/2019-06-01-17-04-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/assets/2019-06-01-17-04-39.png -------------------------------------------------------------------------------- /assets/2019-06-01-21-16-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/assets/2019-06-01-21-16-27.png -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | de34e4a8efe0a9cc12fcd5645342d1c561312fa5 -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | 6560133d7fdf287a7c3e4a3ad1926af791039691 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/1.0.1/spi-app-lib1-1.0.1.aar.sha1: -------------------------------------------------------------------------------- 1 | 86a450523d3ed48b5f5b16379ca83eb2373ca78d -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/1.0.1/spi-app-lib1-1.0.1.pom.sha1: -------------------------------------------------------------------------------- 1 | edf78da0b62de1f5db26ec7771a87148a789586a -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.0/spi-gradle-plugin-1.0.0.jar.md5: -------------------------------------------------------------------------------- 1 | bccaa3c3aa350c3ed74bf51b60ff2896 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.0/spi-gradle-plugin-1.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | 8843683e9613e5a8b453f21d39a91d70 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1.jar.md5: -------------------------------------------------------------------------------- 1 | 3e4be855c15b017301c9d445a1c23756 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1.pom.md5: -------------------------------------------------------------------------------- 1 | 17ac2da10e6db7ffcbfed7a4e09a2532 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0.jar.md5: -------------------------------------------------------------------------------- 1 | fda4deebc85b3ff4c614b5be921df17c -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | 7dace4e7e4b58e53866e97633e3d14ad -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0.jar.md5: -------------------------------------------------------------------------------- 1 | 892a4a59f4a752ce5ac032095a1a2e64 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | a216e76669dab97cf01ebb453930c339 -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | c344c4be0e6a47c9e70426067ec18f7a0da6d85f -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | 5ab10e67f599d2f518de4a7a9f350dd0336c178e -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | 14b60b052f61c0924b465994a75f49547c9e9568 -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | 00d465682568dd266819c6390fe8e00499ac079f -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /spi-plugin/src/main/resources/META-INF/gradle-plugins/spi.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.afirez.spi.SpiPlugin -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.0/spi-gradle-plugin-1.0.0.jar.sha1: -------------------------------------------------------------------------------- 1 | 1a58ec2e006de4f31e7716cc506055bd6ba628bf -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.0/spi-gradle-plugin-1.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 5c0edd636d83d4cc710d2508346f7a05969a7192 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | b26b62361ea076a9b537b530f3ff8c0d -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1-sources.jar.md5: -------------------------------------------------------------------------------- 1 | 1d2aa2c49e89ced82f205e12ba1ad77c -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1.jar.sha1: -------------------------------------------------------------------------------- 1 | 6cf10062267f3f25e0a60e91a69eff0551e1ae09 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1.pom.sha1: -------------------------------------------------------------------------------- 1 | f14f8156213fbb13c058b491002912ce1853c888 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | a955158e6a3a361d6a6f05a1a357237a -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0-sources.jar.md5: -------------------------------------------------------------------------------- 1 | e2959c585afd044b515d8703fa0f7831 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0.jar.sha1: -------------------------------------------------------------------------------- 1 | 07ff40187098d5ffecafb3cb493db9ca4b8f62b7 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 8de94b7bf515ca39e60276ff05afeaec80b3c121 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | 577174b0e1207b53b99f794b351e1548 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0-sources.jar.md5: -------------------------------------------------------------------------------- 1 | 115bdf61edc60eef2b04b95d4b0bb399 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0.jar.sha1: -------------------------------------------------------------------------------- 1 | dbff93984b95b66f28fa121e1fbda78cb6eb6ef3 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 88fc31f095d372f5ca8a786651a85838d344a5e3 -------------------------------------------------------------------------------- /spi-api/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | spi_api 3 | 4 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | f7d30e3af0e27d1319c1f995f629b440c64280e1 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | dff0f173b56d18fa2edddfeb64ed3d36d503b131 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | 5298f0e5f85a782e948d076397f019d3082b7c19 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | 75b34aa783ae72ba85f703d6e80c5131c56c6476 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | 9f0dd9ef95c1d1a54cbdd4685344a85cbd81a0f4 -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | 2c11148bdfa73deb16f894166edbf8c3ffc929ff -------------------------------------------------------------------------------- /spi-plugin/src/main/resources/META-INF/gradle-plugins/com.afirez.spi.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.afirez.spi.SpiPlugin -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.0/spi-1.0.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi/1.0.0/spi-1.0.0.aar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi/1.0.1/spi-1.0.1.aar -------------------------------------------------------------------------------- /spi-component1-impl/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | component1 3 | 4 | -------------------------------------------------------------------------------- /spi-component1/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | component1_api 3 | 4 | -------------------------------------------------------------------------------- /spi-component2/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | component2_api 3 | 4 | -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /spi-applike/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | spi_component_applike 3 | 4 | -------------------------------------------------------------------------------- /knight-transform/src/main/resources/META-INF/gradle-plugins/com.knight.transform.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.knight.transform.KnightPlugin -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi/1.0.1/spi-1.0.1-javadoc.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi/1.0.1/spi-1.0.1-sources.jar -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/knight/transform/2.0.0/transform-2.0.0.jar -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/knight/transform/1.0.10/transform-1.0.10.jar -------------------------------------------------------------------------------- /spi-api/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.0/applike-1.0.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/applike/applike/1.0.0/applike-1.0.0.aar -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/applike/applike/1.0.1/applike-1.0.1.aar -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /spi-applike/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/spi-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/1.0.1/spi-app-lib1-1.0.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-app-lib1/1.0.1/spi-app-lib1-1.0.1.aar -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/knight/transform/1.0.10/transform-1.0.10-javadoc.jar -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/knight/transform/1.0.10/transform-1.0.10-sources.jar -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/knight/transform/2.0.0/transform-2.0.0-javadoc.jar -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/knight/transform/2.0.0/transform-2.0.0-sources.jar -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/applike/applike/1.0.1/applike-1.0.1-javadoc.jar -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/applike/applike/1.0.1/applike-1.0.1-sources.jar -------------------------------------------------------------------------------- /spi-component1/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /spi-component2/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/BaseExtension.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform 2 | 3 | open class BaseExtension(var isScanJar: Boolean = true) { 4 | } 5 | -------------------------------------------------------------------------------- /spi-component2-impl/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.0/spi-gradle-plugin-1.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/1.0.0/spi-gradle-plugin-1.0.0.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1-javadoc.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1-sources.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0-javadoc.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0-sources.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0-javadoc.jar -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afirez/spi/HEAD/repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0-sources.jar -------------------------------------------------------------------------------- /spi-component1/src/main/java/com/afirez/spi/component1/SpiJavaProvider.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1; 2 | 3 | /** 4 | * https://github.com/afirez/spi 5 | */ 6 | public interface SpiJavaProvider { 7 | String helloJava(); 8 | } 9 | -------------------------------------------------------------------------------- /spi-component2/src/main/java/com/afirez/spi/component2/SpiKotlinProvider.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2 2 | 3 | /** 4 | * https://github.com/afirez/spi 5 | */ 6 | interface SpiKotlinProvider { 7 | fun helloKotlin(): String 8 | } 9 | -------------------------------------------------------------------------------- /spi-app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | spi 3 | 4 | 5 | Hello blank fragment 6 | 7 | -------------------------------------------------------------------------------- /spi-component2-impl/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | component2 3 | 4 | 5 | Hello blank fragment 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun May 26 01:07:02 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-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':spi-app', ':spi-applike', 2 | ':spi-component1', 3 | ':spi-component1-impl', 4 | ':spi-component2', 5 | ':spi-component2-impl', 6 | ':spi-api', 7 | ':spi-plugin', 8 | ':knight-transform' 9 | 10 | include ':spi-plugin-booster' 11 | -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /spi-api/src/main/java/com/afirez/spi/SPI.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | /** 7 | * https://github.com/afirez/spi 8 | */ 9 | @Retention(RetentionPolicy.CLASS) 10 | public @interface SPI { 11 | String path() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /spi-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /spi-app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #55b737 4 | 5 | @color/colorPrimary 6 | @color/colorPrimary 7 | 8 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/MemberEntity.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform 2 | 3 | open class MemberEntity(var access: Int, val className: String, val name: String, val desc: String, var type: Int = FIELD) { 4 | 5 | companion object { 6 | const val FIELD = 0 7 | const val METHOD = 1 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /spi-component1/src/main/java/com/afirez/spi/component1/SpiRouter1Provider.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1; 2 | 3 | /** 4 | * https://github.com/afirez/spi 5 | */ 6 | public interface SpiRouter1Provider { 7 | 8 | void navSpiActivity(); 9 | 10 | void navSpiFragmentActivity(); 11 | 12 | SpiJavaProvider spiJavaProvider(); 13 | } 14 | -------------------------------------------------------------------------------- /spi-component2/src/main/java/com/afirez/spi/component2/SpiRouter2Provider.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2; 2 | 3 | import androidx.fragment.app.Fragment; 4 | 5 | /** 6 | * https://github.com/afirez/spi 7 | */ 8 | public interface SpiRouter2Provider { 9 | 10 | SpiKotlinProvider spiKotlinProvider(); 11 | 12 | Fragment spiFragment(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /spi-plugin/src/main/java/com/afirez/spi/SpiExtension.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi 2 | 3 | import com.knight.transform.BaseExtension 4 | 5 | /** 6 | * https://github.com/afirez/spi 7 | */ 8 | open class SpiExtension( 9 | var spiPath: String = "com/afirez/spi/SPI", 10 | var loaderPath: String = "com/afirez/spi/ExtensionLoader", 11 | var add: String = "addExtension" 12 | ) : BaseExtension() -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.afirez.spi 4 | spi-app-lib1 5 | 6 | 1.0.1 7 | 8 | 1.0.1 9 | 10 | 20220908075135 11 | 12 | 13 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.afirez.spi 4 | spi 5 | 6 | 1.0.1 7 | 8 | 1.0.0 9 | 1.0.1 10 | 11 | 20220908075129 12 | 13 | 14 | -------------------------------------------------------------------------------- /repo/com/knight/transform/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.knight 4 | transform 5 | 6 | 2.0.0 7 | 8 | 1.0.10 9 | 2.0.0 10 | 11 | 20220908075124 12 | 13 | 14 | -------------------------------------------------------------------------------- /spi-applike/src/main/java/com/afirez/applike/AppLike.java: -------------------------------------------------------------------------------- 1 | package com.afirez.applike; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | /** 7 | * https://github.com/afirez/spi 8 | */ 9 | public interface AppLike { 10 | 11 | void attachBaseContext(Application app,Context base); 12 | 13 | void onCreate(Application app); 14 | 15 | void onTerminate(Application app) ; 16 | } 17 | -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.afirez.applike 4 | applike 5 | 6 | 1.0.1 7 | 8 | 1.0.0 9 | 1.0.1 10 | 11 | 20210805065854 12 | 13 | 14 | -------------------------------------------------------------------------------- /spi-component1-impl/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/Interceptor/IClassVIsitorInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.Interceptor 2 | 3 | 4 | interface Chain { 5 | fun transform(): ByteArray 6 | fun request(): ClassVisitorParams; 7 | } 8 | 9 | interface IClassVisitorInterceptor { 10 | fun intercept(chain: Chain): ByteArray 11 | } 12 | 13 | data class ClassVisitorParams(var inputByte: ByteArray, var classloader: ClassLoader? = null) -------------------------------------------------------------------------------- /spi-app/src/test/java/com/afirez/spi/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi 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 | -------------------------------------------------------------------------------- /spi-component2-impl/src/main/java/com/afirez/spi/component2/impl/SpiKotlinProviderImpl.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2.impl 2 | 3 | import com.afirez.spi.SPI 4 | import com.afirez.spi.component2.SpiKotlinProvider 5 | 6 | /** 7 | * https://github.com/afirez/spi 8 | */ 9 | @SPI(path = "/spi/provider/kotlin") 10 | class SpiKotlinProviderImpl : SpiKotlinProvider { 11 | override fun helloKotlin(): String { 12 | return "helloKotlin" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spi-plugin/src/main/java/com/afirez/spi/SpiContext.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi 2 | 3 | import com.knight.transform.BaseContext 4 | import com.knight.transform.BaseExtension 5 | import org.gradle.api.Project 6 | 7 | /** 8 | * https://github.com/afirez/spi 9 | */ 10 | class SpiContext(project: Project, extension: BaseExtension) : BaseContext(project, extension) { 11 | 12 | val extensionsMap = HashMap>() 13 | 14 | } -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.0/spi-1.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.afirez.spi 6 | spi 7 | 1.0.0 8 | aar 9 | 10 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi/1.0.1/spi-1.0.1.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.afirez.spi 6 | spi 7 | 1.0.1 8 | aar 9 | 10 | -------------------------------------------------------------------------------- /spi-api/src/test/java/com/afirez/spi/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi; 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 | } -------------------------------------------------------------------------------- /spi-applike/src/test/java/com/afirez/applike/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.applike; 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 | } -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.afirez.spi 4 | spi-gradle-plugin 5 | 6 | 3.0.0 7 | 8 | 1.0.0 9 | 1.0.1 10 | 2.0.0 11 | 3.0.0 12 | 13 | 20220908075647 14 | 15 | 16 | -------------------------------------------------------------------------------- /spi-component2/src/test/java/com/afirez/spi/component2/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2; 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 | } -------------------------------------------------------------------------------- /spi-component1-impl/src/test/java/com/afirez/spi/component1/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1; 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 | } -------------------------------------------------------------------------------- /spi-component2-impl/src/test/java/com/afirez/spi/component2/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2; 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 | } -------------------------------------------------------------------------------- /spi-component1/src/test/java/com/afirez/spi/component1/api/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1.api; 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 | } -------------------------------------------------------------------------------- /spi-component1-impl/src/main/res/layout/activity_spi_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /spi-app/src/main/java/com/afirez/spi/app/PrivateUtil.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.app; 2 | 3 | import android.util.Log; 4 | 5 | public class PrivateUtil { 6 | 7 | public static void reportPrivateApi(String hookedClass, String hookedMethod, String invokedClass, String invokedMethod) { 8 | Log.w("alphazz", "======hookedClass = " + hookedClass); 9 | Log.w("alphazz", "======hookedMethod = " + hookedMethod); 10 | Log.w("alphazz", "======invokedClass = " + invokedClass); 11 | Log.w("alphazz", "======invokedMethod = " + invokedMethod); 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/Utils/Timer.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.Utils 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | object Timer { 6 | val records = HashMap() 7 | 8 | 9 | fun start(key: String) { 10 | records[key] = System.nanoTime() 11 | } 12 | 13 | 14 | fun stop(key: String) { 15 | records[key]?.let { 16 | println("\n $key -->COST: ${TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - it)} ms \n") 17 | } 18 | } 19 | 20 | fun reset() { 21 | records.clear() 22 | } 23 | } -------------------------------------------------------------------------------- /spi-app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/task/WeavedClass.kt: -------------------------------------------------------------------------------- 1 | package transform.task 2 | 3 | import java.io.Serializable 4 | import java.util.LinkedHashSet 5 | 6 | class WeavedClass(val className: String) : Serializable { 7 | 8 | val weavedMethods = LinkedHashSet() 9 | 10 | 11 | fun addWeavedMethod(methodSignature: String) { 12 | weavedMethods.add(methodSignature) 13 | } 14 | 15 | 16 | fun getWeavedMethods(): Set { 17 | return weavedMethods 18 | } 19 | 20 | fun hasWeavedMethod(): Boolean { 21 | return weavedMethods.size > 0 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /gradle/pom-evaluator.gradle: -------------------------------------------------------------------------------- 1 | apply from: "${rootDir}/gradle/bintrayUpload.gradle" 2 | afterEvaluate { 3 | install { 4 | repositories.mavenInstaller { 5 | pom.groupId = project.projectGroupId 6 | pom.version = project.projectVersion 7 | 8 | pom.whenConfigured { pom -> 9 | pom.dependencies.findAll { dep -> dep.groupId == rootProject.name }.collect { dep -> 10 | dep.groupId = pom.groupId = rootProject.groupId 11 | dep.version = pom.version = rootProject.versionName 12 | } 13 | } 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | 11 | #idea 12 | .idea/ 13 | *.iml 14 | *.ipr 15 | *.iws 16 | out/ 17 | 18 | #Gradle build folder 19 | build/ 20 | .gradle 21 | 22 | #Android 23 | local.properties 24 | bin/ 25 | gen/ 26 | 27 | #Eclipse project files 28 | .classpath 29 | .settings/ 30 | .project 31 | 32 | #JNI 33 | *.o 34 | *.o.d 35 | 36 | #as 2.2 c/c++ compile 37 | .externalNativeBuild/ 38 | 39 | #OS autogen folder information 40 | .DS_Stroe 41 | Thumbs.db 42 | 43 | #Temp files 44 | *.bak 45 | *.tmp 46 | *.temp 47 | *.swp 48 | *.*~ 49 | ~*.* 50 | 51 | keystore.properties 52 | -------------------------------------------------------------------------------- /spi-component2-impl/src/main/res/layout/fragment_spi.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.1/spi-gradle-plugin-1.0.1.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.afirez.spi 5 | spi-gradle-plugin 6 | 1.0.1 7 | 8 | 9 | com.knight 10 | transform 11 | 1.0.10 12 | runtime 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/2.0.0/spi-gradle-plugin-2.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.afirez.spi 5 | spi-gradle-plugin 6 | 2.0.0 7 | 8 | 9 | com.knight 10 | transform 11 | 2.0.0 12 | runtime 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.0/applike-1.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.afirez.applike 6 | applike 7 | 1.0.0 8 | aar 9 | 10 | 11 | com.afirez.spi 12 | spi 13 | 1.0.0 14 | compile 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /repo/com/afirez/applike/applike/1.0.1/applike-1.0.1.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.afirez.applike 6 | applike 7 | 1.0.1 8 | aar 9 | 10 | 11 | com.afirez.spi 12 | spi 13 | 1.0.1 14 | compile 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/1.0.0/spi-gradle-plugin-1.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.afirez.spi 5 | spi-gradle-plugin 6 | 1.0.0 7 | 8 | 9 | com.knight 10 | transform 11 | 1.0.10 12 | runtime 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/Interceptor/ClassVisitorChain.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.Interceptor 2 | 3 | import org.gradle.api.GradleException 4 | 5 | class ClassVisitorChain(val index: Int, val interceptors: List, val params: ClassVisitorParams) : Chain { 6 | 7 | 8 | override fun transform(): ByteArray { 9 | if (index >= interceptors.size) { 10 | return params.inputByte 11 | } 12 | 13 | val next = ClassVisitorChain(index + 1, interceptors, params) 14 | val interceptor = interceptors[index] 15 | return interceptor.intercept(next) 16 | } 17 | 18 | override fun request(): ClassVisitorParams { 19 | return params 20 | } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /spi-applike/src/main/java/com/afirez/applike/App.java: -------------------------------------------------------------------------------- 1 | package com.afirez.applike; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | /** 7 | * https://github.com/afirez/spi 8 | */ 9 | public class App extends Application { 10 | @Override 11 | protected void attachBaseContext(Context base) { 12 | super.attachBaseContext(base); 13 | AppDelegate.getInstance().attachBaseContext(this, base); 14 | } 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | AppDelegate.getInstance().onCreate(this); 20 | } 21 | 22 | @Override 23 | public void onTerminate() { 24 | super.onTerminate(); 25 | AppDelegate.getInstance().onTerminate(this); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spi-app/src/androidTest/java/com/afirez/spi/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.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.afirez.spi", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/IPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform 2 | 3 | import com.knight.transform.Interceptor.IClassVisitorInterceptor 4 | import com.knight.transform.asm.IWeaver 5 | import org.objectweb.asm.ClassVisitor 6 | import org.objectweb.asm.ClassWriter 7 | 8 | interface IPlugin { 9 | fun createWeaveClassVisitor(classWriter: ClassWriter): ClassVisitor 10 | 11 | fun createScanClassVisitor(classWriter: ClassWriter): ClassVisitor? 12 | 13 | // 是否需要预先扫描class文件 14 | fun isNeedScanClass(): Boolean 15 | 16 | // 是否需要扫描R文件 17 | fun isNeedScanWeaveRClass(): Boolean 18 | 19 | fun getScanClassVisitorInterceptor(): List? 20 | fun getWeaveClassVisitorInterceptor(): List? 21 | 22 | } -------------------------------------------------------------------------------- /spi-component1-impl/src/main/java/com/afirez/spi/component1/impl/App.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1.impl 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.util.Log 6 | import com.afirez.spi.SPI 7 | import com.afirez.applike.AppLike 8 | 9 | /** 10 | * https://github.com/afirez/spi 11 | */ 12 | @SPI 13 | class App : AppLike { 14 | override fun attachBaseContext(app: Application?, base: Context?) { 15 | Log.d("App", "--> attachBaseContext(app = $app, base = $base) : $this") 16 | } 17 | 18 | override fun onCreate(app: Application?) { 19 | Log.d("App", "--> onCreate(app = $app) : $this") 20 | } 21 | 22 | override fun onTerminate(app: Application?) { 23 | Log.d("App", "--> onTerminate(app = $app) : $this") 24 | } 25 | } -------------------------------------------------------------------------------- /spi-component2-impl/src/main/java/com/afirez/spi/component2/impl/App.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2.impl 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.util.Log 6 | import com.afirez.spi.SPI 7 | import com.afirez.applike.AppLike 8 | 9 | /** 10 | * https://github.com/afirez/spi 11 | */ 12 | @SPI 13 | class App : AppLike { 14 | override fun attachBaseContext(app: Application?, base: Context?) { 15 | Log.d("App", "--> attachBaseContext(app = $app, base = $base) : $this") 16 | } 17 | 18 | override fun onCreate(app: Application?) { 19 | Log.d("App", "--> onCreate(app = $app) : $this") 20 | } 21 | 22 | override fun onTerminate(app: Application?) { 23 | Log.d("App", "--> onTerminate(app = $app) : $this") 24 | } 25 | } -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/asm/IWeaver.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.asm 2 | 3 | import com.knight.transform.IPlugin 4 | import java.io.File 5 | 6 | abstract class IWeaver(var classloader: ClassLoader? = null, var isNeedScanRClass: Boolean = false, 7 | val iPlugin: IPlugin) { 8 | abstract fun weaveJar(inputJar: File, outputJar: File) 9 | abstract fun weaveFile(inputFile: File, outputFile: File, inputDir: String) 10 | open fun isWeaveableClass(filePath: String): Boolean = if (isNeedScanRClass) { 11 | filePath.endsWith(".class") 12 | } else { 13 | filePath.endsWith(".class") 14 | && !filePath.contains("R$") 15 | && !filePath.contains("R.class") 16 | && !filePath.contains("BuildConfig.class") 17 | } 18 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | 4 | sudo: false 5 | 6 | 7 | android: 8 | components: 9 | - platform-tools 10 | - tools 11 | - build-tools-28.0.3 12 | - android-28 13 | - extra-android-m2repository 14 | - extra-android-support 15 | 16 | 17 | before_script: 18 | - chmod +x gradlew 19 | 20 | 21 | script: 22 | - ./gradlew assembleRelease 23 | 24 | # 部署 25 | deploy: 26 | # 部署到GitHub Release。 27 | # 除此之外,Travis CI还支持发布到fir.im、AWS、Google App Engine等 28 | provider: releases 29 | # Github oauth token 30 | api_key: 31 | secure: ${ghToken} 32 | # 部署文件路径;对于Android就部署生成的 apk 文件 33 | file: "spi-app/build/outputs/apk/spi-app-release.apk" 34 | # 避免 Travis CI在部署之前清空生成的APK文件 35 | skip_cleanup: true 36 | # 发布时机 37 | on: 38 | # tags设置为true表示只有在有tag的情况下才部署 39 | tags: true -------------------------------------------------------------------------------- /spi-app/src/main/java/com/afirez/spi/app/App.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.app; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import androidx.multidex.MultiDex; 7 | 8 | import com.afirez.applike.AppDelegate; 9 | 10 | public class App extends Application { 11 | @Override 12 | protected void attachBaseContext(Context base) { 13 | super.attachBaseContext(base); 14 | MultiDex.install(base); 15 | AppDelegate.getInstance().attachBaseContext(this, base); 16 | } 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | AppDelegate.getInstance().onCreate(this); 22 | } 23 | 24 | @Override 25 | public void onTerminate() { 26 | super.onTerminate(); 27 | AppDelegate.getInstance().onTerminate(this); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spi-api/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 | -------------------------------------------------------------------------------- /spi-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 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/weave/BaseClassVisitor.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.weave 2 | 3 | import com.knight.transform.BaseContext 4 | import org.objectweb.asm.ClassVisitor 5 | import org.objectweb.asm.Opcodes 6 | import transform.task.WeavedClass 7 | 8 | abstract class BaseClassVisitor>(val context: C, cv: ClassVisitor) : ClassVisitor(Opcodes.ASM5, cv), Opcodes { 9 | lateinit var className: String 10 | lateinit var weavedClass: WeavedClass 11 | override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array?) { 12 | super.visit(version, access, name, signature, superName, interfaces) 13 | className = name 14 | weavedClass = WeavedClass(className) 15 | context.weavedClassMap.add(weavedClass) 16 | } 17 | } -------------------------------------------------------------------------------- /spi-applike/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 | -------------------------------------------------------------------------------- /spi-component1/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 | -------------------------------------------------------------------------------- /spi-component2/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 | -------------------------------------------------------------------------------- /spi-component1-impl/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 | -------------------------------------------------------------------------------- /spi-component2-impl/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 | -------------------------------------------------------------------------------- /spi-api/src/androidTest/java/com/afirez/spi/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.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.afirez.spi.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spi-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /spi-applike/src/androidTest/java/com/afirez/applike/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.applike; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.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.afirez.applike.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spi-component1-impl/src/main/java/com/afirez/spi/component1/impl/SpiJavaProviderImpl.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1.impl; 2 | 3 | import com.afirez.spi.ExtensionLoader; 4 | import com.afirez.spi.SPI; 5 | import com.afirez.spi.component1.SpiJavaProvider; 6 | import com.afirez.spi.component2.SpiRouter2Provider; 7 | 8 | /** 9 | * https://github.com/afirez/spi 10 | */ 11 | @SPI(path = "/spi/provider/java") 12 | public class SpiJavaProviderImpl implements SpiJavaProvider { 13 | 14 | @Override 15 | public String helloJava() { 16 | SpiRouter2Provider spiRouter2Provider = ExtensionLoader.getInstance().loadExtension(SpiRouter2Provider.class); 17 | String kotlin = spiRouter2Provider != null && spiRouter2Provider.spiKotlinProvider() != null 18 | ? spiRouter2Provider.spiKotlinProvider().helloKotlin() : ""; 19 | return "helloJava --> " + kotlin; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spi-component1/src/androidTest/java/com/afirez/spi/component1/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.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.afirez.app.lib1.api.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spi-component1-impl/src/androidTest/java/com/afirez/spi/component1/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.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.afirez.spi.app.lib1.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spi-component2-impl/src/androidTest/java/com/afirez/spi/component2/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.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.afirez.spi.app.lib2.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spi-component2/src/androidTest/java/com/afirez/spi/component2/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.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.afirez.spi_app_lib2_api2.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spi-component2-impl/src/main/java/com/afirez/spi/component2/impl/SpiRouter2ProviderImpl.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2.impl 2 | 3 | import androidx.fragment.app.Fragment 4 | import com.afirez.spi.ExtensionLoader 5 | import com.afirez.spi.SPI 6 | import com.afirez.spi.component2.SpiKotlinProvider 7 | import com.afirez.spi.component2.SpiRouter2Provider 8 | 9 | @SPI(path = "com.afirez.spi.component2.SpiRouter2Provider") 10 | class SpiRouter2ProviderImpl : SpiRouter2Provider { 11 | 12 | override fun spiKotlinProvider(): SpiKotlinProvider? { 13 | return ExtensionLoader.getInstance().loadExtension("/spi/provider/kotlin") 14 | } 15 | 16 | override fun spiFragment(): Fragment? { 17 | val fragmentType = ExtensionLoader.getInstance().extension("/spi/fragment/spi") 18 | val fragment = fragmentType.newInstance() ?: null 19 | return fragment 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spi-component1/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | defaultConfig { 8 | minSdkVersion 19 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation 'androidx.appcompat:appcompat:1.0.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'androidx.test:runner:1.1.1' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 33 | } 34 | -------------------------------------------------------------------------------- /spi-app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /spi-component1-impl/src/main/java/com/afirez/spi/component1/impl/SpiActivity.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1.impl; 2 | 3 | import android.view.View; 4 | import android.widget.Toast; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import com.afirez.spi.SPI; 8 | 9 | /** 10 | * https://github.com/afirez/spi 11 | */ 12 | @SPI(path = "/spi/activity/spi") 13 | public class SpiActivity extends AppCompatActivity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_spi); 19 | findViewById(R.id.tvSpiActivity).setOnClickListener(new View.OnClickListener() { 20 | @Override 21 | public void onClick(View v) { 22 | Toast.makeText(SpiActivity.this.getApplicationContext(), "SpiActivity", Toast.LENGTH_SHORT).show(); 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spi-app/src/main/res/drawable/selector_green_round_5dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /spi-component1-impl/src/main/res/layout/activity_spi.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/transform/TransformContext.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.transform 2 | 3 | import com.android.build.api.transform.Format 4 | import com.android.build.api.transform.JarInput 5 | import com.android.build.api.transform.QualifiedContent 6 | import com.android.build.api.transform.TransformInvocation 7 | import com.android.ide.common.internal.WaitableExecutor 8 | import java.io.File 9 | import java.util.concurrent.ConcurrentHashMap 10 | 11 | class TransformContext(val invocation: TransformInvocation) { 12 | 13 | val targetJars = ConcurrentHashMap>() 14 | val targetFile = ArrayList() 15 | 16 | 17 | val waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool() 18 | 19 | 20 | fun getOutPutFile(content: QualifiedContent): File { 21 | return invocation.outputProvider.getContentLocation( 22 | content.file.absolutePath, 23 | content.contentTypes, 24 | content.scopes, 25 | if (content is JarInput) Format.JAR else Format.DIRECTORY) 26 | } 27 | } -------------------------------------------------------------------------------- /spi-component2/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | //apply plugin: 'kotlin-android-extensions' 6 | apply plugin: 'kotlin-parcelize' 7 | 8 | android { 9 | compileSdkVersion 28 10 | 11 | 12 | defaultConfig { 13 | minSdkVersion 19 14 | targetSdkVersion 28 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | 34 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | 36 | implementation 'androidx.appcompat:appcompat:1.0.2' 37 | testImplementation 'junit:junit:4.12' 38 | androidTestImplementation 'androidx.test:runner:1.1.1' 39 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 40 | } 41 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-gradle-plugin/3.0.0/spi-gradle-plugin-3.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.afirez.spi 5 | spi-gradle-plugin 6 | 3.0.0 7 | 8 | 9 | org.jetbrains.kotlin 10 | kotlin-stdlib-jdk8 11 | 1.5.31 12 | runtime 13 | 14 | 15 | com.didiglobal.booster 16 | booster-api 17 | 4.6.0 18 | runtime 19 | 20 | 21 | com.didiglobal.booster 22 | booster-transform-asm 23 | 4.6.0 24 | runtime 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/BaseContext.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform 2 | 3 | import com.android.build.gradle.AppExtension 4 | import com.android.build.gradle.LibraryPlugin 5 | import org.gradle.api.Project 6 | import transform.task.WeavedClass 7 | import java.io.File 8 | import java.io.FileNotFoundException 9 | 10 | open class BaseContext(val project: Project, val extension: T) { 11 | 12 | val isLibrary = project.plugins.hasPlugin(LibraryPlugin::class.java) 13 | var weavedClassMap = ArrayList() 14 | 15 | 16 | // private fun getSdkJarDir(): String { 17 | // val compileSdkVersion = android?.compileSdkVersion 18 | // return arrayOf(android?.sdkDirectory?.absolutePath, "platforms", compileSdkVersion).joinToString(File.separator) 19 | // } 20 | // 21 | // fun buildDir(): File { 22 | // return File(project.buildDir, "") 23 | // } 24 | // 25 | // @Throws(FileNotFoundException::class) 26 | // fun androidJar(): File { 27 | // val jar = File(getSdkJarDir(), "android.jar") 28 | // if (!jar.exists()) { 29 | // throw FileNotFoundException("Android jar not found!") 30 | // } 31 | // return jar 32 | // } 33 | } -------------------------------------------------------------------------------- /spi-component1-impl/src/main/java/com/afirez/spi/component1/impl/SpiRouter1ProviderImpl.java: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1.impl; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import com.afirez.spi.ExtensionLoader; 6 | import com.afirez.spi.SPI; 7 | import com.afirez.applike.AppDelegate; 8 | import com.afirez.spi.component1.SpiJavaProvider; 9 | import com.afirez.spi.component1.SpiRouter1Provider; 10 | 11 | /** 12 | * https://github.com/afirez/spi 13 | */ 14 | @SPI(path = "com.afirez.spi.component1.SpiRouter1Provider") 15 | public class SpiRouter1ProviderImpl implements SpiRouter1Provider { 16 | 17 | 18 | @Override 19 | public void navSpiActivity() { 20 | Context context = AppDelegate.getInstance().topActivityOrApp(); 21 | Intent intent = new Intent(context, SpiActivity.class); 22 | context.startActivity(intent); 23 | } 24 | 25 | @Override 26 | public void navSpiFragmentActivity() { 27 | Context context = AppDelegate.getInstance().topActivityOrApp(); 28 | Intent intent = new Intent(context, SpiFragmentActivity.class); 29 | context.startActivity(intent); 30 | } 31 | 32 | @Override 33 | public SpiJavaProvider spiJavaProvider() { 34 | return ExtensionLoader.getInstance().loadExtension("/spi/provider/java"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/Interceptor/impl/BaseClassVisitorInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.Interceptor.impl 2 | 3 | import com.knight.transform.BaseContext 4 | import com.knight.transform.Interceptor.Chain 5 | import com.knight.transform.Interceptor.IClassVisitorInterceptor 6 | import com.knight.transform.weave.BaseClassVisitor 7 | import com.knight.transform.weave.ExtendClassWriter 8 | import org.objectweb.asm.ClassReader 9 | import org.objectweb.asm.ClassVisitor 10 | import org.objectweb.asm.ClassWriter 11 | 12 | class BaseClassVisitorInterceptor(private val wrapClassWriter: (ClassWriter) -> ClassVisitor?) : IClassVisitorInterceptor { 13 | override fun intercept(chain: Chain): ByteArray { 14 | chain.request().let { 15 | val classReader = ClassReader(it.inputByte) 16 | val classWriter = ExtendClassWriter(it.classloader, classReader, 17 | ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS) 18 | val classVisitor = wrapClassWriter.invoke(classWriter) 19 | try { 20 | classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) 21 | } catch (e: Exception) { 22 | println("Exception occurred when visit code \n " + e.printStackTrace()) 23 | 24 | } 25 | it.inputByte = classWriter.toByteArray() 26 | return chain.transform() 27 | } 28 | } 29 | 30 | 31 | } -------------------------------------------------------------------------------- /spi-component2-impl/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | //apply plugin: 'kotlin-android-extensions' 6 | apply plugin: 'kotlin-parcelize' 7 | 8 | android { 9 | compileSdkVersion 28 10 | 11 | 12 | defaultConfig { 13 | minSdkVersion 19 14 | targetSdkVersion 28 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 34 | 35 | implementation "com.afirez.spi:spi:$SPI_VERSION" 36 | 37 | implementation project(':spi-applike') 38 | 39 | implementation project(':spi-component1') 40 | implementation project(':spi-component2') 41 | 42 | implementation 'androidx.appcompat:appcompat:1.0.2' 43 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 44 | testImplementation 'junit:junit:4.12' 45 | androidTestImplementation 'androidx.test:runner:1.1.1' 46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 47 | } 48 | -------------------------------------------------------------------------------- /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 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | 23 | BUILD_GRADLE_VERION=4.0.2 24 | 25 | TRANSFORM_VERSION=2.0.0 26 | 27 | #SPI_PLUGIN_VERSION=2.0.0 28 | SPI_PLUGIN_VERSION=3.0.0 29 | 30 | SPI_VERSION=1.0.1 31 | 32 | #groupId=com.afirez.spi 33 | #versionName=2.0.0 34 | 35 | -------------------------------------------------------------------------------- /spi-component1-impl/src/main/java/com/afirez/spi/component1/impl/SpiFragmentActivity.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component1.impl 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.afirez.spi.ExtensionLoader 6 | import com.afirez.spi.SPI 7 | import com.afirez.spi.component2.SpiRouter2Provider 8 | 9 | /** 10 | * https://github.com/afirez/spi 11 | */ 12 | @SPI(path = "/spi/activity/fragment") 13 | class SpiFragmentActivity : AppCompatActivity() { 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | // setContentView(R.layout.activity_spi_fragment) 18 | 19 | showSpiFragment() 20 | } 21 | 22 | private fun showSpiFragment() { 23 | val path = "/spi/fragment/spi" 24 | val tag = path 25 | var fragment = supportFragmentManager.findFragmentByTag(tag) 26 | if (fragment == null) { 27 | fragment = ExtensionLoader.getInstance().loadExtension(SpiRouter2Provider::class.java).spiFragment() 28 | } 29 | 30 | if (fragment == null) { 31 | return 32 | } 33 | 34 | val transaction = supportFragmentManager.beginTransaction() 35 | if (fragment.isAdded) { 36 | transaction.show(fragment) 37 | } else { 38 | transaction.add(android.R.id.content, fragment, tag) 39 | } 40 | transaction.commitAllowingStateLoss() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spi-component2-impl/src/main/java/com/afirez/spi/component2/impl/SpiFragment.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.component2.impl 2 | 3 | 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import android.widget.Toast 10 | import androidx.fragment.app.Fragment 11 | import com.afirez.spi.ExtensionLoader 12 | import com.afirez.spi.SPI 13 | import com.afirez.spi.component1.SpiRouter1Provider 14 | 15 | /** 16 | * https://github.com/afirez/spi 17 | */ 18 | @SPI(path = "/spi/fragment/spi") 19 | class SpiFragment : Fragment() { 20 | 21 | override fun onCreateView( 22 | inflater: LayoutInflater, container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View? { 25 | // Inflate the layout for this fragment 26 | return inflater.inflate(R.layout.fragment_spi, container, false) 27 | } 28 | 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | view.findViewById(R.id.tvSpiFragment).setOnClickListener { 33 | activity ?: return@setOnClickListener 34 | 35 | val spiRouter1Provider = ExtensionLoader.getInstance().loadExtension(SpiRouter1Provider::class.java) 36 | val tips = if (spiRouter1Provider?.spiJavaProvider() == null) { 37 | "" 38 | } else { 39 | spiRouter1Provider.spiJavaProvider().helloJava() 40 | } 41 | Toast.makeText(activity?.applicationContext, "SpiActivity $tips", Toast.LENGTH_SHORT).show() 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /repo/com/knight/transform/1.0.10/transform-1.0.10.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.knight 5 | transform 6 | 1.0.10 7 | 8 | 9 | com.android.tools.build 10 | gradle 11 | 3.2.0 12 | compile 13 | 14 | 15 | com.android.tools.build 16 | transform-api 17 | 2.0.0-deprecated-use-gradle-api 18 | compile 19 | 20 | 21 | commons-io 22 | commons-io 23 | 2.6 24 | compile 25 | 26 | 27 | org.ow2.asm 28 | asm 29 | 6.2.1 30 | compile 31 | 32 | 33 | org.ow2.asm 34 | asm-commons 35 | 6.2 36 | compile 37 | 38 | 39 | org.ow2.asm 40 | asm-util 41 | 6.0 42 | compile 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /knight-transform/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'kotlin' 3 | //apply plugin: 'com.novoda.bintray-release'//添加 4 | 5 | dependencies { 6 | api localGroovy() 7 | //gradle sdk 8 | api gradleApi() 9 | //build tools 10 | api "com.android.tools.build:gradle:$BUILD_GRADLE_VERION" 11 | 12 | 13 | // transform 14 | // api 'com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api' 15 | 16 | 17 | // 开源框架,io操作 18 | api 'commons-io:commons-io:2.6' 19 | 20 | //包中定义类基于核心 API 的相关操作类 21 | //包中定义了核心 API 的类,其中 ClassVisitor、FieldVisitor、MethodVisitor 和 AnnotationVisitor 22 | // 四个抽象类,用于访问 .class 字节码文件中的 fields、 methods 和 annotations 相关的指令。 23 | api 'org.ow2.asm:asm:7.1' 24 | //包中提供了实用的类或方法的 Adapter 方法转换器; 25 | api 'org.ow2.asm:asm-commons:7.1' 26 | //包中提供了常见的类分析框架和分析器类; 27 | api 'org.ow2.asm:asm-analysis:7.1' 28 | //包中提供了基于核心 API 的常见工具类。 29 | api 'org.ow2.asm:asm-util:7.1' 30 | //包中定义了基于树 API 的类,以及一些用于事件和树 API 转换的工具类; 31 | api 'org.ow2.asm:asm-tree:7.1' 32 | } 33 | 34 | apply from: "${rootDir}/gradle/pkg-src.gradle" 35 | apply plugin: 'maven' 36 | 37 | uploadArchives { 38 | repositories { 39 | mavenDeployer { 40 | //GVA的参数 41 | pom.groupId = 'com.knight'//组id 42 | pom.artifactId = 'transform'//名称 43 | pom.version = TRANSFORM_VERSION//版本号 44 | repository(url: uri('../repo'))//发布的目录 45 | } 46 | } 47 | } 48 | 49 | //publish { 50 | // userOrg = 'knight'//bintray.com用户名 51 | // groupId = 'com.knight'//jcenter上的路径 52 | // artifactId = 'transform'//项目名称 53 | // publishVersion = TRANSFORM_VERSION //版本号 54 | // desc = 'this is base plugin'//描述,不重要 55 | //} 56 | -------------------------------------------------------------------------------- /spi-component1-impl/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | defaultConfig { 8 | minSdkVersion 19 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation "com.afirez.spi:spi:$SPI_VERSION" 30 | 31 | implementation project(':spi-applike') 32 | 33 | implementation project(':spi-component1') 34 | implementation project(':spi-component2') 35 | 36 | implementation 'androidx.appcompat:appcompat:1.0.2' 37 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 38 | testImplementation 'junit:junit:4.12' 39 | androidTestImplementation 'androidx.test:runner:1.1.1' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 42 | } 43 | 44 | apply plugin: 'maven' 45 | apply plugin: 'kotlin-android' 46 | //apply plugin: 'kotlin-android-extensions' 47 | apply plugin: 'kotlin-parcelize' 48 | 49 | uploadArchives { 50 | repositories { 51 | mavenDeployer { 52 | repository(url: uri('../repo')) 53 | 54 | pom.groupId = 'com.afirez.spi' 55 | pom.artifactId = 'spi-app-lib1' 56 | pom.version = SPI_VERSION 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /spi-plugin/src/main/java/com/afirez/spi/SpiPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi 2 | 3 | import com.afirez.spi.weave.SpiScanClassVisitor 4 | import com.afirez.spi.weave.SpiWeaveCodeClassVisitor 5 | import com.android.build.gradle.TestedExtension 6 | import com.knight.transform.KnightPlugin 7 | import org.gradle.api.Project 8 | import org.objectweb.asm.ClassVisitor 9 | import org.objectweb.asm.ClassWriter 10 | 11 | /** 12 | * https://github.com/afirez/spi 13 | */ 14 | class SpiPlugin : KnightPlugin() { 15 | 16 | private val EXTENSION_NAME = "spi" 17 | 18 | override val isNeedPrintMapAndTaskCostTime: Boolean = true 19 | 20 | override fun createExtensions(): SpiExtension { 21 | project.extensions.create(EXTENSION_NAME, SpiExtension::class.java) 22 | (project.extensions.getByName(EXTENSION_NAME) as SpiExtension).apply { 23 | context = SpiContext(project, this) 24 | return this 25 | } 26 | } 27 | 28 | override fun createScanClassVisitor(classWriter: ClassWriter): ClassVisitor? { 29 | return SpiScanClassVisitor(context, classWriter) 30 | } 31 | 32 | override fun createWeaveClassVisitor(classWriter: ClassWriter): ClassVisitor { 33 | return SpiWeaveCodeClassVisitor(context, classWriter) 34 | } 35 | 36 | override fun getContext(project: Project, extension: SpiExtension, android: TestedExtension): SpiContext { 37 | return SpiContext(project, extension) 38 | } 39 | 40 | override fun getTransformName(): String { 41 | return "SpiTransform" 42 | } 43 | 44 | override fun isNeedScanClass(): Boolean { 45 | return true 46 | } 47 | 48 | override fun isNeedScanWeaveRClass(): Boolean { 49 | return false 50 | } 51 | } -------------------------------------------------------------------------------- /gradle/pkg-src.gradle: -------------------------------------------------------------------------------- 1 | //apply plugin: 'com.jfrog.bintray' 2 | //apply plugin: 'com.github.dcendents.android-maven' 3 | apply plugin: 'org.jetbrains.dokka-android' 4 | 5 | def hasKotlin = project.hasProperty("kotlin") 6 | def hasAndroid = project.hasProperty("android") 7 | def hasJava = project.hasProperty("java") 8 | println("hasKotlin = $hasKotlin hasAndroid = $hasAndroid hasJava = $hasJava") 9 | 10 | if (hasKotlin) { //Kotlin libraries 11 | def srcDirs = hasAndroid ? android.sourceSets.main.java.srcDirs : sourceSets.main.allSource 12 | task sourcesJar(type: Jar) { 13 | classifier = 'sources' 14 | from srcDirs 15 | } 16 | if (hasAndroid) { 17 | task javadoc(type: Javadoc, dependsOn: dokka) { 18 | 19 | } 20 | } 21 | } else if (hasAndroid) { // Android libraries 22 | task sourcesJar(type: Jar) { 23 | classifier = 'sources' 24 | from android.sourceSets.main.java.srcDirs 25 | } 26 | 27 | task javadoc(type: Javadoc) { 28 | source = android.sourceSets.main.java.srcDirs 29 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 30 | } 31 | } else { // Java libraries 32 | task sourcesJar(type: Jar, dependsOn: classes) { 33 | classifier = 'sources' 34 | from sourceSets.main.allSource 35 | } 36 | } 37 | 38 | task javadocJar(type: Jar, dependsOn: javadoc) { 39 | classifier = 'javadoc' 40 | from javadoc.destinationDir 41 | } 42 | 43 | //解决kotlin javadoc.options抱错 44 | dokka { 45 | outputFormat = 'html' 46 | outputDirectory = "$buildDir/javadoc" 47 | } 48 | 49 | tasks.withType(Javadoc) { 50 | options.addStringOption('Xdoclint:none', '-quiet') 51 | options.addStringOption('encoding', 'UTF-8') 52 | options.addStringOption('charSet', 'UTF-8') 53 | } 54 | artifacts { 55 | archives javadocJar 56 | archives sourcesJar 57 | } 58 | sourceSets { 59 | main.java.srcDirs += 'src/main/kotlin' 60 | } -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/KnightTaskPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform 2 | 3 | import com.android.build.gradle.* 4 | import com.android.build.gradle.api.BaseVariant 5 | import com.knight.transform.Utils.PrintAllTaskUtil 6 | import org.gradle.api.GradleException 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | import transform.KnightTransform 10 | 11 | abstract class KnightTaskPlugin> : Plugin { 12 | 13 | protected lateinit var context: C 14 | protected lateinit var extension: E 15 | 16 | protected lateinit var project: Project 17 | protected lateinit var android: TestedExtension 18 | 19 | 20 | abstract fun getContext(project: Project, extension: E, android: TestedExtension): C 21 | abstract fun createExtensions(): E 22 | lateinit var transform: KnightTransform 23 | 24 | override fun apply(project: Project) { 25 | if (!project.plugins.hasPlugin(AppPlugin::class.java) && !project.plugins.hasPlugin(LibraryPlugin::class.java)) { 26 | throw GradleException("'com.android.application' or 'com.android.library' plugin required!") 27 | } 28 | 29 | val aClass = if (project.plugins.hasPlugin(AppPlugin::class.java)) 30 | AppExtension::class.java 31 | else LibraryExtension::class.java 32 | this.project = project 33 | android = project.extensions.getByType(aClass) 34 | extension = createExtensions() 35 | context = getContext(project, extension, android) 36 | project.afterEvaluate { 37 | if (android is AppExtension) { 38 | (android as AppExtension).applicationVariants.all { 39 | createTask(it, context) 40 | } 41 | } else if (android is LibraryExtension) { 42 | (android as LibraryExtension).libraryVariants.all { 43 | createTask(it, context) 44 | } 45 | } 46 | } 47 | } 48 | 49 | abstract fun createTask(variant: BaseVariant, context: C) 50 | } -------------------------------------------------------------------------------- /repo/com/knight/transform/2.0.0/transform-2.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.knight 5 | transform 6 | 2.0.0 7 | 8 | 9 | com.android.tools.build 10 | gradle 11 | 4.0.2 12 | compile 13 | 14 | 15 | commons-io 16 | commons-io 17 | 2.6 18 | compile 19 | 20 | 21 | org.ow2.asm 22 | asm 23 | 7.1 24 | compile 25 | 26 | 27 | org.ow2.asm 28 | asm-commons 29 | 7.1 30 | compile 31 | 32 | 33 | org.ow2.asm 34 | asm-analysis 35 | 7.1 36 | compile 37 | 38 | 39 | org.ow2.asm 40 | asm-util 41 | 7.1 42 | compile 43 | 44 | 45 | org.ow2.asm 46 | asm-tree 47 | 7.1 48 | compile 49 | 50 | 51 | org.jetbrains.kotlin 52 | kotlin-stdlib-jdk8 53 | 1.5.31 54 | compile 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /spi-app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /repo/com/afirez/spi/spi-app-lib1/1.0.1/spi-app-lib1-1.0.1.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.afirez.spi 6 | spi-app-lib1 7 | 1.0.1 8 | aar 9 | 10 | 11 | org.jetbrains.kotlin 12 | kotlin-parcelize-runtime 13 | 1.5.31 14 | compile 15 | 16 | 17 | com.afirez.spi 18 | spi 19 | 1.0.1 20 | compile 21 | 22 | 23 | spi 24 | applike 25 | unspecified 26 | compile 27 | 28 | 29 | spi 30 | spi-component1 31 | unspecified 32 | compile 33 | 34 | 35 | spi 36 | spi-component2 37 | unspecified 38 | compile 39 | 40 | 41 | androidx.appcompat 42 | appcompat 43 | 1.0.2 44 | compile 45 | 46 | 47 | androidx.constraintlayout 48 | constraintlayout 49 | 1.1.3 50 | compile 51 | 52 | 53 | org.jetbrains.kotlin 54 | kotlin-stdlib-jdk7 55 | 1.5.31 56 | compile 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /spi-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'kotlin' 3 | 4 | 5 | dependencies { 6 | implementation gradleApi() 7 | implementation localGroovy() 8 | // implementation 'commons-io:commons-io:2.6' 9 | // implementation 'commons-codec:commons-codec:1.10' 10 | //noinspection GradleDependency 11 | // implementation 'org.ow2.asm:asm:5.1' 12 | //noinspection GradleDependency 13 | // implementation 'org.ow2.asm:asm-util:5.1' 14 | //noinspection GradleDependency 15 | // implementation 'org.ow2.asm:asm-commons:5.1' 16 | // implementation 'com.quinn.hunter:hunter-transform:0.9.0' 17 | // implementation "com.knight.component:component:1.0.1" 18 | implementation "com.knight:transform:2.0.0" 19 | } 20 | 21 | repositories { 22 | google() 23 | maven { 24 | url uri('../repo') 25 | } 26 | jcenter() 27 | mavenCentral() 28 | } 29 | 30 | apply from: "${rootDir}/gradle/pkg-src.gradle" 31 | apply plugin: 'maven' 32 | 33 | uploadArchives { 34 | repositories { 35 | mavenDeployer { 36 | pom.groupId = 'com.afirez.spi' 37 | pom.artifactId = 'spi-gradle-plugin' 38 | pom.version = SPI_PLUGIN_VERSION 39 | repository(url: uri('../repo')) 40 | } 41 | } 42 | } 43 | 44 | //ext { 45 | // projectGroupId = 'com.afirez.spi' 46 | // projectPackageing = 'jar' 47 | // projectName = 'spi-gradle-plugin' 48 | // projectArtifactId = 'spi-gradle-plugin' 49 | // projectDesc = 'SPI for componentization.' 50 | // 51 | // siteUrl = 'https://github.com/afirez/spi' 52 | // gitUrl = 'https://github.com/afirez/spi.git' 53 | // 54 | // projectVersion = '1.0.1' 55 | // 56 | // developerId = 'afirez' 57 | // developerName = 'afirez' 58 | // developerEmail = 'afirez.io@gmail.com' 59 | // 60 | // licenseName = 'The Apache Software License, Version 2.0' 61 | // licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 62 | // 63 | // bintrayUser = 'afirez' 64 | // bintrayApiKey = '' 65 | // bintrayRepo = 'android' 66 | // bintrayName = 'spi-gradle-plugin' 67 | // bintrayLicenses = ["Apache-2.0"] 68 | //} 69 | // 70 | //apply from: "${rootDir}/gradle/pom-evaluator.gradle" -------------------------------------------------------------------------------- /spi-applike/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | defaultConfig { 8 | minSdkVersion 19 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation "com.afirez.spi:spi:$SPI_VERSION" 30 | 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test:runner:1.1.1' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 34 | } 35 | 36 | apply from: "${rootDir}/gradle/pkg-src.gradle" 37 | apply plugin: 'maven' 38 | 39 | uploadArchives { 40 | repositories { 41 | mavenDeployer { 42 | repository(url: uri('../repo')) 43 | 44 | pom.groupId = 'com.afirez.applike' 45 | pom.artifactId = 'applike' 46 | pom.version = SPI_VERSION 47 | // pom.version = "1.0.0" 48 | } 49 | } 50 | } 51 | 52 | //ext { 53 | // projectGroupId = 'com.afirez.applike' 54 | // projectPackageing = 'aar' 55 | // projectName = 'applike' 56 | // projectArtifactId = 'applike' 57 | // projectDesc = 'SPI Applike for componentization.' 58 | // 59 | // siteUrl = 'https://github.com/afirez/spi' 60 | // gitUrl = 'https://github.com/afirez/spi.git' 61 | // 62 | // projectVersion = '1.0.1' 63 | // 64 | // developerId = 'afirez' 65 | // developerName = 'afirez' 66 | // developerEmail = 'afirez.io@gmail.com' 67 | // 68 | // licenseName = 'The Apache Software License, Version 2.0' 69 | // licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 70 | // 71 | // bintrayUser = 'afirez' 72 | // bintrayApiKey = '' 73 | // bintrayRepo = 'android' 74 | // bintrayName = 'applike' 75 | // bintrayLicenses = ["Apache-2.0"] 76 | //} 77 | // 78 | //apply from: "${rootDir}/gradle/pom-evaluator.gradle" 79 | -------------------------------------------------------------------------------- /spi-api/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | defaultConfig { 8 | minSdkVersion 19 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | // 29 | // implementation 'androidx.appcompat:appcompat:1.0.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'androidx.test:runner:1.1.1' 32 | // androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 33 | } 34 | 35 | apply from: "${rootDir}/gradle/pkg-src.gradle" 36 | apply plugin: 'maven' 37 | 38 | uploadArchives { 39 | repositories { 40 | mavenDeployer { 41 | repository(url: uri('../repo')) 42 | // repository(url: uri(rootProject.ext.mavenAndroidRepoUrlLocal)) 43 | 44 | pom.groupId = 'com.afirez.spi' 45 | pom.artifactId = 'spi' 46 | pom.version = SPI_VERSION 47 | } 48 | } 49 | } 50 | 51 | //ext { 52 | // projectGroupId = 'com.afirez.spi' 53 | // projectPackageing = 'aar' 54 | // projectName = 'spi' 55 | // projectArtifactId = 'spi' 56 | // projectDesc = 'SPI for componentization.' 57 | // 58 | // siteUrl = 'https://github.com/afirez/spi' 59 | // gitUrl = 'https://github.com/afirez/spi.git' 60 | // 61 | // projectVersion = '1.0.1' 62 | // 63 | // developerId = 'afirez' 64 | // developerName = 'afirez' 65 | // developerEmail = 'afirez.io@gmail.com' 66 | // 67 | // licenseName = 'The Apache Software License, Version 2.0' 68 | // licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 69 | // 70 | // bintrayUser = 'afirez' 71 | // bintrayApiKey = '' 72 | // bintrayRepo = 'android' 73 | // bintrayName = 'spi' 74 | // bintrayLicenses = ["Apache-2.0"] 75 | //} 76 | // 77 | //apply from: "${rootDir}/gradle/pom-evaluator.gradle" 78 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/task/OutPutMappingTask.kt: -------------------------------------------------------------------------------- 1 | package transform.task 2 | 3 | import org.apache.commons.io.FileUtils 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.model.ObjectFactory 6 | import org.gradle.api.tasks.Input 7 | import org.gradle.api.tasks.Internal 8 | import org.gradle.api.tasks.OutputFile 9 | import org.gradle.api.tasks.TaskAction 10 | import java.lang.StringBuilder 11 | import kotlin.reflect.jvm.internal.impl.load.kotlin.JvmType 12 | 13 | open class OutPutMappingTask : DefaultTask() { 14 | init { 15 | group = "outputmapping" 16 | description = "write mapping file" 17 | } 18 | 19 | @Input 20 | var variantName = project.objects.property(String::class.java) 21 | 22 | // @OutputFile 23 | // var outputMappingFile = newOutputFile() 24 | 25 | // @OutputFile 26 | // var outputMappingFile = getServices().get(ObjectFactory::class.java).fileProperty() 27 | 28 | @OutputFile // property needs an annotation 29 | var outputMappingFile = project.objects.fileProperty() 30 | 31 | //// @Internal 32 | // var classes = project.objects.property(ArrayList::class.java) 33 | 34 | @Internal 35 | var classes = project.objects.listProperty(Any::class.java) 36 | 37 | @TaskAction 38 | fun writeMapping() { 39 | var loggable = false 40 | val mappingFile = outputMappingFile.get().asFile 41 | 42 | FileUtils.touch(mappingFile) 43 | val content = StringBuilder() 44 | if (loggable) println("outputtask size: ${classes.get().size}") 45 | (classes.get() as List).forEach { 46 | it?.takeIf { it -> 47 | it?.hasWeavedMethod() 48 | }?.let { 49 | val className = it.className 50 | val doubleCheckMethods = it.weavedMethods 51 | content.append(className).append("\n") 52 | if (loggable) println(className) 53 | doubleCheckMethods.forEach { 54 | content.append("\u21E2 $it").append("\n") 55 | if (loggable) println("\u21E2 $it") 56 | } 57 | } 58 | } 59 | mappingFile.writeText(content.toString()) 60 | // println("Success wrote TXT mapping report to file://${outputMappingFile}") 61 | 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /spi-app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/KnightTransform.kt: -------------------------------------------------------------------------------- 1 | package transform 2 | 3 | import com.android.build.api.transform.QualifiedContent 4 | import com.android.build.api.transform.Transform 5 | import com.android.build.api.transform.TransformInvocation 6 | import com.android.build.gradle.internal.pipeline.TransformManager 7 | import com.knight.transform.BaseContext 8 | import com.knight.transform.IPlugin 9 | import com.knight.transform.Utils.Log 10 | import com.knight.transform.Utils.TransFormUtil 11 | import com.knight.transform.asm.CodeWeaver 12 | import com.knight.transform.asm.ScanWeaver 13 | import transform.Utils.ASMUtils 14 | 15 | open class KnightTransform(private val context: BaseContext<*>, val iPlugin: IPlugin, val getTransformName: () -> String) : Transform() { 16 | private val codeWeaver: CodeWeaver = CodeWeaver(iPlugin) 17 | private val scanWeaver: ScanWeaver? = if (iPlugin.isNeedScanClass()) { 18 | ScanWeaver(iPlugin) 19 | } else null 20 | 21 | 22 | override fun getName(): String { 23 | return getTransformName.invoke() 24 | } 25 | 26 | override fun getInputTypes(): MutableSet { 27 | return TransformManager.CONTENT_CLASS 28 | } 29 | 30 | override fun isIncremental(): Boolean { 31 | return true 32 | } 33 | 34 | override fun getScopes(): MutableSet { 35 | return if (context.isLibrary) TransformManager.PROJECT_ONLY else TransformManager.SCOPE_FULL_PROJECT 36 | } 37 | 38 | override fun transform(invocation: TransformInvocation) { 39 | val classLoader = ASMUtils.getClassLoader(invocation.inputs, invocation.referencedInputs, context.project) 40 | scanWeaver?.let { 41 | it.classloader = classLoader 42 | it.isNeedScanRClass = iPlugin.isNeedScanWeaveRClass() 43 | val startTime = System.currentTimeMillis() 44 | TransFormUtil.transform(context, it, invocation) 45 | val costTime = System.currentTimeMillis() - startTime 46 | Log.i("$name : scan code has costed $costTime ms") 47 | } 48 | codeWeaver.run { 49 | val startTime = System.currentTimeMillis() 50 | classloader = classLoader 51 | isNeedScanRClass = iPlugin.isNeedScanWeaveRClass() 52 | TransFormUtil.transform(context, this, invocation) 53 | val costTime = System.currentTimeMillis() - startTime 54 | Log.i("$name : weave code has costed $costTime ms") 55 | } 56 | 57 | } 58 | } -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/Utils/PrintAllTaskUtil.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.Utils 2 | 3 | import com.knight.transform.BaseContext 4 | import org.gradle.BuildListener 5 | import org.gradle.BuildResult 6 | import org.gradle.api.Project 7 | import org.gradle.api.Task 8 | import org.gradle.api.execution.TaskExecutionListener 9 | import org.gradle.api.initialization.Settings 10 | import org.gradle.api.invocation.Gradle 11 | import org.gradle.api.tasks.TaskState 12 | 13 | /** 14 | * description 15 | * 16 | * @author liyachao 17 | * @date 2019/3/23 18 | */ 19 | object PrintAllTaskUtil { 20 | val TAG = "PrintAllTaskUtil" 21 | //用来记录 task 的执行时长等信息 22 | val timeCostMap = LinkedHashMap() 23 | 24 | private val taskExecutionListener = object : TaskExecutionListener { 25 | override fun beforeExecute(task: Task) { 26 | timeCostMap[task.path] = TaskExecTimeInfo(System.currentTimeMillis(), 0, task.path, 0) 27 | } 28 | 29 | override fun afterExecute(task: Task, p1: TaskState) { 30 | timeCostMap[task.path]?.let { 31 | it.endTime = System.currentTimeMillis() 32 | it.totalTime = it.endTime - it.startTime 33 | } 34 | } 35 | } 36 | 37 | private val buildListener = object : BuildListener { 38 | override fun buildFinished(buildResult: BuildResult) { 39 | val sb = StringBuilder() 40 | sb.append("build finished, now println all task execution time: ") 41 | timeCostMap.forEach { 42 | sb.append("\n${it.key} [${it.value.totalTime}]") 43 | } 44 | Log.e(sb.toString()) 45 | } 46 | 47 | override fun projectsLoaded(p0: Gradle) { 48 | } 49 | 50 | override fun buildStarted(p0: Gradle) { 51 | } 52 | 53 | override fun projectsEvaluated(p0: Gradle) { 54 | } 55 | 56 | override fun settingsEvaluated(p0: Settings) { 57 | } 58 | 59 | } 60 | 61 | fun printAllTasks(context: BaseContext<*>) { 62 | context.project.gradle.addListener(taskExecutionListener) 63 | context.project.gradle.addBuildListener(buildListener) 64 | } 65 | 66 | fun printAllTasks(project: Project) { 67 | project.gradle.addListener(taskExecutionListener) 68 | project.gradle.addBuildListener(buildListener) 69 | } 70 | } 71 | 72 | data class TaskExecTimeInfo(var startTime: Long = 0, var endTime: Long = 0, 73 | var path: String = "", var totalTime: Long = 0) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/asm/ScanWeaver.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.asm 2 | 3 | import com.knight.transform.IPlugin 4 | import com.knight.transform.Interceptor.ClassVisitorChain 5 | import com.knight.transform.Interceptor.ClassVisitorParams 6 | import com.knight.transform.Utils.Log 7 | import com.knight.transform.weave.ExtendClassWriter 8 | import org.objectweb.asm.ClassReader 9 | import org.objectweb.asm.ClassVisitor 10 | import org.objectweb.asm.ClassWriter 11 | import java.io.BufferedInputStream 12 | import java.io.File 13 | import java.io.FileInputStream 14 | import java.io.InputStream 15 | import java.util.zip.ZipEntry 16 | import java.util.zip.ZipFile 17 | 18 | 19 | class ScanWeaver(iPlugin: IPlugin) : IWeaver(iPlugin = iPlugin) { 20 | 21 | val TAG = "ScanWeaver" 22 | override fun weaveJar(inputJar: File, outputJar: File) { 23 | val inputZip = ZipFile(inputJar) 24 | Log.d("scanJar is ${inputJar.name}") 25 | inputZip.entries().toList().forEach { entry -> 26 | val outEntry = ZipEntry(entry.name) 27 | if (isWeaveableClass(outEntry.name.replace("/", "."))) { 28 | scanWithByteChain(BufferedInputStream(inputZip.getInputStream(entry))) 29 | } 30 | } 31 | } 32 | 33 | override fun weaveFile(inputFile: File, outputFile: File, inputDir: String) { 34 | var inputBaseDir = inputDir 35 | if (!inputBaseDir.endsWith("/")) inputBaseDir += "/" 36 | if (inputFile.isFile && isWeaveableClass(inputFile.absolutePath.replace(inputBaseDir, "").replace("/", "."))) { 37 | val inputStream = FileInputStream(inputFile) 38 | scanWithByteChain(inputStream) 39 | } 40 | } 41 | 42 | fun scanWithByte(intputStream: InputStream) { 43 | val classReader = ClassReader(intputStream) 44 | val classWriter = ExtendClassWriter(classloader, classReader, 45 | ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS) 46 | val classVisitor = iPlugin.createScanClassVisitor(classWriter) 47 | try { 48 | classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) 49 | } catch (e: Exception) { 50 | println("Exception occurred when visit code \n " + e.printStackTrace()) 51 | 52 | } 53 | } 54 | 55 | fun scanWithByteChain(intputStream: InputStream) { 56 | iPlugin.getScanClassVisitorInterceptor()?.let { 57 | val chain = ClassVisitorChain(0, it, ClassVisitorParams(intputStream.readBytes(), classloader)) 58 | chain.transform() 59 | } ?: scanWithByte(intputStream) 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /spi-plugin-booster/src/main/java/com/afirez/spi/dummy/TestPrivateTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.dummy 2 | 3 | import com.didiglobal.booster.kotlinx.asIterable 4 | import com.didiglobal.booster.transform.TransformContext 5 | import com.didiglobal.booster.transform.asm.ClassTransformer 6 | import org.objectweb.asm.Opcodes 7 | import org.objectweb.asm.tree.ClassNode 8 | import org.objectweb.asm.tree.LdcInsnNode 9 | import org.objectweb.asm.tree.MethodInsnNode 10 | 11 | //@AutoService(ClassTransformer::class) 12 | class TestPrivateTransformer : ClassTransformer { 13 | 14 | override fun transform(context: TransformContext, klass: ClassNode): ClassNode { 15 | val hookedClassName = klass.name 16 | var hookedMethodName = "" 17 | val iterator = klass.methods.iterator() 18 | while (iterator.hasNext()) { 19 | val method = iterator.next() 20 | method.instructions?.iterator()?.forEach { 21 | // 查找TelephonyManager.getNetworkType 22 | if (it.opcode == Opcodes.INVOKEVIRTUAL && it is MethodInsnNode) { 23 | if (it.owner == "android/telephony/TelephonyManager" && it.name == "getNetworkType") { 24 | hookedMethodName = method.name 25 | 26 | println("====find private success : $hookedClassName / $hookedMethodName") 27 | } 28 | } 29 | } 30 | // 如果hookedMethodName不为空那么表示找到了,那么就插入代码 31 | if (!(hookedMethodName == null || hookedClassName.isEmpty())) { 32 | method?.instructions?.iterator()?.asIterable()?.filter { 33 | it.opcode == Opcodes.RETURN 34 | }?.forEach { 35 | method.instructions?.apply { 36 | insertBefore(it, LdcInsnNode(hookedClassName)) 37 | insertBefore(it, LdcInsnNode(hookedMethodName)) 38 | insertBefore(it, LdcInsnNode("android/telephony/TelephonyManager")) 39 | insertBefore(it, LdcInsnNode("getNetworkType")) 40 | insertBefore( 41 | it, 42 | MethodInsnNode( 43 | Opcodes.INVOKESTATIC, 44 | "com/afirez/spi/app", 45 | "reportPrivateApi", 46 | "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", 47 | false 48 | ) 49 | ) 50 | } 51 | } 52 | } 53 | } 54 | 55 | return super.transform(context, klass) 56 | } 57 | } -------------------------------------------------------------------------------- /spi-plugin/src/main/java/com/afirez/spi/weave/SpiWeaveCodeClassVisitor.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.weave 2 | 3 | import com.afirez.spi.SpiContext 4 | import com.afirez.spi.SpiExtension 5 | import com.knight.transform.weave.BaseClassVisitor 6 | import org.objectweb.asm.* 7 | 8 | /** 9 | * https://github.com/afirez/spi 10 | */ 11 | class SpiWeaveCodeClassVisitor(context: SpiContext, cv: ClassVisitor) : BaseClassVisitor(context, cv) { 12 | val extension = context.extension as SpiExtension 13 | 14 | var isServiceLoader = false 15 | 16 | 17 | override fun visit( 18 | version: Int, 19 | access: Int, 20 | name: String, 21 | signature: String?, 22 | superName: String?, 23 | interfaces: Array? 24 | ) { 25 | super.visit(version, access, name, signature, superName, interfaces) 26 | isServiceLoader = name == extension.loaderPath 27 | } 28 | 29 | override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor { 30 | return super.visitAnnotation(descriptor, visible) 31 | } 32 | 33 | override fun visitMethod( 34 | access: Int, 35 | name: String?, 36 | descriptor: String?, 37 | signature: String?, 38 | exceptions: Array? 39 | ): MethodVisitor { 40 | 41 | val mv = super.visitMethod(access, name, descriptor, signature, exceptions) 42 | 43 | if (isServiceLoader && access == 2 && name == "" && descriptor == "()V") { //找到目标类的私有构造方法 44 | return SpiWeaveMethodVisitor(context, mv) 45 | } 46 | 47 | return mv 48 | } 49 | 50 | class SpiWeaveMethodVisitor(val context: SpiContext, mv: MethodVisitor?) : MethodVisitor(Opcodes.ASM5, mv) { 51 | val extension = context.extension as SpiExtension 52 | 53 | override fun visitInsn(opcode: Int) { 54 | when (opcode) { 55 | Opcodes.IRETURN, 56 | Opcodes.FRETURN, 57 | Opcodes.ARETURN, 58 | Opcodes.LRETURN, 59 | Opcodes.DRETURN, 60 | Opcodes.RETURN -> { 61 | context.extensionsMap.forEach { type, map -> 62 | map.forEach{path, extension -> 63 | inject(type, extension, path) 64 | } 65 | } 66 | } 67 | } 68 | super.visitInsn(opcode) 69 | } 70 | 71 | private fun inject(type: String, extension: String, path: String) { 72 | println(" ====> Spi ${this.extension.add} [ $type --> $extension ]") 73 | mv.visitVarInsn(Opcodes.ALOAD, 0) 74 | mv.visitLdcInsn(Type.getObjectType(type)) 75 | mv.visitLdcInsn(Type.getObjectType(extension)) 76 | mv.visitLdcInsn(path) 77 | mv.visitMethodInsn( 78 | Opcodes.INVOKEVIRTUAL, 79 | this.extension.loaderPath, 80 | this.extension.add, 81 | "(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)V", 82 | false 83 | ) 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /spi-plugin-booster/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'kotlin' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | //plugins { 6 | // id 'java-library' 7 | // id 'org.jetbrains.kotlin.jvm' 8 | //} 9 | 10 | java { 11 | sourceCompatibility = JavaVersion.VERSION_1_8 12 | targetCompatibility = JavaVersion.VERSION_1_8 13 | } 14 | 15 | compileKotlin { 16 | kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8 17 | } 18 | 19 | 20 | dependencies { 21 | // implementation gradleApi() 22 | // implementation localGroovy() 23 | 24 | // implementation 'commons-io:commons-io:2.6' 25 | // implementation 'commons-codec:commons-codec:1.10' 26 | //noinspection GradleDependency 27 | // implementation 'org.ow2.asm:asm:5.1' 28 | //noinspection GradleDependency 29 | // implementation 'org.ow2.asm:asm-util:5.1' 30 | //noinspection GradleDependency 31 | // implementation 'org.ow2.asm:asm-commons:5.1' 32 | // implementation 'com.quinn.hunter:hunter-transform:0.9.0' 33 | // implementation "com.knight.component:component:1.0.1" 34 | // implementation "com.knight:transform:2.0.0" 35 | 36 | kapt "com.google.auto.service:auto-service:1.0" 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 38 | 39 | implementation "com.didiglobal.booster:booster-api:$booster_version" 40 | implementation "com.didiglobal.booster:booster-transform-asm:$booster_version" 41 | 42 | } 43 | 44 | repositories { 45 | google() 46 | maven { 47 | url uri('../repo') 48 | } 49 | jcenter() 50 | mavenCentral() 51 | 52 | // OPTIONAL If you want to use SNAPSHOT version, sonatype repository is required. 53 | // maven { url 'https://oss.sonatype.org/content/repositories/public' } 54 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } 55 | // maven { url "https://plugins.gradle.org/m2/" } 56 | } 57 | 58 | apply from: "${rootDir}/gradle/pkg-src.gradle" 59 | apply plugin: 'maven' 60 | 61 | uploadArchives { 62 | repositories { 63 | mavenDeployer { 64 | pom.groupId = 'com.afirez.spi' 65 | pom.artifactId = 'spi-gradle-plugin' 66 | pom.version = SPI_PLUGIN_VERSION 67 | // repository(url: uri('../repo')) 68 | repository(url: uri('/Users/afirez/studio/android/apps-android-repo')) 69 | } 70 | } 71 | } 72 | 73 | //ext { 74 | // projectGroupId = 'com.afirez.spi' 75 | // projectPackageing = 'jar' 76 | // projectName = 'spi-gradle-plugin' 77 | // projectArtifactId = 'spi-gradle-plugin' 78 | // projectDesc = 'SPI for componentization.' 79 | // 80 | // siteUrl = 'https://github.com/afirez/spi' 81 | // gitUrl = 'https://github.com/afirez/spi.git' 82 | // 83 | // projectVersion = '1.0.1' 84 | // 85 | // developerId = 'afirez' 86 | // developerName = 'afirez' 87 | // developerEmail = 'afirez.io@gmail.com' 88 | // 89 | // licenseName = 'The Apache Software License, Version 2.0' 90 | // licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 91 | // 92 | // bintrayUser = 'afirez' 93 | // bintrayApiKey = '' 94 | // bintrayRepo = 'android' 95 | // bintrayName = 'spi-gradle-plugin' 96 | // bintrayLicenses = ["Apache-2.0"] 97 | //} 98 | // 99 | //apply from: "${rootDir}/gradle/pom-evaluator.gradle" -------------------------------------------------------------------------------- /spi-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: 'kotlin-parcelize' 7 | 8 | // didi booster 9 | apply plugin: 'com.didiglobal.booster' 10 | // gradle plugin version <= 4.1.2 11 | // apply plugin: 'spi' 12 | // or apply plugin: 'com.afirez.spi' 13 | 14 | def isRunningOnTravis = System.getenv("CI") == "true" 15 | 16 | def keystorePropertiesFile = rootProject.file("keystore.properties") 17 | def keystoreProperties = new Properties() 18 | if (keystorePropertiesFile.exists()) { 19 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 20 | } 21 | 22 | android { 23 | compileSdkVersion 28 24 | defaultConfig { 25 | applicationId "com.afirez.spiapp" 26 | minSdkVersion 19 27 | targetSdkVersion 28 28 | versionCode 1 29 | versionName "1.0" 30 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 31 | } 32 | 33 | if (isRunningOnTravis) { 34 | signingConfigs { 35 | release { 36 | keyAlias System.getenv("keyAlias") 37 | keyPassword System.getenv("keyPwd") 38 | storeFile rootProject.file('app-afirez.jks') 39 | storePassword System.getenv("storePwd") 40 | } 41 | } 42 | } else { 43 | signingConfigs { 44 | release { 45 | keyAlias keystoreProperties.containsKey("keyAlias") ? keystoreProperties['keyAlias'] : System.getenv("keyAlias") 46 | keyPassword keystoreProperties.containsKey("keyPwd") ? keystoreProperties['keyPwd'] : System.getenv("keyPwd") 47 | storeFile rootProject.file('app-afirez.jks') 48 | storePassword keystoreProperties.containsKey("storePwd") ? keystoreProperties['storePwd'] : System.getenv("storePwd") 49 | } 50 | } 51 | } 52 | 53 | 54 | buildTypes { 55 | release { 56 | signingConfig signingConfigs.release 57 | minifyEnabled false 58 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 59 | } 60 | } 61 | 62 | lintOptions { 63 | checkReleaseBuilds false 64 | abortOnError false 65 | } 66 | 67 | compileOptions { 68 | sourceCompatibility JavaVersion.VERSION_1_8 69 | targetCompatibility JavaVersion.VERSION_1_8 70 | } 71 | 72 | } 73 | 74 | dependencies { 75 | implementation fileTree(dir: 'libs', include: ['*.jar']) 76 | implementation "androidx.multidex:multidex:2.0.0" 77 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 78 | implementation 'androidx.appcompat:appcompat:1.0.2' 79 | implementation 'androidx.core:core-ktx:1.0.2' 80 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 81 | 82 | implementation "com.afirez.spi:spi:$SPI_VERSION" 83 | 84 | implementation project(':spi-applike') 85 | 86 | implementation project(':spi-component1') 87 | implementation project(':spi-component2') 88 | 89 | implementation project(':spi-component1-impl') 90 | implementation project(':spi-component2-impl') 91 | 92 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 93 | testImplementation 'junit:junit:4.12' 94 | androidTestImplementation 'androidx.test:runner:1.1.1' 95 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 96 | } 97 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/Utils/TypeUtil.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.Utils 2 | 3 | import org.objectweb.asm.Opcodes 4 | 5 | import java.util.regex.Matcher 6 | import java.util.regex.Pattern 7 | 8 | object TypeUtil { 9 | private val paramsPat = Pattern.compile("(\\[?[BCZSIJFD])|(L[^;]+;)") 10 | 11 | fun removeFirstParam(desc: String): String { 12 | if (desc.startsWith("()")) { 13 | return desc 14 | } 15 | var index = 1 16 | var c = desc[index] 17 | while (c == '[') { 18 | index++ 19 | c = desc[index] 20 | } 21 | if (c == 'L') { 22 | while (desc[index] != ';') { 23 | index++ 24 | } 25 | } 26 | return "(" + desc.substring(index + 1) 27 | } 28 | 29 | fun getParameterCountFromMethodDesc(desc: String): Int { 30 | val beginIndex = desc.indexOf('(') + 1 31 | val endIndex = desc.lastIndexOf(')') 32 | val paramsDesc = desc.substring(beginIndex, endIndex) 33 | if (paramsDesc.isEmpty()) return 0 34 | var count = 0 35 | val matcher = paramsPat.matcher(paramsDesc) 36 | while (matcher.find()) { 37 | count++ 38 | } 39 | return count 40 | } 41 | 42 | fun desc2Name(desc: String): String { 43 | return if (!desc.startsWith("L") && !desc.endsWith(";")) { 44 | desc 45 | } else desc.substring(1, desc.length - 1) 46 | } 47 | 48 | fun descToStatic(access: Int, desc: String, className: String): String { 49 | var desc = desc 50 | if (access and Opcodes.ACC_STATIC == 0) { 51 | desc = "(L" + className.replace('.', '/') + ";" + desc.substring(1) 52 | } 53 | return desc 54 | } 55 | 56 | fun descToNonStatic(desc: String): String { 57 | return "(" + desc.substring(desc.indexOf(';') + 1) 58 | } 59 | 60 | fun parseArray(index: Int, desc: String): Int { 61 | var index = index 62 | while (desc[index] == '[') index++ 63 | if (desc[index] == 'L') { 64 | while (desc[index] != ';') index++ 65 | } 66 | return index 67 | } 68 | 69 | fun parseObject(index: Int, desc: String): Int { 70 | var index = index 71 | while (desc[index] != ';') index++ 72 | return index 73 | } 74 | 75 | fun isStatic(access: Int): Boolean { 76 | return access and Opcodes.ACC_STATIC == Opcodes.ACC_STATIC 77 | } 78 | 79 | fun isAbstract(access: Int): Boolean { 80 | return access and Opcodes.ACC_ABSTRACT == Opcodes.ACC_ABSTRACT 81 | } 82 | 83 | fun isSynthetic(access: Int): Boolean { 84 | return access and Opcodes.ACC_SYNTHETIC == Opcodes.ACC_SYNTHETIC 85 | } 86 | 87 | fun isPrivate(access: Int): Boolean { 88 | return access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE 89 | } 90 | 91 | fun isPublic(access: Int): Boolean { 92 | return access and Opcodes.ACC_PUBLIC == Opcodes.ACC_PUBLIC 93 | } 94 | 95 | fun isProtected(access: Int): Boolean { 96 | return access and Opcodes.ACC_PROTECTED == Opcodes.ACC_PROTECTED 97 | } 98 | 99 | fun resetAccessScope(access: Int, scope: Int): Int { 100 | return access and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED).inv() or scope 101 | } 102 | 103 | fun isInterface(access: Int): Boolean { 104 | return access and Opcodes.ACC_INTERFACE == Opcodes.ACC_INTERFACE 105 | } 106 | 107 | fun isInt(desc: String): Boolean { 108 | return "I" == desc 109 | } 110 | 111 | fun isFinal(access: Int): Boolean { 112 | return access and Opcodes.ACC_FINAL == Opcodes.ACC_FINAL 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/Utils/ASMUtils.kt: -------------------------------------------------------------------------------- 1 | package transform.Utils 2 | 3 | import org.objectweb.asm.Opcodes 4 | import org.objectweb.asm.Type 5 | 6 | import org.objectweb.asm.Opcodes.ACC_PRIVATE 7 | import org.objectweb.asm.Opcodes.ACC_PUBLIC 8 | import org.objectweb.asm.Opcodes.ACC_STATIC 9 | import com.android.build.api.transform.JarInput 10 | import com.android.build.api.transform.DirectoryInput 11 | import com.android.build.api.transform.TransformInput 12 | import com.android.build.gradle.AppExtension 13 | import com.google.common.collect.ImmutableList 14 | import com.google.common.collect.Iterables 15 | import org.gradle.api.Project 16 | import java.io.File 17 | import java.net.MalformedURLException 18 | import java.net.URL 19 | import java.net.URLClassLoader 20 | 21 | 22 | object ASMUtils { 23 | fun isPrivate(access: Int): Boolean { 24 | return access and ACC_PRIVATE != 0 25 | } 26 | 27 | fun isPublic(access: Int): Boolean { 28 | return access and ACC_PUBLIC != 0 29 | } 30 | 31 | fun isStatic(access: Int): Boolean { 32 | return access and ACC_STATIC != 0 33 | } 34 | 35 | fun isFinal(access: Int): Boolean { 36 | return access and Opcodes.ACC_FINAL == Opcodes.ACC_FINAL 37 | } 38 | 39 | fun isInt(desc: String): Boolean { 40 | return "I" == desc 41 | } 42 | 43 | fun convertSignature(name: String, desc: String): String { 44 | val method = Type.getType(desc) 45 | val sb = StringBuilder() 46 | sb.append(method.returnType.className).append(" ").append(name) 47 | sb.append("(") 48 | for (i in 0 until method.argumentTypes.size) { 49 | sb.append(method.argumentTypes[i].className) 50 | if (i != method.argumentTypes.size - 1) { 51 | sb.append(",") 52 | } 53 | } 54 | sb.append(")") 55 | return sb.toString() 56 | } 57 | 58 | fun isSelfFile(className: String): Boolean { 59 | if (className.startsWith("android/") || className.startsWith("kotlin/")) { 60 | return false 61 | } 62 | return true 63 | } 64 | 65 | @Throws(MalformedURLException::class) 66 | fun getClassLoader(inputs: Collection, 67 | referencedInputs: Collection, 68 | project: Project): URLClassLoader { 69 | val urls = ImmutableList.Builder() 70 | val androidJarPath = getAndroidJarPath(project) 71 | val file = File(androidJarPath) 72 | val androidJarURL = file.toURI().toURL() 73 | urls.add(androidJarURL) 74 | for (totalInputs in Iterables.concat(inputs, referencedInputs)) { 75 | for (directoryInput in totalInputs.directoryInputs) { 76 | if (directoryInput.file.isDirectory) { 77 | urls.add(directoryInput.file.toURI().toURL()) 78 | } 79 | } 80 | for (jarInput in totalInputs.jarInputs) { 81 | if (jarInput.file.isFile) { 82 | urls.add(jarInput.file.toURI().toURL()) 83 | } 84 | } 85 | } 86 | val allUrls = urls.build() 87 | val classLoaderUrls = allUrls.toTypedArray() 88 | return URLClassLoader(classLoaderUrls) 89 | } 90 | 91 | /** 92 | * /Users/quinn/Documents/Android/SDK/platforms/android-28/android.jar 93 | */ 94 | private fun getAndroidJarPath(project: Project): String { 95 | val appExtension = project.properties["android"] as AppExtension? 96 | var sdkDirectory = appExtension!!.sdkDirectory.absolutePath 97 | val compileSdkVersion = appExtension.compileSdkVersion 98 | sdkDirectory = sdkDirectory + File.separator + "platforms" + File.separator 99 | return sdkDirectory + compileSdkVersion + File.separator + "android.jar" 100 | } 101 | } -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/asm/CodeWeaver.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.asm 2 | 3 | import com.knight.transform.IPlugin 4 | import com.knight.transform.Interceptor.ClassVisitorChain 5 | import com.knight.transform.Interceptor.ClassVisitorParams 6 | import com.knight.transform.weave.ExtendClassWriter 7 | import org.apache.commons.io.FileUtils 8 | import org.apache.commons.io.IOUtils 9 | import org.objectweb.asm.ClassReader 10 | import org.objectweb.asm.ClassVisitor 11 | import org.objectweb.asm.ClassWriter 12 | import java.io.* 13 | import java.lang.Exception 14 | import java.nio.file.Files 15 | import java.nio.file.attribute.FileTime 16 | import java.util.zip.CRC32 17 | import java.util.zip.ZipEntry 18 | import java.util.zip.ZipFile 19 | import java.util.zip.ZipOutputStream 20 | 21 | 22 | class CodeWeaver(iPlugin: IPlugin) : IWeaver(iPlugin = iPlugin) { 23 | 24 | 25 | private val ZERO = FileTime.fromMillis(0)!! 26 | 27 | fun weaveClassToByteArray(intpuStream: InputStream): ByteArray { 28 | val classReader = ClassReader(intpuStream) 29 | val classWriter = ExtendClassWriter(classloader, classReader, 30 | ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS) 31 | val classVisitor = iPlugin.createWeaveClassVisitor(classWriter) 32 | try { 33 | classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) 34 | } catch (e: Exception) { 35 | println("Exception occurred when visit code \n " + e.printStackTrace()) 36 | 37 | } 38 | return classWriter.toByteArray() 39 | } 40 | 41 | fun weaveClassToByteChain(intpuStream: InputStream): ByteArray { 42 | iPlugin.getWeaveClassVisitorInterceptor()?.let { 43 | val chain = ClassVisitorChain(0, it, ClassVisitorParams(intpuStream.readBytes(), classloader)) 44 | return chain.transform() 45 | } ?: return weaveClassToByteArray(intpuStream) 46 | } 47 | 48 | override fun weaveJar(inputJar: File, outputJar: File) { 49 | val inputZip = ZipFile(inputJar) 50 | val outputZipStream = ZipOutputStream(BufferedOutputStream(Files.newOutputStream(outputJar.toPath()))) 51 | inputZip.entries().toList().forEach { entry -> 52 | val originalFile = BufferedInputStream(inputZip.getInputStream(entry)) 53 | val outEntry = ZipEntry(entry.name) 54 | val newEntryContent: ByteArray 55 | if (!isWeaveableClass(outEntry.name.replace("/", "."))) { 56 | newEntryContent = IOUtils.toByteArray(originalFile) 57 | } else { 58 | newEntryContent = weaveClassToByteChain(originalFile) 59 | } 60 | val crc32 = CRC32() 61 | crc32.update(newEntryContent) 62 | outEntry.crc = crc32.value 63 | outEntry.method = (ZipEntry.STORED) 64 | outEntry.size = (newEntryContent.size.toLong()) 65 | outEntry.compressedSize = (newEntryContent.size.toLong()) 66 | outEntry.lastAccessTime = (ZERO) 67 | outEntry.lastModifiedTime = (ZERO) 68 | outEntry.creationTime = (ZERO) 69 | outputZipStream.putNextEntry(outEntry) 70 | outputZipStream.write(newEntryContent) 71 | outputZipStream.closeEntry() 72 | } 73 | outputZipStream.flush() 74 | outputZipStream.close() 75 | } 76 | 77 | override fun weaveFile(inputFile: File, outputFile: File, inputDir: String) { 78 | var inputBaseDir = inputDir 79 | if (!inputBaseDir.endsWith("/")) inputBaseDir += "/" 80 | if (isWeaveableClass(inputFile.absolutePath.replace(inputBaseDir, "").replace("/", "."))) { 81 | FileUtils.touch(outputFile) 82 | val inputStream = FileInputStream(inputFile) 83 | val bytes = weaveClassToByteChain(inputStream) 84 | val fos = FileOutputStream(outputFile) 85 | fos.write(bytes) 86 | fos.close() 87 | inputStream.close() 88 | } else { 89 | if (inputFile.isFile) { 90 | FileUtils.touch(outputFile) 91 | FileUtils.copyFile(inputFile, outputFile) 92 | } 93 | } 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/KnightPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform 2 | 3 | import com.android.build.api.transform.Transform 4 | import com.android.build.gradle.* 5 | import com.android.build.gradle.api.BaseVariant 6 | import com.android.builder.model.AndroidProject 7 | import com.knight.transform.Interceptor.IClassVisitorInterceptor 8 | import com.knight.transform.Utils.Timer 9 | import org.gradle.api.* 10 | import transform.KnightTransform 11 | import transform.task.OutPutMappingTask 12 | 13 | abstract class KnightPlugin> : Plugin, IPlugin { 14 | 15 | protected lateinit var context: C 16 | protected lateinit var extension: E 17 | 18 | protected lateinit var project: Project 19 | protected lateinit var android: TestedExtension 20 | protected abstract val isNeedPrintMapAndTaskCostTime: Boolean 21 | 22 | 23 | abstract fun getContext(project: Project, extension: E, android: TestedExtension): C 24 | abstract fun createExtensions(): E 25 | lateinit var transform: KnightTransform 26 | 27 | override fun apply(project: Project) { 28 | if (!project.plugins.hasPlugin(AppPlugin::class.java) && !project.plugins.hasPlugin(LibraryPlugin::class.java)) { 29 | throw GradleException("'com.android.application' or 'com.android.library' plugin required!") 30 | } 31 | 32 | val aClass = if (project.plugins.hasPlugin(AppPlugin::class.java)) 33 | AppExtension::class.java 34 | else LibraryExtension::class.java 35 | this.project = project 36 | android = project.extensions.getByType(aClass) 37 | extension = createExtensions() 38 | context = getContext(project, extension, android) 39 | if (android is AppExtension) { 40 | (android as AppExtension).applicationVariants.all { 41 | createTask(it, context) 42 | } 43 | } else if (android is LibraryExtension) { 44 | (android as LibraryExtension).libraryVariants.all { 45 | createTask(it, context) 46 | } 47 | } 48 | transform = KnightTransform(context, this, ::getTransformName) 49 | android.registerTransform(transform) 50 | } 51 | 52 | abstract fun getTransformName(): String 53 | 54 | 55 | override fun getScanClassVisitorInterceptor(): List? { 56 | return null 57 | } 58 | 59 | override fun getWeaveClassVisitorInterceptor(): List? { 60 | return null 61 | } 62 | 63 | 64 | open fun createTask(variant: BaseVariant, context: BaseContext<*>) { 65 | createWriteMappingTask(variant, context) 66 | } 67 | 68 | private fun createWriteMappingTask(variant: BaseVariant, context: BaseContext<*>) { 69 | if (!isNeedPrintMapAndTaskCostTime) { 70 | return 71 | } 72 | val mappingTaskName = "${transform.name}outputMappingFor${variant.name.capitalize()}" 73 | val myTask = project.tasks.getByName("transformClassesWith${transform.name}For${variant.name.capitalize()}") 74 | myTask.apply { 75 | doFirst { 76 | Timer.start(name) 77 | } 78 | 79 | doLast { 80 | Timer.stop(name) 81 | } 82 | } 83 | val outputMappingTask = project.tasks.create(mappingTaskName, OutPutMappingTask::class.java) 84 | 85 | outputMappingTask.apply { 86 | this as OutPutMappingTask 87 | classes.set(context.weavedClassMap) 88 | variantName.set(transform.name) 89 | outputMappingFile.set(com.android.utils.FileUtils.join(project.buildDir, AndroidProject.FD_OUTPUTS, "mapping", 90 | transform.name, variant.name, "${transform.name}Mapping.txt")) 91 | 92 | doFirst { 93 | Timer.start(name) 94 | } 95 | 96 | doLast { 97 | Timer.stop(name) 98 | } 99 | } 100 | 101 | myTask.finalizedBy(outputMappingTask) 102 | outputMappingTask.onlyIf { myTask.didWork } 103 | outputMappingTask.dependsOn(myTask) 104 | } 105 | } -------------------------------------------------------------------------------- /spi-plugin-booster/src/main/java/com/afirez/spi/SpiSupervisor.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi 2 | 3 | 4 | import com.didiglobal.booster.transform.asm.ClassTransformer 5 | import com.didiglobal.booster.transform.asm.asClassNode 6 | import com.didiglobal.booster.transform.asm.className 7 | import com.didiglobal.booster.transform.util.AbstractSupervisor 8 | import org.objectweb.asm.tree.AnnotationNode 9 | 10 | /** 11 | * 收集 spi service 12 | */ 13 | class SpiSupervisor( 14 | // private val prefix: String, 15 | action: (HashMap>) -> Unit 16 | ) : AbstractSupervisor>>(action) { 17 | override fun accept(name: String): Boolean { 18 | return name.endsWith(".class", true) 19 | } 20 | 21 | override fun collect(name: String, data: () -> ByteArray) { 22 | val klass = data().asClassNode() 23 | val className = klass.name 24 | // val superName = klass.superName 25 | val interfaces = klass.interfaces 26 | 27 | val annotations = mutableListOf() 28 | if (klass.invisibleAnnotations != null) { 29 | annotations += klass.invisibleAnnotations 30 | } 31 | if (klass.visibleAnnotations != null) { 32 | annotations += klass.visibleAnnotations 33 | } 34 | annotations.forEach { 35 | // println("Annotation ${it.desc}") 36 | when (it.desc) { 37 | "L${SpiTransformer.spiAnotattion};" -> { 38 | /** 39 | * {api -> { path -> impl}} or {api -> { path -> impl}} 40 | */ 41 | var extensionsMap = HashMap>() 42 | var anotattionName: Any? = null 43 | var anotattionValue: Any? = null 44 | if (!it.values.isNullOrEmpty()) { 45 | anotattionName = it.values[0] 46 | anotattionValue = it.values[1] 47 | } 48 | println("@SPI ( $anotattionName = '$anotattionValue' ) class $className:") 49 | 50 | if (interfaces?.size == 0) { 51 | 52 | val type = className 53 | val extension = className 54 | val path = if (anotattionValue == null) { 55 | className.replace("/", ".") 56 | } else { 57 | "$anotattionValue" 58 | } 59 | 60 | // var extensionMap = context.extensionsMap[type] 61 | var extensionMap = extensionsMap[type] 62 | if (extensionMap == null) { 63 | extensionMap = HashMap() 64 | extensionsMap[type] = extensionMap 65 | // println(" new Map:[ $type -> $extensionMap ]") 66 | } 67 | extensionMap[path] = extension 68 | action(extensionsMap) 69 | return 70 | } 71 | 72 | interfaces?.forEach { 73 | val type = it 74 | val extension = className 75 | println(" SPI: [ $type -> $extension ]") 76 | val path = if (anotattionValue == null) { 77 | className.replace("/", ".") 78 | } else { 79 | "$anotattionValue" 80 | } 81 | // var extensionMap = context.extensionsMap[type] 82 | var extensionMap = extensionsMap[type] 83 | if (extensionMap == null) { 84 | extensionMap = HashMap() 85 | // context.extensionsMap[type] = extensionMap!! 86 | extensionsMap[type] = extensionMap!! 87 | // println("Spi: new Map:[ $service -> $map ]") 88 | } 89 | extensionMap!![path] = extension 90 | 91 | action(extensionsMap) 92 | } 93 | 94 | println(" --> end class $className") 95 | } 96 | 97 | } 98 | } 99 | 100 | 101 | // action(HashMap>()) 102 | } 103 | 104 | 105 | } -------------------------------------------------------------------------------- /spi-app/src/main/java/com/afirez/spi/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.app 2 | 3 | import android.os.Bundle 4 | import android.widget.TextView 5 | import android.widget.Toast 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.afirez.spi.ExtensionLoader 8 | import com.afirez.spi.SPI 9 | import com.afirez.spi.component1.SpiRouter1Provider 10 | import com.afirez.spi.component2.SpiRouter2Provider 11 | 12 | /** 13 | * https://github.com/afirez/spi 14 | */ 15 | @SPI(path = "/spi/activity/main") 16 | class MainActivity : AppCompatActivity() { 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_main) 21 | val tv01AppLike = findViewById(R.id.tv01AppLike) 22 | val tv02Activity = findViewById(R.id.tv02Activity) 23 | val tv03Fragment = findViewById(R.id.tv03Fragment) 24 | val tv04Provider = findViewById(R.id.tv04Provider) 25 | val tv05RouterProvider = findViewById(R.id.tv05RouterProvider) 26 | val tv05More = findViewById(R.id.tv05More) 27 | tv01AppLike.setOnClickListener { 28 | /** 29 | * @see com.afirez.applike.AppLike 30 | */ 31 | Toast.makeText(this@MainActivity, "@see com.afirez.applike.AppLike", Toast.LENGTH_SHORT).show() 32 | } 33 | 34 | tv02Activity.setOnClickListener { 35 | /** 36 | * @see com.afirez.spi.component1.impl.SpiActivity 37 | */ 38 | ExtensionLoader.getInstance().loadExtension(SpiRouter1Provider::class.java).navSpiActivity() 39 | } 40 | 41 | tv03Fragment.setOnClickListener { 42 | /** 43 | * @see com.afirez.spi.component2.impl.SpiFragment 44 | */ 45 | ExtensionLoader.getInstance().loadExtension(SpiRouter1Provider::class.java).navSpiFragmentActivity() 46 | } 47 | 48 | tv04Provider.setOnClickListener { 49 | /** 50 | * @see com.afirez.spi.component1.SpiJavaProvider 51 | */ 52 | val spiJavaProvider = 53 | ExtensionLoader.getInstance().loadExtension(SpiRouter1Provider::class.java).spiJavaProvider() 54 | if (spiJavaProvider != null) { 55 | val helloJava = spiJavaProvider.helloJava() 56 | Toast.makeText(this@MainActivity, "spi $helloJava", Toast.LENGTH_SHORT).show() 57 | } 58 | /** 59 | * @see com.afirez.spi.component2.SpiKotlinProvider 60 | */ 61 | val spiKotlinProvider = 62 | ExtensionLoader.getInstance().loadExtension(SpiRouter2Provider::class.java).spiKotlinProvider() 63 | if (spiJavaProvider != null) { 64 | val helloKotlin = spiKotlinProvider.helloKotlin() 65 | tv04Provider.postDelayed({ 66 | Toast.makeText(this@MainActivity, "spi $helloKotlin", Toast.LENGTH_SHORT).show() 67 | }, 1000) 68 | } 69 | } 70 | 71 | tv05RouterProvider.setOnClickListener { 72 | /** 73 | * @see com.afirez.spi.component1.SpiRouter1Provider 74 | */ 75 | Toast.makeText( 76 | this@MainActivity, 77 | "@see com.afirez.spi.component1.SpiRouter1Provider", 78 | Toast.LENGTH_SHORT 79 | ).show() 80 | 81 | 82 | /** 83 | * @see com.afirez.spi.component2.SpiRouter2Provider 84 | */ 85 | tv05RouterProvider.postDelayed({ 86 | Toast.makeText( 87 | this@MainActivity, 88 | "@see com.afirez.spi.component2.SpiRouter2Provider", 89 | Toast.LENGTH_SHORT 90 | ).show() 91 | }, 1000) 92 | } 93 | 94 | tv05More.setOnClickListener { 95 | Toast.makeText( 96 | this@MainActivity, 97 | "1 、 SPI 是一个方便接口聚合与分发的工具,很多接口扩展的场景都需要用到, 让我们的代码遵循开闭原则 !例如 AppLike 实现 Application 生命周期分发, 请参考 AppLike 和 Provider 等用例自行扩展。\n" 98 | + "2、 SPI 本身已有一个路由表,可以为接口或类(类支持 Activity 和 Fragment )的实现类指定 path 路径 , 方便我们拿到指定 path 的 实现类 。 如果你想自己实现组件化的路由, 完全可以基于 spi 实现 \n" 99 | + "3、 SPI 聚焦于接口发现与注册和路由表实现, 仅仅基于 SPI 实现 Android 组件化, 模块解偶很彻底, 但是缺少路由的一些功能,后续考虑开源一个路由项目,敬请期待!", 100 | Toast.LENGTH_LONG 101 | ).show() 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /spi-applike/src/main/java/com/afirez/applike/AppDelegate.java: -------------------------------------------------------------------------------- 1 | package com.afirez.applike; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.os.Bundle; 7 | import com.afirez.spi.ExtensionLoader; 8 | 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | /** 13 | * https://github.com/afirez/spi 14 | */ 15 | public class AppDelegate { 16 | 17 | 18 | public static AppDelegate getInstance() { 19 | return SingletonHolder.INSTANCE; 20 | } 21 | 22 | private static class SingletonHolder { 23 | static AppDelegate INSTANCE = new AppDelegate(); 24 | } 25 | 26 | private Application app; 27 | 28 | private Activity topActivity; 29 | 30 | public Application app() { 31 | return app; 32 | } 33 | 34 | public Activity topActivity() { 35 | return topActivity; 36 | } 37 | 38 | public Context topActivityOrApp() { 39 | return topActivity == null ? app : topActivity; 40 | } 41 | 42 | public void attachBaseContext(Application app, Context base) { 43 | this.app = app; 44 | Map appLikeMap = ExtensionLoader.getInstance().loadExtensions(AppLike.class); 45 | if (appLikeMap == null) { 46 | return; 47 | } 48 | Set> entries = appLikeMap.entrySet(); 49 | 50 | for (Map.Entry entry : entries) { 51 | if (entry == null) { 52 | continue; 53 | } 54 | 55 | AppLike appLike = entry.getValue(); 56 | if (appLike == null) { 57 | continue; 58 | } 59 | 60 | appLike.attachBaseContext(app, base); 61 | } 62 | } 63 | 64 | public void onCreate(Application app) { 65 | this.app = app; 66 | app.registerActivityLifecycleCallbacks(activityLifecycleCallbacks); 67 | 68 | Map appLikeMap = ExtensionLoader.getInstance().loadExtensions(AppLike.class); 69 | if (appLikeMap == null) { 70 | return; 71 | } 72 | 73 | Set> entries = appLikeMap.entrySet(); 74 | 75 | for (Map.Entry entry : entries) { 76 | if (entry == null) { 77 | continue; 78 | } 79 | 80 | AppLike appLike = entry.getValue(); 81 | if (appLike == null) { 82 | continue; 83 | } 84 | 85 | appLike.onCreate(app); 86 | } 87 | } 88 | 89 | 90 | public void onTerminate(Application app) { 91 | Map appLikeMap = ExtensionLoader.getInstance().loadExtensions(AppLike.class); 92 | if (appLikeMap == null) { 93 | return; 94 | } 95 | Set> entries = appLikeMap.entrySet(); 96 | 97 | for (Map.Entry entry : entries) { 98 | if (entry == null) { 99 | continue; 100 | } 101 | 102 | AppLike appLike = entry.getValue(); 103 | if (appLike == null) { 104 | continue; 105 | } 106 | 107 | appLike.onTerminate(app); 108 | } 109 | } 110 | 111 | 112 | private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = 113 | new Application.ActivityLifecycleCallbacks() { 114 | @Override 115 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 116 | 117 | } 118 | 119 | @Override 120 | public void onActivityStarted(Activity activity) { 121 | 122 | } 123 | 124 | @Override 125 | public void onActivityResumed(Activity activity) { 126 | topActivity = activity; 127 | } 128 | 129 | @Override 130 | public void onActivityPaused(Activity activity) { 131 | topActivity = null; 132 | } 133 | 134 | @Override 135 | public void onActivityStopped(Activity activity) { 136 | 137 | } 138 | 139 | @Override 140 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 141 | 142 | } 143 | 144 | @Override 145 | public void onActivityDestroyed(Activity activity) { 146 | 147 | } 148 | }; 149 | 150 | } 151 | -------------------------------------------------------------------------------- /spi-plugin-booster/src/main/java/com/afirez/spi/SpiTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi 2 | 3 | import com.didiglobal.booster.transform.TransformContext 4 | import com.didiglobal.booster.transform.asm.ClassTransformer 5 | import com.didiglobal.booster.transform.asm.defaultInit 6 | import com.didiglobal.booster.transform.util.NameCollector 7 | import com.google.auto.service.AutoService 8 | import org.objectweb.asm.Opcodes 9 | import org.objectweb.asm.Type 10 | import org.objectweb.asm.tree.* 11 | 12 | @AutoService(ClassTransformer::class) 13 | class SpiTransformer : ClassTransformer { 14 | 15 | val services = HashMap>() 16 | 17 | override fun onPreTransform(context: TransformContext) { 18 | context.registerCollector(SpiSupervisor { 19 | it.forEach { api, newImplMap -> 20 | val implMap = services.getOrDefault(api, HashMap()) 21 | implMap += newImplMap 22 | services.put(api, implMap) 23 | } 24 | }) 25 | 26 | // 一旦有 JAR/DIR 中包含 ${SERVICE_REGISTRY} 这个文件 27 | // 则强制走 transform 的流程,无论是全量还是增量 28 | context.registerCollector(NameCollector(SERVICE_REGISTRY)) 29 | } 30 | 31 | override fun transform(context: TransformContext, klass: ClassNode) = klass.apply { 32 | val name = klass.name 33 | when (name) { 34 | SERVICE_REGISTRY -> { 35 | // println("services ${services.size}") 36 | val init = methods.find { 37 | it.name == "" && it.desc == "()V" 38 | } ?: defaultInit.apply { 39 | methods.add(this) 40 | } 41 | 42 | val pre = InsnList() 43 | val post = InsnList() 44 | var beforeReturn = true 45 | val oldInstructions = init.instructions 46 | val size = oldInstructions.size() 47 | for (i in 0 until size) { 48 | val inst = oldInstructions.get(i) 49 | if (beforeReturn) { 50 | // if (inst is InsnNode) { 51 | when (inst.opcode) { 52 | Opcodes.IRETURN, 53 | Opcodes.FRETURN, 54 | Opcodes.ARETURN, 55 | Opcodes.LRETURN, 56 | Opcodes.DRETURN, 57 | Opcodes.RETURN -> { 58 | beforeReturn = false 59 | } 60 | else -> {} 61 | } 62 | // } 63 | } 64 | if (beforeReturn) { 65 | pre.add(inst) 66 | } else { 67 | post.add(inst) 68 | } 69 | } 70 | 71 | val newInsnList = InsnList().apply { 72 | add(pre) 73 | 74 | services.forEach { api, implMap -> 75 | implMap.forEach { path, impl -> 76 | add(VarInsnNode(Opcodes.ALOAD, 0)) 77 | // class of api 78 | add(LdcInsnNode(Type.getObjectType(api))) 79 | // class of implementation 80 | add(LdcInsnNode(Type.getObjectType(impl))) 81 | // path to class of api 82 | add(LdcInsnNode(path)) 83 | // ServiceRegistry.register(interface, path, implementation) 84 | add( 85 | MethodInsnNode( 86 | Opcodes.INVOKEVIRTUAL, 87 | SERVICE_REGISTRY, 88 | addService, 89 | "(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)V", 90 | ) 91 | ) 92 | } 93 | } 94 | 95 | add(post) 96 | // add(InsnNode(Opcodes.RETURN)) 97 | } 98 | init.instructions = newInsnList 99 | } 100 | } 101 | } 102 | 103 | companion object { 104 | val spiAnotattion: String = "com/afirez/spi/SPI" 105 | // private const val SERVICE_REGISTRY = "com/your/package/ServiceRegistry.class" 106 | // private const 107 | 108 | val SERVICE_REGISTRY = "com/afirez/spi/ExtensionLoader" 109 | val addService: String = "addExtension" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /spi-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_CN.md: -------------------------------------------------------------------------------- 1 | # 你可能需要 SPI 了 2 | 3 | ![](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f4aab6d3f?w=1906&h=1408&f=png&s=261001) 4 | 5 | ## 为什么会开源这个 SPI 项目 ? 6 | 7 | 在项目组件化的过程中,曾通过 AndoridManifest.xml 注册的方式实现过 Application 生命周期的组件化,类似 Glide 中解析 AndoridManifest.xml 发现 GlideModule 的机制,有点繁琐。而后了解到 Google 的 AutoService,通过注解加 Java 原生 SPI,实现 Appcation 生命周期的组件化,好像刚刚好。SPI 可以没有,没有 SPI,可以用其他方案,比如说路由。首先,一般路由基本在提供路由该提供的功能,路由发现和注册机制局限在路由框架内部,不会提供 SPI,如果用路由实现,每添加一个需要 Application 生命周期的组件都需要通过在 Application 中添加代码,不是那么好维护。SPI 也可以到处都是,随着项目组件化后,发现很多地方都需要用到 SPI 机制,AutoService 加原生 SPI 机制,除了有反射还有IO,在接口非常多的时候,比较耗时。而 SPI 机制是遵循开闭原则的,这个事情也是在脑海里留下了不可描述的印象......于是慢慢的,才有了这个 SPI 项目。 8 | 9 | ## 在项目中引入 [SPI](https://github.com/afirez/spi) 10 | 11 | 添加 **spi-gradle-plugin** 插件到你的项目 12 | 13 | ``` 14 | 15 | buildscript { 16 | ext { 17 | kotlin_version = '1.5.31' 18 | booster_version = '4.13.0' 19 | } 20 | 21 | repositories { 22 | jcenter() 23 | 24 | // add maven repository for spi-plugin at build.gradle file of root project 25 | maven { url "https://raw.githubusercontent.com/afirez/spi/master/repo/" } 26 | } 27 | 28 | dependencies { 29 | classpath 'com.afirez.spi:spi-gradle-plugin:3.0.0' 30 | // ① 31 | classpath "com.didiglobal.booster:booster-gradle-plugin:$booster_version" 32 | // ② figure out the features you really need, then choose the right module for integration 33 | // ② 弄清楚真正需要的特性,然后从下面的模块列表中选择正确的模块进行集成 34 | // classpath "com.didiglobal.booster:booster-task-all:$booster_version" 35 | // classpath "com.didiglobal.booster:booster-transform-all:$booster_version" 36 | 37 | // gradle plugin version <= 4.1.2 38 | // classpath 'com.afirez.spi:spi-gradle-plugin:2.0.0' 39 | 40 | // gradle plugin version <= 3.6.4 41 | // classpath 'com.afirez.spi:spi-gradle-plugin:1.0.1' 42 | } 43 | } 44 | 45 | // in module build.gradle 46 | apply plugin: 'com.android.application' 47 | 48 | // didi booster 49 | apply plugin: 'com.didiglobal.booster' // ③ 50 | 51 | // gradle plugin version <= 4.1.2 52 | // apply plugin: 'spi' 53 | // or apply plugin: 'com.afirez.spi' 54 | ``` 55 | 56 | 添加 **spi** 到需要的子模块 57 | 58 | ``` 59 | allprojects { 60 | repositories { 61 | ... 62 | 63 | mavenCentral() 64 | google() 65 | // jcenter() 66 | 67 | // didi booster 68 | maven { url 'https://oss.sonatype.org/content/repositories/public/' } 69 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } 70 | 71 | // add maven repository for spi at build.gradle file of root project 72 | maven { url "https://raw.githubusercontent.com/afirez/spi/master/repo/" } 73 | 74 | ... 75 | } 76 | } 77 | ``` 78 | 79 | ``` 80 | 81 | implementation "com.afirez.spi:spi:1.0.1" 82 | ``` 83 | 84 | ## 如何使用 SPI 85 | 86 | ### 1、从 SPI AppLike 谈起 87 | 88 | ![](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f4c0b6821?w=1252&h=946&f=png&s=193865) 89 | 90 | 实现 AppLike 接口,并添加 @SPI 注解,该实现者 App 将会在编译时被发现和注册到 ExtensionLoader 中,如下图所示: 91 | 92 | ![](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f4c026017?w=1632&h=1272&f=png&s=299116) 93 | 94 | 然后, AppDelegate 可以通过 ExtensionLoader 拿到所有 AppLike 的实现者并分发 Application 的生命周期。如下图所示: 95 | 96 | ![](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f51b66ed3?w=2018&h=1774&f=png&s=329836) 97 | 98 | 最后,记得在项目的 App 中调用 AppDelegate 相应方法(attachBaseContext,onCreate,onTerminate 等方法),这样其他模块便有了 Application 的生命周期。 99 | 100 | ![](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f55447fa2?w=1374&h=1048&f=png&s=234748) 101 | 102 | ### 2、SPI Actiivty 103 | 104 | ![](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f51c034a6?w=2040&h=1192&f=png&s=333383) 105 | 106 | 被 @SPI 注解过的 Activity ,会在编译期被发现并注册到 ExtensionLoader,然后可以通过 ExtensionLoader 获取指定路径 path 的 Activity 类。(如果 @SPI 中没有路径 path 的值,路径 path 的值将会是被注解类的包名加类名) 107 | 108 | ### 3、SPI Fragment 109 | 110 | ![](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f7f338ee5?w=1932&h=1666&f=png&s=400667) 111 | 112 | 被 @SPI 注解过的 Fragment,会在编译期被发现并注册到 ExtensionLoader,然后可以通过 ExtensionLoader 获取指定路径 path 的 Fragment 类。(如果 @SPI 中没有路径 path 的值,路径 path 的值将会是被注解类的包名加类名) 113 | 114 | ### 4、SPI Provider 115 | 116 | ![](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f89aab5fc?w=1930&h=1012&f=png&s=246460) 117 | 118 | 被 @SPI 注解过的 SpiKotlinProviderImpl,同样会在编译期被发现并注册到 ExtensionLoader,然后可以通过 ExtensionLoader 获取指定路径 path 的 SpiKotlinProvider 实现类。(如果 @SPI 中没有路径 path 的值,路径 path 的值将会是被注解类的包名加类名) 119 | 120 | ### 5、用 SPI 搞更多事情 121 | 122 | SPI 是一个方便接口聚合与分发的工具,很多接口扩展的场景都需要用到,让我们的代码遵循开闭原则!例如 AppLike 实现 Application 生命周期组件化,请参考 AppLike 等用例自行扩展。 123 | 124 | SPI 其实本身已有一个路由表,可以为接口或类(类支持 Activity 和 Fragment)的实现类指定 path 路径,方便我们拿到指定 path 的 实现类。如果你想自己实现组件化的路由,完全可以基于 spi 实现 125 | 126 | SPI 聚焦于接口发现与注册和路由表,仅仅基于 SPI 实现 Android 组件化,模块解偶很彻底,但是缺少路由的一些功能,后续考虑开源一个路由项目,敬请期待! 127 | 128 | 去 [spi](https://github.com/afirez/spi) github 下载源码试试, 了解更多细节。 129 | 130 | ## 感谢 131 | 132 | - [ASM](https://asm.ow2.io/) 133 | - [hunter](https://github.com/Leaking/Hunter) 134 | - [KnightTransform](https://github.com/kakayang2011/KnightTransform) 135 | - [booster](https://github.com/didi/booster) 136 | 137 | ![afirez](https://user-gold-cdn.xitu.io/2019/6/1/16b13c2f917705f9?w=200&h=200&f=jpeg&s=20853) -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/transform/Transform.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.transform 2 | 3 | import com.android.build.api.transform.Format 4 | import com.android.build.api.transform.Status 5 | import com.android.build.api.transform.TransformInvocation 6 | import com.android.ide.common.internal.WaitableExecutor 7 | import com.knight.transform.BaseContext 8 | import com.knight.transform.asm.IWeaver 9 | import org.apache.commons.io.FileUtils 10 | import java.io.File 11 | 12 | 13 | class Transform(val myContext: BaseContext<*>, val weaver: IWeaver) { 14 | val waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool() 15 | 16 | fun transform(transform: TransformInvocation) { 17 | transform.apply { 18 | if (!isIncremental) outputProvider.deleteAll() 19 | inputs.forEach { 20 | it.jarInputs.forEach { jarInput -> 21 | val status = jarInput.status 22 | val dest = outputProvider.getContentLocation( 23 | jarInput.file.absolutePath, 24 | jarInput.contentTypes, 25 | jarInput.scopes, 26 | Format.JAR) 27 | if (isIncremental) { 28 | when (status) { 29 | Status.ADDED, Status.CHANGED -> { 30 | transformJar(jarInput.file, dest, myContext.extension.isScanJar) 31 | } 32 | Status.REMOVED -> { 33 | if (dest.exists()) { 34 | FileUtils.forceDelete(dest) 35 | } 36 | } 37 | else -> { 38 | } 39 | } 40 | } else { 41 | transformJar(jarInput.file, dest, myContext.extension.isScanJar) 42 | } 43 | } 44 | 45 | it.directoryInputs.forEach { directoryInput -> 46 | val dest = outputProvider.getContentLocation( 47 | directoryInput.name, 48 | directoryInput.contentTypes, 49 | directoryInput.scopes, 50 | Format.DIRECTORY) 51 | FileUtils.forceMkdir(dest) 52 | if (isIncremental) { 53 | val srcDirPath = directoryInput.file.absolutePath 54 | val destDirPath = dest.absolutePath 55 | directoryInput.changedFiles.forEach { inputFile, status -> 56 | val destFilePath = inputFile.absolutePath.replace(srcDirPath, destDirPath) 57 | val destFile = File(destFilePath) 58 | when (status) { 59 | Status.ADDED, Status.CHANGED -> { 60 | FileUtils.touch(destFile) 61 | transformSingleFile(inputFile, destFile, srcDirPath) 62 | } 63 | Status.REMOVED -> { 64 | if (destFile.exists()) { 65 | destFile.delete() 66 | } 67 | } 68 | else -> { 69 | } 70 | } 71 | } 72 | } else { 73 | transformDir(directoryInput.file, dest) 74 | } 75 | } 76 | } 77 | } 78 | waitableExecutor.waitForTasksWithQuickFail(true) 79 | } 80 | 81 | private fun transformJar(srcJar: File, destJar: File, isNeedScan: Boolean) { 82 | waitableExecutor.execute { 83 | if (isNeedScan) { 84 | weaver.weaveJar(srcJar, destJar) 85 | } else { 86 | FileUtils.copyFile(srcJar, destJar) 87 | } 88 | } 89 | } 90 | 91 | private fun transformSingleFile(inputFile: File, outputFile: File, srcDir: String) { 92 | waitableExecutor.execute { 93 | weaver.weaveFile(inputFile, outputFile, srcDir) 94 | } 95 | } 96 | 97 | private fun transformDir(inputFile: File, outputFile: File) { 98 | val inputDirPath = inputFile.absolutePath 99 | val outputDirPath = outputFile.absolutePath 100 | if (inputFile.isDirectory) { 101 | com.android.utils.FileUtils.getAllFiles(inputFile).forEach { 102 | waitableExecutor.execute { 103 | try { 104 | val filePath = it.absolutePath 105 | val outputFile = File(filePath.replace(inputDirPath, outputDirPath)) 106 | weaver.weaveFile(it, outputFile, inputDirPath) 107 | } catch (e: Exception) { 108 | println("error: ${e}") 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /knight-transform/src/main/kotlin/com/knight/transform/weave/ExtendClassWriter.kt: -------------------------------------------------------------------------------- 1 | package com.knight.transform.weave 2 | 3 | import org.objectweb.asm.ClassReader 4 | import org.objectweb.asm.ClassWriter 5 | import org.objectweb.asm.Opcodes 6 | import java.io.IOException 7 | 8 | class ExtendClassWriter(private val urlClassLoader: ClassLoader?, classReader: ClassReader? = null, flags: Int) : ClassWriter(classReader, flags) { 9 | 10 | override fun getCommonSuperClass(type1: String?, type2: String?): String? { 11 | if (urlClassLoader == null) { 12 | super.getCommonSuperClass(type1, type2) 13 | } 14 | if (type1 == null || type1 == OBJECT || type2 == null || type2 == OBJECT) { 15 | return OBJECT 16 | } 17 | if (type1 == type2) { 18 | return type1 19 | } 20 | val type1ClassReader = getClassReader(type1) 21 | val type2ClassReader = getClassReader(type2) 22 | if (type1ClassReader == null || type2ClassReader == null) { 23 | return OBJECT 24 | } 25 | if (isInterface(type1ClassReader)) { 26 | var interfaceName: String = type1 27 | if (isImplements(interfaceName, type2ClassReader)) { 28 | return interfaceName 29 | } 30 | if (isInterface(type2ClassReader)) { 31 | interfaceName = type2 32 | if (isImplements(interfaceName, type1ClassReader)) { 33 | return interfaceName 34 | } 35 | } 36 | return OBJECT 37 | } 38 | if (isInterface(type2ClassReader)) { 39 | return if (isImplements(type2, type1ClassReader)) { 40 | type2 41 | } else OBJECT 42 | } 43 | val superClassNames = HashSet() 44 | superClassNames.add(type1) 45 | superClassNames.add(type2) 46 | var type1SuperClassName = type1ClassReader.superName 47 | if (!superClassNames.add(type1SuperClassName)) { 48 | return type1SuperClassName 49 | } 50 | var type2SuperClassName = type2ClassReader.superName 51 | if (!superClassNames.add(type2SuperClassName)) { 52 | return type2SuperClassName 53 | } 54 | while (type1SuperClassName != null || type2SuperClassName != null) { 55 | if (type1SuperClassName != null) { 56 | type1SuperClassName = getSuperClassName(type1SuperClassName) 57 | if (type1SuperClassName != null) { 58 | if (!superClassNames.add(type1SuperClassName)) { 59 | return type1SuperClassName 60 | } 61 | } 62 | } 63 | if (type2SuperClassName != null) { 64 | type2SuperClassName = getSuperClassName(type2SuperClassName) 65 | if (type2SuperClassName != null) { 66 | if (!superClassNames.add(type2SuperClassName)) { 67 | return type2SuperClassName 68 | } 69 | } 70 | } 71 | } 72 | return OBJECT 73 | } 74 | 75 | private fun isImplements(interfaceName: String?, classReader: ClassReader): Boolean { 76 | var classInfo: ClassReader? = classReader 77 | while (classInfo != null) { 78 | val interfaceNames = classInfo.interfaces 79 | for (name in interfaceNames) { 80 | if (name != null && name == interfaceName) { 81 | return true 82 | } 83 | } 84 | for (name in interfaceNames) { 85 | if (name != null) { 86 | val interfaceInfo = getClassReader(name) 87 | if (interfaceInfo != null) { 88 | if (isImplements(interfaceName, interfaceInfo)) { 89 | return true 90 | } 91 | } 92 | } 93 | } 94 | val superClassName = classInfo.superName 95 | if (superClassName == null || superClassName == OBJECT) { 96 | break 97 | } 98 | classInfo = getClassReader(superClassName) 99 | } 100 | return false 101 | } 102 | 103 | private fun isInterface(classReader: ClassReader): Boolean { 104 | return classReader.getAccess() and Opcodes.ACC_INTERFACE !== 0 105 | } 106 | 107 | private fun getSuperClassName(className: String): String? { 108 | val classReader = getClassReader(className) ?: return null 109 | return classReader.superName 110 | } 111 | 112 | private fun getClassReader(className: String): ClassReader? { 113 | val inputStream = urlClassLoader?.getResourceAsStream("$className.class") 114 | try { 115 | if (inputStream != null) { 116 | return ClassReader(inputStream) 117 | } 118 | } catch (ignored: IOException) { 119 | } finally { 120 | if (inputStream != null) { 121 | try { 122 | inputStream.close() 123 | } catch (ignored: IOException) { 124 | } 125 | 126 | } 127 | } 128 | return null 129 | } 130 | 131 | companion object { 132 | val TAG = "ExtendClassWriter" 133 | private val OBJECT = "java/lang/Object" 134 | } 135 | } -------------------------------------------------------------------------------- /spi-plugin/src/main/java/com/afirez/spi/weave/SpiScanClassVisitor.kt: -------------------------------------------------------------------------------- 1 | package com.afirez.spi.weave 2 | 3 | import com.afirez.spi.SpiContext 4 | import com.afirez.spi.SpiExtension 5 | import com.knight.transform.weave.BaseClassVisitor 6 | import org.objectweb.asm.* 7 | 8 | /** 9 | * @link https://github.com/afirez/spi 10 | */ 11 | class SpiScanClassVisitor(context: SpiContext, classWriter: ClassWriter) : 12 | BaseClassVisitor(context, classWriter) { 13 | var superName: String? = null 14 | var interfaces: Array? = null 15 | val extension = context.extension as SpiExtension 16 | 17 | override fun visit( 18 | version: Int, 19 | access: Int, 20 | name: String, 21 | signature: String?, 22 | superName: String?, 23 | interfaces: Array? 24 | ) { 25 | super.visit(version, access, name, signature, superName, interfaces) 26 | this.superName = superName 27 | this.interfaces = interfaces 28 | } 29 | 30 | override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor { 31 | var av = super.visitAnnotation(descriptor, visible) 32 | 33 | when (descriptor) { 34 | "L${extension.spiPath};" -> { 35 | av = SpiScanAnnotationVisitor(av) 36 | } 37 | } 38 | 39 | return av 40 | } 41 | 42 | 43 | inner class SpiScanAnnotationVisitor(av: AnnotationVisitor) : AnnotationVisitor(Opcodes.ASM5, av) { 44 | var name: String? = null 45 | var value: Any? = null 46 | 47 | 48 | override fun visit(name: String?, value: Any?) { 49 | super.visit(name, value) 50 | this.name = name 51 | this.value = value 52 | } 53 | 54 | override fun visitEnd() { 55 | super.visitEnd() 56 | println("@SPI ( $name = '$value' ) class $className:") 57 | 58 | if (interfaces?.size == 0) { 59 | 60 | val type = className 61 | val extension = className 62 | val path = if (value == null) { 63 | className.replace("/", ".") 64 | } else { 65 | "$value" 66 | } 67 | var extensionMap = context.extensionsMap[type] 68 | if (extensionMap == null) { 69 | extensionMap = HashMap() 70 | context.extensionsMap[type] = extensionMap 71 | // println(" new Map:[ $type -> $extensionMap ]") 72 | } 73 | extensionMap[path] = extension 74 | return 75 | } 76 | 77 | interfaces?.forEach { 78 | val type = it 79 | val extension = className 80 | println(" SPI: [ $type -> $extension ]") 81 | val path = if (value == null) { 82 | className.replace("/", ".") 83 | } else { 84 | "$value" 85 | } 86 | var extensionMap = context.extensionsMap[type] 87 | if (extensionMap == null) { 88 | extensionMap = HashMap() 89 | context.extensionsMap[type] = extensionMap!! 90 | // println("Spi: new Map:[ $service -> $map ]") 91 | } 92 | extensionMap!![path] = extension 93 | } 94 | 95 | println(" --> end class $className") 96 | } 97 | } 98 | 99 | 100 | // private fun typeOf(className: String?, targetclassName: String): Boolean { 101 | // className ?: return false 102 | // 103 | // if (className == "Ljava/lang/Object") { 104 | // return false 105 | // } 106 | // if (className == targetclassName) { 107 | // return true 108 | // } 109 | // 110 | // return typeOf(getSuperClass(className), targetclassName) 111 | // 112 | // } 113 | 114 | // List getSuperClasses(className){ 115 | // superClass=getSuperClass(className) 116 | // return superClass+getSuperClasses(superClass) 117 | // } 118 | // 119 | // String getSuperClass(className) { 120 | // cw=new ClassWriter() 121 | // v=new SuperClassReadingClassVisitor(cw) 122 | // new ClassReader(className).accept(v) 123 | // return v.superClass 124 | // } 125 | 126 | // fun getSuperClass(className: String?): String? { 127 | // className ?: return null 128 | // 129 | // try { 130 | // val classReader = ClassReader(className) 131 | // val cv = object : ClassVisitor(Opcodes.ASM5, cv) { 132 | // var superName: String? = "" 133 | // override fun visit( 134 | // version: Int, 135 | // access: Int, 136 | // name: String?, 137 | // signature: String?, 138 | // superName: String?, 139 | // interfaces: Array? 140 | // ) { 141 | // this.superName = superName 142 | // super.visit(version, access, name, signature, superName, interfaces) 143 | // } 144 | // } 145 | // classReader.accept(cv, ClassReader.SKIP_FRAMES) 146 | // return cv.superName 147 | // } catch (e: Throwable) { 148 | // e.printStackTrace() 149 | // return null 150 | // } 151 | // } 152 | 153 | } 154 | --------------------------------------------------------------------------------