├── .gitignore
├── .travis.yml
├── README.md
├── annotation
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── kotlin
│ └── com
│ └── omooo
│ └── annotation
│ └── MethodTrace.kt
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── runtime
│ └── devRelease
│ │ └── runtimeSchemes.json
├── singer
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ ├── unused.json
│ ├── unused2.json
│ └── unused3.json
│ ├── java
│ └── com
│ │ └── omooo
│ │ └── plugin
│ │ ├── DemoActivity.java
│ │ ├── MainActivity.kt
│ │ └── MyApplication.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable-xxhdpi
│ └── bg.jpg
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_demo.xml
│ └── activity_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── refs.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── buildSrc
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── kotlin
│ └── com
│ └── omooo
│ └── buildsrc
│ └── Config.kt
├── frontend
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ ├── kotlin
│ └── top
│ │ └── omooo
│ │ └── frontend
│ │ ├── Main.kt
│ │ ├── bean
│ │ ├── AarAnalyseReporter.kt
│ │ ├── AarFile.kt
│ │ ├── AppFile.kt
│ │ ├── AppReporter.kt
│ │ └── FileType.kt
│ │ ├── chart
│ │ ├── ApexCharts.kt
│ │ ├── BarChartConfig.kt
│ │ ├── ChartConfig.kt
│ │ ├── ChartUtils.kt
│ │ ├── ChartsComponent.kt
│ │ └── LineChartConfig.kt
│ │ ├── common
│ │ ├── Header.kt
│ │ ├── MissedWrapper.kt
│ │ ├── ThemeModule.kt
│ │ └── Themes.kt
│ │ ├── component
│ │ ├── AarAccordion.kt
│ │ ├── AarList.kt
│ │ ├── AarTitle.kt
│ │ ├── OwnerSelects.kt
│ │ └── Summary.kt
│ │ ├── page
│ │ └── ApkAnalysePage.kt
│ │ └── util
│ │ ├── AarAnalyseData.kt
│ │ └── Format.kt
│ └── resources
│ ├── index.html
│ └── report.json
├── gradle.properties
├── gradle
├── plugin.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── kotlin-js-store
└── yarn.lock
├── lavender-plugin
└── ownership.yaml
├── library
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── omooo
│ │ └── library
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── library.json
│ │ └── lottie
│ │ │ └── lottie.json
│ ├── java
│ │ └── com
│ │ │ └── omooo
│ │ │ └── library
│ │ │ ├── LibraryActivity.kt
│ │ │ └── LibraryMain.kt
│ └── res
│ │ └── layout
│ │ └── activity_library.xml
│ └── test
│ └── java
│ └── com
│ └── omooo
│ └── library
│ └── ExampleUnitTest.kt
├── plugin
├── .gitignore
├── build.gradle
└── src
│ └── main
│ ├── kotlin
│ └── com
│ │ └── omooo
│ │ └── plugin
│ │ ├── Lavender.kt
│ │ ├── bean
│ │ ├── CheckSchemeModifiedExtension.kt
│ │ ├── Constants.kt
│ │ ├── InvokeCheckExtension.kt
│ │ └── WebpToolBean.kt
│ │ ├── internal
│ │ ├── ArtifactType.kt
│ │ ├── aar
│ │ │ └── AarAnalyse.kt
│ │ ├── apk
│ │ │ ├── ApkIncrementAnalyse.kt
│ │ │ ├── ApkParser.kt
│ │ │ ├── AppFileCleaner.kt
│ │ │ ├── ClassCleaner.kt
│ │ │ ├── ICleaner.kt
│ │ │ ├── ResourceCleaner.kt
│ │ │ └── TypeAssigningCleaner.kt
│ │ └── cha
│ │ │ ├── ClassSetCache.kt
│ │ │ ├── ComponentHandler.kt
│ │ │ ├── LayoutHandler.kt
│ │ │ └── ReferenceAnalyser.kt
│ │ ├── reporter
│ │ ├── AarAnalyseReporter.kt
│ │ ├── AppReporter.kt
│ │ ├── HtmlReporter.kt
│ │ ├── Insight.kt
│ │ └── common
│ │ │ ├── AarFile.kt
│ │ │ ├── AppFile.kt
│ │ │ └── FileType.kt
│ │ ├── scan
│ │ └── BuildScan.kt
│ │ ├── spi
│ │ └── VariantProcessor.kt
│ │ ├── task
│ │ ├── AarAnalyseTask.kt
│ │ ├── AarAnalyseTaskProcessor.kt
│ │ ├── ApkAnalyseTask.kt
│ │ ├── ApkAnalyseVariantProcessor.kt
│ │ ├── CheckExportedTask.kt
│ │ ├── CheckExportedVariantProcessor.kt
│ │ ├── CheckSchemeModifiedProcessor.kt
│ │ ├── CheckSchemeModifiedTask.kt
│ │ ├── CheckServiceTypeTask.kt
│ │ ├── CheckServiceTypeVariantProcessor.kt
│ │ ├── DetectTranslucentActivityTask.kt
│ │ ├── DetectTranslucentActivityVariantProcessor.kt
│ │ ├── FragmentNonConstructCheckTask.kt
│ │ ├── FragmentNonConstructCheckVariantProcessor.kt
│ │ ├── ListAarSizeTask.kt
│ │ ├── ListAarSizeVariantProcessor.kt
│ │ ├── ListAssetsTask.kt
│ │ ├── ListAssetsVariantProcessor.kt
│ │ ├── ListClassOwnerMapTask.kt
│ │ ├── ListClassOwnerMapVariantProcessor.kt
│ │ ├── ListImageTask.kt
│ │ ├── ListImageVariantProcessor.kt
│ │ ├── ListPackageNameTask.kt
│ │ ├── ListPackageNameVariantProcessor.kt
│ │ ├── ListPermissionTask.kt
│ │ ├── ListPermissionVariantProcessor.kt
│ │ ├── ListSchemeTask.kt
│ │ ├── ListSchemeVariantProcessor.kt
│ │ ├── ListUnusedAssetsTask.kt
│ │ ├── ListUnusedAssetsVariantProcessor.kt
│ │ ├── ListUnusedClassTask.kt
│ │ ├── ListUnusedClassVariantProcessor.kt
│ │ ├── ListUnusedResTask.kt
│ │ ├── ListUnusedResVariantProcessor.kt
│ │ ├── RepeatResDetectorTask.kt
│ │ └── RepeatResDetectorVariantProcessor.kt
│ │ ├── transform
│ │ ├── BaseClassNode.kt
│ │ ├── BaseClassVisitor.kt
│ │ ├── invoke
│ │ │ ├── InvokeCheckClassNode.kt
│ │ │ ├── InvokeCheckCvFactory.kt
│ │ │ └── InvokeCheckParams.kt
│ │ ├── shrinkres
│ │ │ ├── IdentifierCheckClassNode.kt
│ │ │ └── IdentifierCheckCvFactory.kt
│ │ └── systrace
│ │ │ ├── SystraceClassVisitor.kt
│ │ │ └── SystraceCvFactory.kt
│ │ └── util
│ │ ├── ArtifactExt.kt
│ │ ├── ClassDataExt.kt
│ │ ├── ClassNodeExt.kt
│ │ ├── CollectionsExt.kt
│ │ ├── ConsoleExt.kt
│ │ ├── FileExt.kt
│ │ ├── InsnNodeExt.kt
│ │ ├── ManifestFileExt.kt
│ │ ├── OpcodesExt.kt
│ │ ├── ProjectExt.kt
│ │ ├── StringExt.kt
│ │ ├── TransformReporter.kt
│ │ ├── Utils.kt
│ │ ├── VariantExt.kt
│ │ ├── WebpToolUtil.kt
│ │ ├── XmlParseExt.kt
│ │ └── ZipUtils.kt
│ └── resources
│ ├── META-INF
│ └── gradle-plugins
│ │ └── com.omooo.lavender.properties
│ ├── aarAnalyse-Template.html
│ └── apkAnalyse-Template.html
├── settings.gradle
├── tools
└── cwebp
│ ├── linux
│ └── cwebp
│ ├── mac
│ └── cwebp
│ └── windows
│ └── cwebp.exe
└── wiki
├── 包体积优化
├── APK 增量分析.md
├── 删除无用 Assets 资源.md
├── 图片压缩使用文档.md
├── 无用 Assets 资源检测.md
├── 无用资源监测.md
├── 输出 App 依赖 AAR 下的 assets 资源.md
├── 输出 App 依赖的 AAR 大小.md
├── 输出图片列表.md
└── 重复资源检测.md
└── 静态分析
├── exported 属性检测.md
├── scheme 变更检查.md
├── 依赖权限检测.md
└── 方法、字段、常量调用检测.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | /repo
10 | lavender-plugin/apk/previous.json
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | dist: trusty
3 | android:
4 | components:
5 | # Uncomment the lines below if you want to
6 | # use the latest revision of Android SDK Tools
7 | # - tools
8 | # - platform-tools
9 |
10 | # The BuildTools version used by your project
11 | - build-tools-29.0.2
12 |
13 | # The SDK version used to compile your project
14 | - android-29
15 |
16 | # Additional components
17 | - extra-google-google_play_services
18 | - extra-google-m2repository
19 | - extra-android-m2repository
20 |
21 | # Specify at least one system image,
22 | # if you need to run emulator(s) during your tests
23 | - sys-img-x86-android-26
24 | - sys-img-armeabi-v7a-android-17
25 | before_cache:
26 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
27 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
28 | cache:
29 | directories:
30 | - $HOME/.gradle/caches/
31 | - $HOME/.gradle/wrapper/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | 概览
3 | ---
4 |
5 | ## 一、Lavener 是什么
6 |
7 | Lavener 是一个 Gradle Plugin,提供一系列静态检测的能力,其目标主要是为了解决随着 APP 复杂度的提升而带来的性能、稳定性、包体积等一系列质量问题。
8 |
9 | ## 二、为什么以此命名?
10 |
11 | Lavender 直译为薰衣草。薰衣草的香味能够帮助我们缓解压力、减少沮丧。
12 |
13 | 希望这个 Gradle Plugin 也能够帮助我们减少重复的劳动工作,使我们的工作更加轻松顺畅。
14 |
15 | ## 三、功能列表
16 |
17 | 目前包含包体积瘦身和安全合规检查相关功能。
18 |
19 | ### 包体积瘦身
20 |
21 | | 功能 | 使用文档 |
22 | | ------------------------------------- | ------------------------------------------------------------ |
23 | | 重复资源监测 | [重复资源检测](/wiki/包体积优化/重复资源检测.md) |
24 | | 输出 AAR 大小 | [输出 App 依赖的 AAR 大小](/wiki/包体积优化/输出%20App%20依赖的%20AAR%20大小.md) |
25 | | 打包时自动 png 转 webp、webp 图片压缩 | [图片压缩使用文档](/wiki/包体积优化/图片压缩使用文档.md) |
26 | | 无用资源监测 | [无用资源监测](/wiki/包体积优化/无用资源监测.md) |
27 | | 无用 Assets 资源监测 | [无用 Assets 资源检测](/wiki/包体积优化/无用%20Assets%20资源检测.md) |
28 | | 输出 App 依赖的 AAR 下的 assets 资源 | [输出 App 依赖的所有 assets 资源](/wiki/包体积优化/输出%20App%20依赖%20AAR%20下的%20assets%20资源.md) |
29 | | 删除无用 Assets 资源 | [删除无用 Assets 资源](/wiki/包体积优化/删除无用%20Assets%20资源.md) |
30 | | APK 增量分析 | [APK 增量分析](/wiki/包体积优化/APK%20增量分析.md) |
31 | | 输出图片列表 | [输出图片列表](/wiki/包体积优化/输出图片列表.md) |
32 |
33 | ### 静态分析
34 |
35 | | 功能 | 使用 |
36 | | ---------------------------------------------------- | ------------------------------------------------------ |
37 | | 输出 App 及其依赖的 AAR 权限 | [依赖权限检测]() |
38 | | 检测 Manifest 注册组件是否声明 android:exported 属性 | [exported 属性检测]() |
39 | | 类、方法、常量、字段调用检测 | [方法调用检测]() |
40 | | 输出无用类文件 | [输出无用类文件]() |
41 | | 输出类及所属负责人的映射 | [输出类及所属的负责人的映射]() |
42 | | 输出 Manifest 定义的 scheme | [输出 Manifest 定义的 scheme ]() |
43 | | 检测透明 Activity 设置了 screenOrientation 属性 | [检测透明 Activity 设置了 screenOrientation 属性]() |
44 | | 输出依赖的包名列表 | [输出依赖的包名列表]() |
45 | | scheme 变更检查 | [scheme 变更检查](/wiki/静态分析/scheme%20变更检查.md) |
--------------------------------------------------------------------------------
/annotation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/annotation/build.gradle:
--------------------------------------------------------------------------------
1 | apply from: '../gradle/plugin.gradle'
2 |
3 | //apply plugin: 'maven'
4 | apply plugin: 'java'
5 |
6 | //uploadArchives {
7 | // repositories {
8 | // mavenDeployer {
9 | // repository(url: "http://192.168.9.230:8081/repository/app-releases/") {
10 | // authentication(userName: "admin", password: "admin123")
11 | // }
12 | //
13 | // pom.groupId = 'com.omooo.plugin'
14 | // pom.artifactId = 'annotation'
15 | // pom.version = '0.1.1'
16 | //
17 | // pom.project {
18 | // licenses {
19 | // license {
20 | // name 'The Apache Software License, Version 2.0'
21 | // url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
22 | // }
23 | // }
24 | // }
25 | // }
26 | // }
27 | //}
28 | java {
29 | sourceCompatibility = JavaVersion.VERSION_17
30 | targetCompatibility = JavaVersion.VERSION_17
31 | }
32 |
33 | kotlin {
34 | jvmToolchain(17)
35 | }
--------------------------------------------------------------------------------
/annotation/src/main/kotlin/com/omooo/annotation/MethodTrace.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.annotation
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2019/10/11
6 | * Version: v0.1.0
7 | * Desc: 方法耗时打点注解
8 | */
9 |
10 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
11 | @Retention(AnnotationRetention.BINARY)
12 | annotation class MethodTrace
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'com.omooo.lavender'
6 | //invokeCheckConfig {
7 | // methodList = [
8 | // "android.widget.Toast#makeText",
9 | // "android.widget.Toast",
10 | // "android.widget.Toast#show()V",
11 | // ]
12 | // packageList = [
13 | // "com.omooo.library",
14 | // ]
15 | //}
16 |
17 | //checkSchemeModifiedConfig {
18 | // enable = true
19 | // baselineSchemeFile = file("runtime/devRelease/runtimeSchemes.json")
20 | //}
21 |
22 | apply plugin: 'com.spotify.ruler'
23 | ruler {
24 | abi.set("arm64-v8a")
25 | locale.set("zh")
26 | screenDensity.set(480)
27 | sdkVersion.set(33)
28 | }
29 |
30 | android {
31 | compileSdkVersion 34
32 | namespace 'com.omooo.plugin'
33 | defaultConfig {
34 | applicationId "com.omooo.plugin"
35 | minSdkVersion 23
36 | targetSdkVersion 34
37 | versionCode 1
38 | versionName "1.0"
39 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
40 | }
41 | buildTypes {
42 | release {
43 | minifyEnabled true
44 | shrinkResources true
45 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
46 | }
47 | }
48 | }
49 |
50 | dependencies {
51 | implementation fileTree(dir: 'libs', include: ['*.jar'])
52 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10"
53 | implementation 'androidx.appcompat:appcompat:1.6.1'
54 | implementation 'androidx.core:core-ktx:1.10.1'
55 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
56 |
57 | implementation(project(":library"))
58 | }
59 |
60 | kotlin {
61 | jvmToolchain(17)
62 | }
63 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/runtime/devRelease/runtimeSchemes.json:
--------------------------------------------------------------------------------
1 | {
2 | "app.ui.activity.DemoActivity": {
3 | "first": "owner@demo.com",
4 | "second": "scheme://host/path"
5 | }
6 | }
--------------------------------------------------------------------------------
/app/singer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/singer
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/assets/unused.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
--------------------------------------------------------------------------------
/app/src/main/assets/unused2.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
--------------------------------------------------------------------------------
/app/src/main/assets/unused3.json:
--------------------------------------------------------------------------------
1 | {
2 | "key": "value"
3 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omooo/plugin/DemoActivity.java:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin;
2 |
3 | import android.os.Bundle;
4 | import android.os.PersistableBundle;
5 |
6 | import androidx.annotation.Nullable;
7 | import androidx.appcompat.app.AppCompatActivity;
8 |
9 | /**
10 | * Author: Omooo
11 | * Date: 2023/4/4
12 | * Desc:
13 | */
14 | public class DemoActivity extends AppCompatActivity {
15 | @Override
16 | public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
17 | super.onCreate(savedInstanceState, persistentState);
18 | setContentView(R.layout.activity_demo);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/omooo/plugin/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin
2 |
3 | import android.os.Bundle
4 | import android.widget.Toast
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.omooo.library.LibraryMain
7 |
8 | class MainActivity : AppCompatActivity() {
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 | setContentView(R.layout.activity_main)
13 |
14 | LibraryMain().show(this)
15 |
16 | assets.open("unused.json").close()
17 | show()
18 | }
19 |
20 | private fun show() {
21 | Toast.makeText(this, "2333", Toast.LENGTH_SHORT).show()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/omooo/plugin/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin
2 |
3 | import android.app.Application
4 |
5 | /**
6 | * @author Omooo
7 | * @version v1.0
8 | * @Date 2020/03/11 16:46
9 | * desc :
10 | */
11 | class MyApplication : Application() {
12 | override fun onCreate() {
13 | super.onCreate()
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/drawable-xxhdpi/bg.jpg
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/refs.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Lavender
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.9.0'
5 | repositories {
6 | mavenLocal()
7 | google()
8 | mavenCentral()
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:8.2.0'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 |
17 | classpath "com.omooo:lavender:0.0.1"
18 | classpath("com.spotify.ruler:ruler-gradle-plugin:2.0.1")
19 | }
20 | }
21 |
22 | allprojects {
23 | repositories {
24 | mavenLocal()
25 | google()
26 | mavenCentral()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.9.0'
3 | repositories {
4 | mavenLocal()
5 | google()
6 | mavenCentral()
7 | }
8 | dependencies {
9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
10 | }
11 | }
12 |
13 | apply plugin: 'java'
14 | apply plugin: 'maven-publish'
15 | apply plugin: 'kotlin'
16 |
17 | repositories {
18 | mavenLocal()
19 | google()
20 | mavenCentral()
21 | }
22 |
23 | dependencies {
24 | implementation gradleApi()
25 | implementation localGroovy()
26 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
27 | implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
28 | testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
29 | testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
30 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/omooo/buildsrc/Config.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.buildsrc
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2019/9/27
6 | * Version: v0.1.0
7 | * Desc: 统一依赖版本配置
8 | */
9 | class Config {
10 | companion object {
11 | const val kotlin_version = "1.9.0"
12 | }
13 | }
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /externals
--------------------------------------------------------------------------------
/frontend/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("js")
3 | kotlin("plugin.serialization")
4 | }
5 |
6 | fun kotlinw(target: String): String =
7 | "org.jetbrains.kotlin-wrappers:kotlin-$target"
8 |
9 | dependencies {
10 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
11 | implementation("org.jetbrains.kotlin-wrappers:kotlin-extensions:1.0.1-pre.343")
12 |
13 | implementation(enforcedPlatform(kotlinw("wrappers-bom:1.0.0-pre.477")))
14 |
15 | implementation(kotlinw("react"))
16 | implementation(kotlinw("react-dom"))
17 | implementation(kotlinw("react-router-dom"))
18 |
19 | implementation(kotlinw("emotion"))
20 | implementation(kotlinw("mui"))
21 | implementation(kotlinw("mui-icons"))
22 | implementation(npm("apexcharts", "3.41.0"))
23 |
24 | implementation(npm("date-fns", "2.29.3"))
25 | implementation(npm("@date-io/date-fns", "2.16.0"))
26 | }
27 |
28 | kotlin {
29 | js(IR) {
30 | browser {
31 | commonWebpackConfig {
32 | cssSupport {
33 | this.enabled = true
34 | }
35 | }
36 | }
37 | binaries.executable()
38 | }
39 | }
40 |
41 | // 注册 browserPackage Task
42 | tasks.register("browserPackage") {
43 | dependsOn("browserDistribution")
44 | mustRunAfter("browserDistribution")
45 | doLast {
46 | val rootDir = buildDir.resolve("distributions")
47 | val html = rootDir.resolve("index.html").readText()
48 | val javascript = rootDir.resolve("frontend.js").readText()
49 | val reportFile = rootDir.resolve("report.html")
50 | reportFile.writeText(
51 | html.replace("", "")
52 | )
53 | println("Wrote HTML report to ${reportFile.toPath().toUri()}")
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/bean/AarAnalyseReporter.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.bean
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/7/14
6 | * Desc: AAR 分析报告
7 | */
8 | @kotlinx.serialization.Serializable
9 | data class AarAnalyseReporter(
10 | /** 描述信息 */
11 | val desc: String,
12 | /** 文档链接 */
13 | val documentLink: String,
14 | /** 包名 */
15 | val packageName: String,
16 | /** AAR 列表 */
17 | val aarList: List>>,
18 | /** 所属人映射 */
19 | val ownerMap: Map> = emptyMap(),
20 | ) {
21 |
22 | /**
23 | * 获取 AAR 趋势表格的 x 轴标签
24 | */
25 | fun getChartLabels(): Array {
26 | return aarList.map {
27 | it.first
28 | }.toTypedArray().reversedArray()
29 | }
30 |
31 | /**
32 | * 获取 AAR 趋势表格的 y 轴数据
33 | */
34 | fun getChartSeries(owner: String, aarName: String): Pair {
35 | val series = LongArray(aarList.size)
36 | aarList.forEachIndexed { index, pair ->
37 | if (aarName == "All") {
38 | series[index] = pair.second.filter {
39 | if (owner == "All") true else it.owner == owner
40 | }.totalSize()
41 | } else {
42 | series[index] = pair.second.find { it.name == aarName }?.size ?: 0
43 | }
44 | }
45 | return Pair("大小", series.reversedArray())
46 | }
47 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/bean/AarFile.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.bean
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Author: Omooo
7 | * Date: 2023/2/3
8 | * Desc:
9 | */
10 | @Serializable
11 | data class AarFile(
12 | val name: String,
13 | val size: Long,
14 | val owner: String,
15 | val fileList: List = emptyList(),
16 | )
17 |
18 | internal fun List.totalSize(): Long {
19 | if (isEmpty()) {
20 | return 0
21 | }
22 | return map { it.size }.reduce { acc, l -> acc + l }
23 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/bean/AppFile.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.bean
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Author: Omooo
7 | * Date: 2023/2/2
8 | * Desc:
9 | */
10 | @Serializable
11 | data class AppFile(
12 | val name: String,
13 | val size: Long,
14 | val desc: String,
15 | var fileType: FileType = FileType.OTHER,
16 | )
17 |
18 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/bean/AppReporter.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.bean
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/2/3
6 | * Desc: App 报告类
7 | */
8 | @kotlinx.serialization.Serializable
9 | data class AppReporter(
10 | /** 描述信息 */
11 | val desc: String,
12 | /** 文档链接 */
13 | val documentLink: String,
14 | /** 版本号 */
15 | val versionName: String,
16 | /** 构建类型名 */
17 | val variantName: String,
18 | /** AAR 列表 */
19 | val aarList: List,
20 | )
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/bean/FileType.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.bean
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/3/17
6 | * Desc: 文件类型
7 | */
8 | enum class FileType {
9 | CLASS,
10 | RESOURCE,
11 | ASSET,
12 | NATIVE_LIB,
13 | OTHER,
14 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/chart/ApexCharts.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.chart
2 |
3 |
4 | import org.w3c.dom.Element
5 |
6 | typealias NumberFormatter = (Number) -> String
7 | typealias TooltipAxisFormatter = (Number, TooltipAxisFormatterOptions) -> String
8 |
9 | @JsModule("apexcharts")
10 | @JsNonModule
11 | @Suppress("UnusedPrivateMember")
12 | external class ApexCharts(element: Element?, options: dynamic) {
13 | fun render()
14 | fun destroy()
15 | }
16 |
17 | external interface ApexChartOptions {
18 | var chart: ChartOptions
19 | var dataLabels: DataLabelOptions
20 | var fill: FillOptions
21 | var grid: GridOptions
22 | var legend: LegendOptions
23 | var plotOptions: PlotOptions
24 | var series: Array
25 | var stroke: StrokeOptions
26 | var tooltip: TooltipOptions
27 | var xaxis: AxisOptions
28 | var yaxis: AxisOptions
29 | var title: TitleOptions
30 | // var annotations: AnnotationsOptions
31 | var colors: Array
32 | }
33 |
34 | external interface AnnotationsOptions {
35 | var yaxis: Array
36 | }
37 |
38 | external interface AnnotationYaxisOptions {
39 | var y: Long
40 | var y2: Long
41 | var borderColor: String
42 | var fillColor: String
43 | var label: AnnotationLabelOptions
44 | var strokeDashArray: Array
45 | }
46 |
47 | external interface AnnotationLabelOptions {
48 | var borderColor: String
49 | var text: String
50 | var style: AnnotationLabelStyleOptions
51 | }
52 |
53 | external interface AnnotationLabelStyleOptions {
54 | var color: String
55 | var background: String
56 | }
57 |
58 | external interface AxisLabelOptions {
59 | var style: AxisLabelStyleOptions
60 | var formatter: NumberFormatter
61 | }
62 |
63 | external interface AxisLabelStyleOptions {
64 | var fontSize: Int
65 | }
66 |
67 | external interface AxisOptions {
68 | var categories: Array
69 | var labels: AxisLabelOptions
70 | }
71 |
72 | external interface BarPlotOptions {
73 | var horizontal: Boolean
74 | }
75 |
76 | external interface ChartOptions {
77 | var fontFamily: String
78 | var height: Int
79 | var toolbar: ToolbarOptions
80 | var type: String
81 | var zoom: ChartZoomOptions
82 | }
83 |
84 | external interface ChartZoomOptions {
85 | var enabled: Boolean
86 | }
87 |
88 | external interface DataLabelOptions {
89 | var enabled: Boolean
90 | var formatter: NumberFormatter
91 | }
92 |
93 | external interface FillOptions {
94 | var opacity: Double
95 | }
96 |
97 | external interface GridAxisLineOptions {
98 | var show: Boolean
99 | }
100 |
101 | external interface GridAxisOptions {
102 | var lines: GridAxisLineOptions
103 | }
104 |
105 | external interface GridRowOptions {
106 | var colors: Array
107 | var opacity: Float
108 | }
109 |
110 | external interface GridOptions {
111 | var xaxis: GridAxisOptions
112 | var yaxis: GridAxisOptions
113 | var row: GridRowOptions
114 | }
115 |
116 | external interface LegendMarkerOptions {
117 | var width: Int
118 | var height: Int
119 | }
120 |
121 | external interface LegendOptions {
122 | var fontSize: Int
123 | var markers: LegendMarkerOptions
124 | }
125 |
126 | external interface PlotOptions {
127 | var bar: BarPlotOptions
128 | }
129 |
130 | external interface Series {
131 | var type: String
132 | var name: String
133 | var data: Array
134 | }
135 |
136 | external interface StrokeOptions {
137 | var show: Boolean
138 | var colors: Array
139 | var width: Int
140 | var curve: String
141 | }
142 |
143 | external interface ToolbarOptions {
144 | var show: Boolean
145 | }
146 |
147 | external interface TooltipAxisFormatterOptions {
148 | var series: Array>
149 | var seriesIndex: Int
150 | }
151 |
152 | external interface TooltipAxisOptions {
153 | var formatter: TooltipAxisFormatter
154 | }
155 |
156 | external interface TooltipOptions {
157 | var x: TooltipAxisOptions
158 | var y: TooltipAxisOptions
159 | }
160 |
161 | external interface TitleOptions {
162 | var text: String
163 | var align: String
164 | }
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/chart/BarChartConfig.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.chart
2 |
3 | import top.omooo.frontend.util.formatPercentage
4 |
5 | /** Chart config for bar charts. */
6 | @Suppress("LongParameterList")
7 | class BarChartConfig(
8 | private val chartLabels: Array,
9 | private val chartSeries: Array,
10 | private val chartHeight: Int,
11 | private val horizontal: Boolean = false,
12 | private val xAxisFormatter: NumberFormatter = Number::toString,
13 | private val yAxisFormatter: NumberFormatter = Number::toString,
14 | private val chartSeriesTotals: LongArray? = null,
15 | ) : ChartConfig() {
16 |
17 | override fun getOptions() = buildOptions {
18 | series = chartSeries
19 | xaxis.categories = chartLabels
20 | chart.type = "bar"
21 | chart.height = chartHeight
22 |
23 | grid.xaxis.lines.show = horizontal
24 | grid.yaxis.lines.show = !horizontal
25 | plotOptions.bar.horizontal = horizontal
26 |
27 | xaxis.labels.formatter = xAxisFormatter
28 | yaxis.labels.formatter = yAxisFormatter
29 | tooltip.y.formatter = ::formatTooltip
30 | }
31 |
32 | private fun formatTooltip(number: Number, options: TooltipAxisFormatterOptions): String {
33 | val axisFormatter = if (horizontal) xAxisFormatter else yAxisFormatter
34 | val total = if (chartSeriesTotals != null) {
35 | chartSeriesTotals[options.seriesIndex]
36 | } else {
37 | options.series[options.seriesIndex].sumOf(Number::toLong)
38 | }
39 | return "${axisFormatter.invoke(number)} (${formatPercentage(number, total)})"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/chart/ChartConfig.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.chart
2 |
3 | import js.core.jso
4 |
5 | /** Base config for displaying charts. Check https://apexcharts.com/docs/options/ for all chart types and options. */
6 | abstract class ChartConfig {
7 |
8 | /** Returns the chart options for this config used by ApexCharts. */
9 | abstract fun getOptions(): ApexChartOptions
10 |
11 | /** Utility function which allows concrete configs to start with a common sets of defaults. */
12 | protected fun buildOptions(builder: ApexChartOptions.() -> Unit) = jso {
13 | chart = jso {
14 | fontFamily = FONT_FAMILY
15 | toolbar = jso {
16 | show = false
17 | }
18 | zoom = jso {
19 | enabled = false
20 | }
21 | }
22 | dataLabels = jso {
23 | enabled = false
24 | formatter = jso()
25 | }
26 | fill = jso {
27 | opacity = 1.0
28 | }
29 | grid = jso {
30 | xaxis = jso {
31 | lines = jso()
32 | }
33 | yaxis = jso {
34 | lines = jso()
35 | }
36 | row = jso {
37 | colors = jso()
38 | opacity = jso()
39 | }
40 | }
41 | legend = jso {
42 | fontSize = FONT_SIZE
43 | markers = jso {
44 | width = FONT_SIZE
45 | height = FONT_SIZE
46 | }
47 | }
48 | plotOptions = jso {
49 | bar = jso()
50 | }
51 | stroke = jso {
52 | show = true
53 | colors = arrayOf("transparent")
54 | width = STROKE_WIDTH
55 | curve = jso()
56 | }
57 | tooltip = jso {
58 | x = jso()
59 | y = jso()
60 | }
61 | xaxis = jso {
62 | labels = jso {
63 | style = jso {
64 | fontSize = FONT_SIZE
65 | }
66 | }
67 | }
68 | yaxis = jso {
69 | labels = jso {
70 | style = jso {
71 | fontSize = FONT_SIZE
72 | }
73 | }
74 | }
75 | title = jso {
76 | text = jso()
77 | align = jso()
78 | }
79 | // annotations = jso {
80 | // yaxis = arrayOf(
81 | // jso {
82 | // y = jso()
83 | // y2 = jso()
84 | // borderColor = jso()
85 | // fillColor = jso()
86 | // label = jso {
87 | // borderColor = jso()
88 | // text = jso()
89 | // style = jso {
90 | // color = jso()
91 | // background = jso()
92 | // }
93 | // }
94 | // strokeDashArray = jso()
95 | // }
96 | // )
97 | // }
98 | colors = jso()
99 | }.apply(builder)
100 |
101 | private companion object {
102 | const val FONT_FAMILY = "var(--bs-body-font-family)"
103 | const val FONT_SIZE = 14
104 | const val STROKE_WIDTH = 3
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/chart/ChartUtils.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.chart
2 |
3 | import js.core.jso
4 |
5 | fun seriesOf(name: String, data: LongArray): Series = jso {
6 | this.name = name
7 | this.data = data.map(Long::toInt).toTypedArray()
8 | }
9 |
10 | fun annotationYaxisOptionsOf(y: Long, desc: String): AnnotationYaxisOptions = jso {
11 | this.y = y
12 | this.borderColor = "#00E396"
13 | this.label = jso {
14 | this.text = desc
15 | this.borderColor = "#00E396"
16 | this.style = jso {
17 | color = "#FFFFFF"
18 | background = "#00E396"
19 | }
20 | }
21 | this.strokeDashArray = arrayOf(5f, 5f)
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/chart/ChartsComponent.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.chart
2 |
3 | import kotlinx.browser.document
4 | import react.FC
5 | import react.Props
6 | import react.dom.html.ReactHTML.div
7 | import react.useEffect
8 | import top.omooo.frontend.util.formatSize
9 | import kotlin.math.roundToLong
10 |
11 | val ChartsComponent = FC { props->
12 | div {
13 | id = "id-charts"
14 | val thresholdSize = props.chartSeries.second.find {
15 | it != 0L
16 | }?.times(1.2f)?.roundToLong() ?: 0L
17 | val thresholdSeries = props.chartSeries.second.map {
18 | if (it != 0L) thresholdSize else it
19 | }.toLongArray()
20 | val config = LineChartConfig(
21 | chartLabels = props.chartLabels,
22 | chartSeries = arrayOf(
23 | seriesOf(props.chartSeries.first, props.chartSeries.second),
24 | seriesOf("阈值", thresholdSeries),
25 | ),
26 | chartHeight = 350,
27 | yAxisFormatter = Number::formatSize,
28 | )
29 | useEffect {
30 | val chart = ApexCharts(document.getElementById("id-charts"), config.getOptions())
31 | chart.render()
32 | cleanup {
33 | chart.destroy()
34 | }
35 | }
36 |
37 | }
38 | }
39 |
40 | external interface ChartsComponentProps : Props {
41 | var chartSeries: Pair
42 | var chartLabels: Array
43 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/chart/LineChartConfig.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.chart
2 |
3 | /**
4 | * Chart config for line charts.
5 | * 文档:https://apexcharts.com/javascript-chart-demos/line-charts/basic/
6 | */
7 | @Suppress("LongParameterList")
8 | class LineChartConfig(
9 | private val chartLabels: Array,
10 | private val chartSeries: Array,
11 | private val chartHeight: Int,
12 | private val xAxisFormatter: NumberFormatter = Number::toString,
13 | private val yAxisFormatter: NumberFormatter = Number::toString,
14 | ) : ChartConfig() {
15 |
16 | override fun getOptions() = buildOptions {
17 | series = chartSeries
18 | xaxis.categories = chartLabels
19 | chart.type = "line"
20 | chart.height = chartHeight
21 | chart.zoom.enabled = false
22 |
23 | grid.xaxis.lines.show = true
24 |
25 | grid.row.apply {
26 | colors = arrayOf("#f3f3f3", "transparent")
27 | opacity = 0.5f
28 | }
29 |
30 | dataLabels.enabled = true
31 | dataLabels.formatter = yAxisFormatter
32 |
33 | yaxis.labels.formatter = yAxisFormatter
34 |
35 | stroke.apply {
36 | show = true
37 | curve = "straight"
38 | colors = arrayOf("#4095E5", "#BD3124")
39 | }
40 | title.apply {
41 | text = "阈值为最初版本的 120% 大小"
42 | align = "middle"
43 | }
44 | colors = arrayOf("#4095E5", "#BD3124")
45 | // annotations.yaxis = arrayOf(annotationYaxisOptionsOf(5, "阈值"))
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/common/Header.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.common
2 |
3 | import csstype.integer
4 | import csstype.number
5 | import kotlinx.browser.window
6 | import mui.icons.material.Brightness4
7 | import mui.icons.material.Brightness7
8 | import mui.icons.material.MenuBook
9 | import mui.material.*
10 | import mui.material.styles.TypographyVariant.h6
11 | import mui.system.sx
12 | import react.*
13 | import react.dom.html.ReactHTML.div
14 |
15 | /**
16 | * Author: Omooo
17 | * Date: 2023/2/1
18 | * Desc: 通用顶部 Header
19 | */
20 |
21 | external interface HeaderProps : Props {
22 | /** 标题 */
23 | var title: String
24 |
25 | /** 文档链接 */
26 | var documentLink: String
27 | }
28 |
29 | val Header = FC { props ->
30 | var theme by useContext(ThemeContext)
31 |
32 | AppBar {
33 | position = AppBarPosition.sticky
34 | sx {
35 | zIndex = integer(1_500)
36 | }
37 |
38 | Toolbar {
39 | Typography {
40 | sx { flexGrow = number(1.0) }
41 | variant = h6
42 | noWrap = true
43 | component = div
44 |
45 | +props.title
46 | }
47 |
48 | Tooltip {
49 | title = ReactNode("Theme")
50 |
51 | Switch {
52 | icon = Brightness7.create()
53 | checkedIcon = Brightness4.create()
54 | checked = theme == Themes.Dark
55 |
56 | onChange = { _, checked ->
57 | theme = if (checked) Themes.Dark else Themes.Light
58 | }
59 | }
60 | }
61 |
62 | Tooltip {
63 | title = ReactNode("Read Documentation")
64 |
65 | IconButton {
66 | size = Size.large
67 | color = IconButtonColor.inherit
68 | onClick = {
69 | window.open(props.documentLink)
70 | }
71 |
72 | MenuBook()
73 | }
74 | }
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/common/MissedWrapper.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.common
2 |
3 | import mui.material.GridProps
4 | import mui.material.TypographyProps
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2023/7/17
9 | * Desc: Remove when it will be implemented in MUI wrappers
10 | */
11 |
12 | inline var GridProps.xs: Int
13 | get() = TODO("Prop is write-only!")
14 | set(value) {
15 | asDynamic().xs = value
16 | }
17 |
18 | inline var TypographyProps.color: String
19 | get() = TODO("Prop is write-only!")
20 | set(value) {
21 | asDynamic().color = value
22 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/common/ThemeModule.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.common
2 |
3 | import mui.material.CssBaseline
4 | import mui.material.styles.Theme
5 | import mui.material.styles.ThemeProvider
6 | import react.*
7 |
8 | typealias ThemeState = StateInstance
9 |
10 | val ThemeContext = createContext()
11 |
12 | val ThemeModule = FC { props ->
13 | val state = useState(Themes.Light)
14 | val (theme) = state
15 |
16 | ThemeContext(state) {
17 | ThemeProvider {
18 | this.theme = theme
19 |
20 | CssBaseline()
21 | +props.children
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/common/Themes.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.common
2 |
3 | import js.core.jso
4 | import mui.material.PaletteMode.dark
5 | import mui.material.PaletteMode.light
6 | import mui.material.styles.createTheme
7 |
8 | object Themes {
9 | val Light = createTheme(
10 | jso {
11 | palette = jso { mode = light }
12 | }
13 | )
14 |
15 | val Dark = createTheme(
16 | jso {
17 | palette = jso { mode = dark }
18 | }
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/component/AarAccordion.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.component
2 |
3 | import csstype.*
4 | import mui.icons.material.ExpandMore
5 | import mui.material.*
6 | import mui.material.Size
7 | import mui.system.sx
8 | import react.FC
9 | import react.Props
10 | import react.ReactNode
11 | import react.create
12 | import top.omooo.frontend.bean.AarFile
13 | import top.omooo.frontend.bean.AppFile
14 | import top.omooo.frontend.util.formatSize
15 |
16 | /**
17 | * Author: Omooo
18 | * Date: 2023/2/1
19 | * Desc: Aar 可展开列表
20 | */
21 |
22 | external interface AarAccordionProps : Props {
23 | var aarList: List
24 | }
25 |
26 | val AarAccordion = FC { props ->
27 | props.aarList.forEach { aarData ->
28 | Accordion {
29 | sx {
30 | paddingLeft = 20.px
31 | flexGrow = number(1.0)
32 | }
33 | AccordionSummary {
34 | expandIcon = ExpandMore.create()
35 | Typography {
36 | +aarData.name
37 | }
38 | Chip {
39 | sx {
40 | marginLeft = 18.px
41 | }
42 | size = Size.small
43 | label = ReactNode(aarData.owner)
44 | variant = ChipVariant.outlined
45 | }
46 | if (aarData.size != 0L) {
47 | Typography {
48 | sx {
49 | flexGrow = number(1.0)
50 | marginRight = 5.px
51 | textAlign = TextAlign.right
52 | }
53 | +aarData.size.formatSize()
54 | }
55 | }
56 | }
57 | AccordionDetails {
58 | AppFileList {
59 | list = aarData.fileList.sortedByDescending {
60 | it.size
61 | }
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
68 | external interface AppFileListProps : Props {
69 | var list: List
70 | }
71 |
72 | private val AppFileList = FC { props ->
73 | List {
74 | sx {
75 | paddingRight = 10.px
76 | }
77 | props.list.forEach { appFile ->
78 | ListItem {
79 | ListItemText {
80 | +appFile.name
81 | }
82 | if (appFile.size != 0L || appFile.desc.isNotEmpty()) {
83 | ListItemText {
84 | sx {
85 | textAlign = TextAlign.right
86 | }
87 | +if (appFile.size != 0L) appFile.size.formatSize() else appFile.desc
88 | }
89 | }
90 | }
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/component/AarList.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.component
2 |
3 | import csstype.*
4 | import mui.material.Divider
5 | import mui.material.List
6 | import mui.material.ListItem
7 | import mui.material.ListItemText
8 | import mui.system.sx
9 | import react.FC
10 | import react.Props
11 | import top.omooo.frontend.bean.AarFile
12 | import top.omooo.frontend.util.formatSize
13 |
14 | /**
15 | * Author: Omooo
16 | * Date: 2023/7/17
17 | * Desc: Aar 列表
18 | */
19 |
20 | val AarList = FC { props ->
21 | List {
22 | sx {
23 | paddingRight = 10.px
24 | }
25 | props.aarList.forEach { appFile ->
26 | ListItem {
27 | ListItemText {
28 | +appFile.name
29 | }
30 | ListItemText {
31 | sx {
32 | textAlign = TextAlign.right
33 | if (props.showDiff) {
34 | color = if (appFile.size < 0) Color("#4095E5") else Color("#BD3124")
35 | }
36 | }
37 | +"${if (!props.showDiff || appFile.size < 0) "" else "+"}${appFile.size.formatSize()}"
38 | }
39 | }
40 | Divider()
41 | }
42 | }
43 | }
44 |
45 | external interface AarListProps : Props {
46 | var showDiff: Boolean
47 | var aarList: List
48 | }
49 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/component/AarTitle.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.component
2 |
3 | import csstype.AlignItems
4 | import csstype.Border
5 | import csstype.Color
6 | import csstype.LineStyle
7 | import csstype.px
8 | import emotion.react.css
9 | import mui.material.*
10 | import react.FC
11 | import react.Props
12 | import top.omooo.frontend.bean.AarAnalyseReporter
13 | import top.omooo.frontend.bean.totalSize
14 | import top.omooo.frontend.common.color
15 | import top.omooo.frontend.common.xs
16 | import top.omooo.frontend.util.formatSize
17 |
18 | /**
19 | * Author: Omooo
20 | * Date: 2023/7/17
21 | * Desc: AAR 标题
22 | */
23 |
24 | val AarTitle = FC { props ->
25 | Box {
26 | css {
27 | border = Border(1.px, LineStyle.solid, Color("#C0C0C0"))
28 | }
29 | Grid {
30 | container = true
31 | css {
32 | alignItems = AlignItems.flexEnd
33 | }
34 | Grid {
35 | item = true
36 | xs = 8
37 | Box {
38 | css {
39 | padding = 16.px
40 | }
41 | Typography {
42 | +props.data.packageName
43 | }
44 | Typography {
45 | color = "text.secondary"
46 | +"Version: ${props.data.aarList.getOrNull(0)?.first} (previous: ${
47 | props.data.aarList.getOrNull(1)?.first
48 | })"
49 | }
50 | }
51 | }
52 | Grid {
53 | item = true
54 | xs = 2
55 | Box {
56 | css {
57 | padding = 16.px
58 | }
59 | Typography {
60 | +"Total Count"
61 | }
62 | Typography {
63 | color = "text.secondary"
64 | +props.data.aarList.getOrNull(0)?.second.orEmpty().size.toString()
65 | }
66 | }
67 | }
68 | Grid {
69 | item = true
70 | xs = 2
71 | Box {
72 | css {
73 | padding = 16.px
74 | }
75 | Typography {
76 | +"Total Size"
77 | }
78 | Typography {
79 | color = "text.secondary"
80 | +props.data.aarList.getOrNull(0)?.second.orEmpty().totalSize().formatSize()
81 | }
82 | }
83 | }
84 | }
85 | }
86 | }
87 |
88 | external interface AarTitleProps : Props {
89 | var data: AarAnalyseReporter
90 | }
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/component/OwnerSelects.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.component
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/2/5
6 | * Desc:
7 | */
8 | import csstype.TextAlign
9 | import csstype.px
10 | import mui.material.*
11 | import mui.system.sx
12 | import react.FC
13 | import react.Props
14 | import react.ReactNode
15 | import react.useState
16 |
17 | /**
18 | * Author: Omooo
19 | * Date: 2023/2/5
20 | * Desc: Owner 下拉选择器
21 | */
22 |
23 | external interface OwnerSelectsProps : Props {
24 | var ownerList: List
25 | var defaultSelect: String
26 | var onSelect: (String) -> Unit
27 | }
28 |
29 | val OwnerSelects = FC { props ->
30 | var owner by useState(props.defaultSelect)
31 |
32 | Box {
33 | sx {
34 | minWidth = 300.px
35 | marginRight = 18.px
36 | textAlign = TextAlign.center
37 | }
38 | FormControl {
39 | fullWidth = true
40 | InputLabel {
41 | id = "select-label"
42 | +"Owner"
43 | }
44 | Select {
45 | labelId = "select-label"
46 | id = ""
47 | value = owner
48 | label = ReactNode("Owner")
49 | onChange = { event, _ ->
50 | owner = event.target.value
51 | props.onSelect(event.target.value)
52 | }
53 | props.ownerList.forEach {
54 | MenuItem {
55 | value = it
56 | +it
57 | }
58 | }
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/component/Summary.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.component
2 |
3 | import csstype.Position
4 | import mui.material.*
5 | import mui.system.sx
6 | import react.FC
7 | import react.Props
8 | import react.create
9 |
10 | /**
11 | * Author: Omooo
12 | * Date: 2023/2/3
13 | * Desc:
14 | */
15 |
16 | external interface SummaryProps : Props {
17 | var title: String
18 | var subtitle: String
19 | var ownerList: List
20 | var defaultSelect: String
21 | var onSelect: (String) -> Unit
22 | }
23 |
24 | val Summary = FC { props ->
25 | Alert {
26 | sx {
27 | position = Position.sticky
28 | }
29 | severity = AlertColor.info
30 | color = AlertColor.info
31 |
32 | AlertTitle {
33 | +props.title
34 | }
35 | +props.subtitle
36 |
37 | if (props.ownerList.isNotEmpty()) {
38 | action = OwnerSelects.create {
39 | ownerList = props.ownerList
40 | defaultSelect = props.defaultSelect
41 | onSelect = props.onSelect
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/page/ApkAnalysePage.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.page
2 |
3 | import kotlinext.js.require
4 | import kotlinx.serialization.json.Json
5 | import mui.system.Box
6 | import react.FC
7 | import react.Props
8 | import react.create
9 | import react.dom.client.createRoot
10 | import react.useState
11 | import top.omooo.frontend.bean.AppReporter
12 | import top.omooo.frontend.common.Header
13 | import top.omooo.frontend.common.ThemeModule
14 | import top.omooo.frontend.component.AarAccordion
15 | import top.omooo.frontend.component.Summary
16 | import top.omooo.frontend.util.*
17 | import web.dom.document
18 |
19 | fun main() {
20 | val text = require("./report.json").toString()
21 | val data = Json.decodeFromString(AppReporter.serializer(), text)
22 | createRoot(document.getElementById("root")!!).render(
23 | App.create {
24 | appReporter = data
25 | }
26 | )
27 | }
28 |
29 | private external interface AppProps : Props {
30 | var appReporter: AppReporter
31 | }
32 |
33 | private val App = FC { props ->
34 | var owner by useState("none")
35 | ThemeModule {
36 | Box {
37 | Header {
38 | title = props.appReporter.desc
39 | documentLink = props.appReporter.documentLink
40 | }
41 |
42 | Summary {
43 | title = props.appReporter.aarList.filter {
44 | if (owner == "none") true else it.owner == owner
45 | }.let { list ->
46 | if ((list.firstOrNull()?.size ?: 0) > 0) {
47 | "A total of ${list.size} components belong to $owner, including ${
48 | list.map { it.size }.reduce { acc, l -> acc + l }.formatSize()
49 | } of resources."
50 | } else {
51 | "A total of ${list.size} components belong to $owner, including ${
52 | if (list.isEmpty()) {
53 | 0
54 | } else {
55 | list.map { it.fileList.size }.reduce { acc, l -> acc + l }
56 | }
57 | } items need to check."
58 | }
59 | }
60 | subtitle = props.appReporter.let {
61 | "Version: ${it.versionName} (${it.variantName})"
62 | }
63 | ownerList = mutableListOf("none").apply {
64 | addAll(props.appReporter.aarList.map { it.owner }.toSet())
65 | }
66 | defaultSelect = "none"
67 | onSelect = {
68 | owner = it
69 | }
70 | }
71 |
72 | AarAccordion {
73 | aarList = props.appReporter.aarList.filter {
74 | if (owner == "none") true else it.owner == owner
75 | }
76 | }
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/frontend/src/main/kotlin/top/omooo/frontend/util/Format.kt:
--------------------------------------------------------------------------------
1 | package top.omooo.frontend.util
2 |
3 | import kotlin.math.abs
4 |
5 | /**
6 | * Author: Omooo
7 | * Date: 2023/2/3
8 | * Desc: 数字相关格式化
9 | */
10 |
11 | /**
12 | * 格式化数字
13 | *
14 | * ag: 21578 字节 -> 21.1 KB
15 | * ag: -21578 字节 -> -21.1 KB
16 | */
17 | fun Number.formatSize(): String {
18 | val units = mutableListOf("B", "KB", "MB", "GB", "TB", "PB")
19 | var remainder = this.toDouble()
20 | var negative = remainder < 0
21 | if (remainder < 0) {
22 | remainder = abs(remainder)
23 | }
24 | while (remainder > BYTE_FACTOR) {
25 | remainder /= BYTE_FACTOR
26 | units.removeFirst()
27 | }
28 | return "${if (negative) "-" else ""}${remainder.asDynamic().toFixed(1)} ${units.first()}"
29 | }
30 |
31 | fun formatPercentage(fraction: Number, total: Number): String {
32 | val percentage = PERCENT_FACTOR * fraction.toDouble() / total.toDouble()
33 | return "${percentage.asDynamic().toFixed(2)} %"
34 | }
35 |
36 | private const val BYTE_FACTOR = 1024
37 |
38 | private const val PERCENT_FACTOR = 100
--------------------------------------------------------------------------------
/frontend/src/main/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Lavender-Frontend
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/frontend/src/main/resources/report.json:
--------------------------------------------------------------------------------
1 | {"key":"REPLACE_ME"}
--------------------------------------------------------------------------------
/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=-Xmx3g
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 | # display all warnings in sync.(mode: all/fail/none/summary)
24 | # https://docs.gradle.org/6.5.1/userguide/command_line_interface.html#sec:command_line_warnings
25 | Dorg.gradle.warning.mode=all
26 |
27 | #org.gradle.jvmargs=-XX:MaxPermSize=4g -XX:+HeapDumpOnOutOfMemoryError -Xmx4g -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006
28 |
29 | #org.gradle.configuration-cache=true
--------------------------------------------------------------------------------
/gradle/plugin.gradle:
--------------------------------------------------------------------------------
1 | import com.omooo.buildsrc.Config
2 |
3 | apply plugin: 'kotlin'
4 | apply plugin: 'kotlin-kapt'
5 |
6 | sourceSets {
7 | main {
8 | java {
9 | srcDirs += []
10 | }
11 | kotlin {
12 | srcDirs += ['src/main/kotlin', 'src/main/java']
13 | }
14 | }
15 | test {
16 | java {
17 | srcDirs += []
18 | }
19 | kotlin {
20 | srcDirs += ['src/main/kotlin', 'src/main/java']
21 | }
22 | }
23 | }
24 |
25 | compileKotlin {
26 | kotlinOptions.jvmTarget = JavaVersion.VERSION_17
27 | }
28 |
29 | compileTestKotlin {
30 | kotlinOptions.jvmTarget = JavaVersion.VERSION_17
31 | }
32 |
33 | dependencies {
34 | implementation "org.jetbrains.kotlin:kotlin-stdlib:${Config.kotlin_version}"
35 | implementation "org.jetbrains.kotlin:kotlin-reflect:${Config.kotlin_version}"
36 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 14 16:11:02 CST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lavender-plugin/ownership.yaml:
--------------------------------------------------------------------------------
1 | Omooo:
2 | - app-startup
3 |
4 | Android:
5 | - appcompat
6 | - material
7 | - recyclerview
8 | - fragment
9 | - constraintlayout-solver
10 | - constraintlayout
11 |
12 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk 34
8 | namespace 'com.omooo.plugin.library'
9 | defaultConfig {
10 | minSdk 21
11 | targetSdk 34
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | implementation 'androidx.core:core-ktx:1.10.1'
27 | implementation 'androidx.appcompat:appcompat:1.6.1'
28 | implementation 'com.google.android.material:material:1.10.0'
29 | }
30 |
31 | kotlin {
32 | jvmToolchain(17)
33 | }
--------------------------------------------------------------------------------
/library/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Omooo/Lavender/0a300ad745f7cb6fa6ee9094dc1e0ffdaaa7db72/library/consumer-rules.pro
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/library/src/androidTest/java/com/omooo/library/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.library
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext
22 | assertEquals("com.omooo.library.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/library/src/main/assets/library.json:
--------------------------------------------------------------------------------
1 | {
2 | ":library": [
3 | {
4 | "fileName": "library.json",
5 | "size": 4
6 | },
7 | {
8 | "fileName": "lottie/lottie.json",
9 | "size": 4
10 | }
11 | ],
12 | "app": [
13 | {
14 | "fileName": "unused.json",
15 | "size": 4
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/library/src/main/assets/lottie/lottie.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/omooo/library/LibraryActivity.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.library
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import com.omooo.plugin.library.R
6 |
7 | /**
8 | * Author: Omooo
9 | * Date: 2024/4/28
10 | * Desc:
11 | */
12 | internal class LibraryActivity : Activity() {
13 |
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | setContentView(R.layout.activity_library)
17 | }
18 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/omooo/library/LibraryMain.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.library
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 |
6 | class LibraryMain {
7 | fun show(context: Context) {
8 | Toast.makeText(context, "LibraryMain", Toast.LENGTH_SHORT).show()
9 | }
10 |
11 | fun main(context: Context) {
12 | show(context)
13 | }
14 | }
--------------------------------------------------------------------------------
/library/src/main/res/layout/activity_library.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/library/src/test/java/com/omooo/library/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.library
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 | }
--------------------------------------------------------------------------------
/plugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | src/main/resources/ownership.yaml
3 |
--------------------------------------------------------------------------------
/plugin/build.gradle:
--------------------------------------------------------------------------------
1 | //apply from: '../gradle/plugin.gradle'
2 |
3 | apply plugin: 'kotlin'
4 | apply plugin: 'kotlin-kapt'
5 | apply plugin: 'maven-publish'
6 | apply plugin: 'java-library'
7 | apply plugin: 'kotlinx-serialization'
8 |
9 | dependencies {
10 |
11 | implementation localGroovy()
12 | implementation gradleApi()
13 |
14 | implementation "com.android.tools.build:gradle-api:8.2.0"
15 | compileOnly "com.android.tools.build:gradle:8.2.0"
16 | implementation 'org.json:json:20220924'
17 | implementation "com.google.auto.service:auto-service:1.0.1"
18 | kapt "com.google.auto.service:auto-service:1.0.1"
19 | compileOnly 'com.android.tools:common:30.4.1'
20 | compileOnly 'com.android.tools:sdklib:30.4.1'
21 |
22 | implementation "org.ow2.asm:asm:9.4"
23 | implementation "org.ow2.asm:asm-tree:9.4"
24 | implementation "org.ow2.asm:asm-util:9.4"
25 | implementation "org.ow2.asm:asm-commons:9.4"
26 |
27 | implementation "org.yaml:snakeyaml:1.30"
28 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
29 | implementation("org.smali:dexlib2:2.5.2")
30 | implementation("com.android.tools.apkparser:apkanalyzer:30.1.2") {
31 | exclude group: 'com.android.tools.lint'
32 | }
33 | }
34 |
35 | //sourceSets.main {
36 | // resources.srcDir(project(":frontend").tasks.named("browserDistribution"))
37 | //}
38 |
39 | java {
40 | sourceCompatibility = JavaVersion.VERSION_17
41 | targetCompatibility = JavaVersion.VERSION_17
42 | }
43 |
44 | kotlin {
45 | jvmToolchain(17)
46 | }
47 |
48 | publishing {
49 | publications {
50 | mavenJava(MavenPublication) {
51 | from components.java
52 | groupId = 'com.omooo'
53 | artifactId = 'lavender'
54 | version = '0.0.1'
55 | }
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/bean/CheckSchemeModifiedExtension.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.bean
2 |
3 | import com.omooo.plugin.task.CheckSchemeModifiedTask
4 | import java.io.File
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2023/08/22
9 | * Desc: [CheckSchemeModifiedTask] 配置
10 | */
11 | open class CheckSchemeModifiedExtension {
12 |
13 | /** 开启该任务,默认不开启 */
14 | var enable = false
15 |
16 | /** 基线对比文件 */
17 | var baselineSchemeFile: File? = null
18 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/bean/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.bean
2 |
3 | import org.objectweb.asm.Opcodes
4 |
5 | /** LAVENDER */
6 | internal const val LAVENDER = "lavender"
7 | /** ASM 版本号 */
8 | internal const val ASM_VERSION = Opcodes.ASM9
9 |
10 | /** gradle properties key artifact id */
11 | internal const val KEY_ARTIFACT_ID = "POM_ARTIFACT_ID"
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/bean/InvokeCheckExtension.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.bean
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2022/11/6
6 | * Desc: 检测方法调用的扩展类,使用方配置
7 | */
8 | open class InvokeCheckExtension {
9 | /**
10 | * 方法调用列表(方法的全限定名)
11 | *
12 | * ag: ["android/widget/Toast#show()V", "xxx"]
13 | */
14 | var methodList = arrayOf()
15 |
16 | /**
17 | * 包名列表
18 | *
19 | * ag: ["android/", "xxx"]
20 | */
21 | var packageList = arrayOf()
22 |
23 | /**
24 | * 常量列表
25 | *
26 | * ag: ["android.permission.READ_EXTERNAL_STORAGE", "xxx"]
27 | */
28 | var constantsList = arrayOf()
29 |
30 | /**
31 | * 字段调用列表(字段的全限定名)
32 | *
33 | * ag: ["android.os.Build$VERSION.SDK_INT:I", "xxx"]
34 | */
35 | var fieldList = arrayOf()
36 |
37 |
38 |
39 | /* -------------------------- internal ----------------------- */
40 |
41 | /**
42 | * 是否开启,true: 开启
43 | * 校验条件: 检测列表有一项不为空
44 | */
45 | internal fun enable(): Boolean {
46 | return methodList.isNotEmpty() || packageList.isNotEmpty()
47 | || constantsList.isNotEmpty() || fieldList.isNotEmpty()
48 | }
49 |
50 | /**
51 | * 获取方法列表
52 | *
53 | * @return Tripe
54 | */
55 | internal fun getMethodList(): List> {
56 | return methodList.filter {
57 | it.isNotEmpty()
58 | }.map {
59 | val owner = it.substringBefore("#").replace(".", "/")
60 | val name = it.substringAfter("#", "").substringBefore("(")
61 | val desc = if (name.isNotEmpty()) it.substringAfterLast(name, "") else ""
62 | Triple(owner, name, desc)
63 | }
64 | }
65 |
66 | /**
67 | * 获取包名列表
68 | */
69 | internal fun getPackageList(): List {
70 | return packageList.filter {
71 | it.isNotEmpty()
72 | }.map {
73 | it.replace(".", "/")
74 | }
75 | }
76 |
77 | /**
78 | * 获取字段列表
79 | *
80 | * @return Tripe
81 | */
82 | internal fun getFieldList(): List> {
83 | return fieldList.filter {
84 | it.isNotEmpty()
85 | }.map {
86 | val owner = it.substringBeforeLast(".").replace(".", "/")
87 | val name = it.substringBefore(":").substringAfterLast(".")
88 | val desc = it.substringAfter(":")
89 | Triple(owner, name, desc)
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/bean/WebpToolBean.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.bean
2 |
3 | import java.io.File
4 |
5 | /**
6 | * Created by Omooo
7 | * Date: 2020-02-13
8 | * Desc: cwebp 工具路径
9 | */
10 | object WebpToolBean {
11 | private lateinit var rootDir: String
12 |
13 | fun setRootDir(rootDir: String) {
14 | this.rootDir = rootDir
15 | }
16 |
17 | fun getRootDirPath(): String {
18 | return rootDir
19 | }
20 |
21 | fun getToolsDir(): File {
22 | return File("$rootDir/tools/cwebp")
23 | }
24 |
25 | fun getToolsDirPath(): String {
26 | return "$rootDir/tools/cwebp"
27 | }
28 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/ArtifactType.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2024/5/7
6 | * Desc: 产物类型
7 | */
8 | internal enum class ArtifactType(val type: String, val prefix: String) {
9 | CLASS("android-classes", ""),
10 | RES("android-res", "res"),
11 | ASSETS("android-assets", "assets"),
12 | NATIVE_LIB("android-jni", "lib"),
13 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/aar/AarAnalyse.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.aar
2 |
3 | import com.omooo.plugin.reporter.AarAnalyseReporter
4 | import com.omooo.plugin.reporter.Insight
5 | import com.omooo.plugin.reporter.common.AarFile
6 | import com.omooo.plugin.util.getArtifactIdFromAarName
7 | import com.omooo.plugin.util.getOwnerMap
8 | import com.omooo.plugin.util.getOwnerShip
9 | import com.omooo.plugin.util.writeToJson
10 | import kotlinx.serialization.json.Json
11 | import org.gradle.api.Project
12 | import java.io.File
13 |
14 | /**
15 | * Author: Omooo
16 | * Date: 2023/07/25
17 | * Desc: AAR 配额分析
18 | */
19 | internal class AarAnalyse(private val project: Project) {
20 |
21 | private val previousDataPath: String by lazy {
22 | "${project.parent?.projectDir}/lavender-plugin/aar/previous.json"
23 | }
24 |
25 | /**
26 | * 增量、趋势分析
27 | *
28 | * @param pkgName 包名
29 | * @param currentAarPair 当前 AAR 数据
30 | *
31 | * @return 返回分析报告
32 | */
33 | fun analyse(pkgName: String, currentAarPair: Pair>): AarAnalyseReporter {
34 | val ownerMap = project.getOwnerMap()
35 | val reporter = getPreviousReporter().apply {
36 | this.packageName = pkgName
37 | if (this.aarList.getOrNull(0)?.first == currentAarPair.first) {
38 | this.aarList.removeAt(0)
39 | }
40 | this.aarList.add(0, currentAarPair)
41 | // 说明 owner 配置文件发生变更,则需要重新给 AAR 打 owner 标签
42 | if (ownerMap != this.ownerMap) {
43 | this.ownerMap = ownerMap
44 | val ownership = project.getOwnerShip()
45 | aarList.flatMap { it.second }.forEach {
46 | it.owner = ownership.getOrDefault(it.name.getArtifactIdFromAarName(), "unknown")
47 | }
48 | }
49 | }
50 | if (project.hasProperty("forceRefresh")) {
51 | val f = File(previousDataPath)
52 | if (f.exists()) {
53 | f.delete()
54 | }
55 | f.mkdirs()
56 | reporter.writeToJson(previousDataPath)
57 | }
58 | return reporter
59 | }
60 |
61 | /**
62 | * 获取上一个版本的报告
63 | */
64 | private fun getPreviousReporter(): AarAnalyseReporter {
65 | val jsonFile = project.parent?.projectDir?.resolve(previousDataPath)
66 | if (jsonFile?.exists() == true && jsonFile.readText().isNotEmpty()) {
67 | return Json.decodeFromString(AarAnalyseReporter.serializer(), jsonFile.readText())
68 | }
69 | return AarAnalyseReporter(
70 | desc = Insight.Title.AAR_ANALYSE,
71 | documentLink = Insight.DocumentLink.AAR_ANALYSE,
72 | packageName = "",
73 | aarList = ArrayList(),
74 | ownerMap = project.getOwnerMap(),
75 | )
76 | }
77 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/apk/ApkIncrementAnalyse.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.apk
2 |
3 | import com.omooo.plugin.reporter.common.AarFile
4 | import com.omooo.plugin.reporter.common.AppFile
5 | import com.omooo.plugin.reporter.common.totalSize
6 | import com.omooo.plugin.util.writeToJson
7 | import kotlinx.serialization.builtins.ListSerializer
8 | import kotlinx.serialization.json.Json
9 | import org.gradle.api.Project
10 | import java.io.File
11 |
12 | /**
13 | * Author: Omooo
14 | * Date: 2023/4/3
15 | * Desc: APK 增量分析
16 | */
17 | internal class ApkIncrementAnalyse(private val project: Project) {
18 |
19 | private val previousDataPath: String by lazy {
20 | "${project.parent?.projectDir}/lavender-plugin/apk/previous.json"
21 | }
22 |
23 | /**
24 | * 增量分析
25 | *
26 | * @return 返回差异列表
27 | */
28 | fun analyse(currentList: List): List {
29 | val previousList = getPreviousAarFileList()
30 | if (previousList.isEmpty() || project.hasProperty("forceRefresh")) {
31 | val f = File(previousDataPath)
32 | if (f.exists()) {
33 | f.delete()
34 | }
35 | f.mkdirs()
36 | return currentList.apply {
37 | writeToJson(previousDataPath)
38 | }
39 | }
40 | val map = previousList.associateBy { it.name.substringBeforeLast(":") }
41 | val result = mutableListOf()
42 | currentList.forEach { aarFile ->
43 | // 过滤掉版本号
44 | // com.xxx:xx:2.8.0 -> com.xxx:xx
45 | val aarId = aarFile.name.substringBeforeLast(":")
46 | if (map.containsKey(aarId)) {
47 | separateChange(aarFile.fileList, map[aarId]!!.fileList).takeIf {
48 | it.isNotEmpty()
49 | }?.let {
50 | result.add(
51 | AarFile(aarFile.name, it.totalSize(), aarFile.owner, it.toMutableList())
52 | )
53 | }
54 | } else {
55 | result.add(aarFile)
56 | }
57 | }
58 | return result.sortedByDescending { it.size }
59 | }
60 |
61 | /**
62 | * 分离变更
63 | */
64 | private fun separateChange(cList: List, pList: List): List {
65 | val map = pList.associateBy { it.name.substringBeforeLast(":") }
66 | return cList.filterNot {
67 | map.containsKey(it.name.substringBeforeLast(":"))
68 | }
69 | }
70 |
71 | private fun getPreviousAarFileList(): List {
72 | val jsonFile = project.parent?.projectDir?.resolve(previousDataPath)
73 | if (jsonFile?.exists() == true && jsonFile.readText().isNotEmpty()) {
74 | return Json.decodeFromString(ListSerializer(AarFile.serializer()), jsonFile.readText())
75 | }
76 | return emptyList()
77 | }
78 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/apk/ApkParser.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.apk
2 |
3 | import com.android.SdkConstants
4 | import com.android.tools.apk.analyzer.dex.DexFiles
5 | import com.omooo.plugin.reporter.common.AppFile
6 | import com.omooo.plugin.reporter.common.FileType
7 | import java.io.File
8 | import java.util.zip.ZipFile
9 |
10 | /**
11 | * Author: Omooo
12 | * Date: 2023/3/17
13 | * Desc: APK 解析
14 | */
15 | internal class ApkParser {
16 |
17 | /**
18 | * 解析 Apk
19 | */
20 | fun parse(apkFile: File): List {
21 | val result = mutableListOf()
22 | ZipFile(apkFile).use { zipFile ->
23 | zipFile.entries().iterator().forEach { entry ->
24 | if (entry.name.endsWith(SdkConstants.DOT_DEX, true)) {
25 | result.addAll(parseDex(zipFile.getInputStream(entry).readBytes()))
26 | } else {
27 | result.add(AppFile(entry.name, entry.compressedSize))
28 | }
29 | }
30 | }
31 | return result
32 | }
33 |
34 | /**
35 | * 解析 Dex 文件
36 | */
37 | private fun parseDex(byteArray: ByteArray): List {
38 | return DexFiles.getDexFile(byteArray).classes.map {
39 | AppFile(it.type, it.size.toLong(), fileType = FileType.CLASS)
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/apk/AppFileCleaner.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.apk
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.omooo.plugin.reporter.common.AppFile
6 | import com.omooo.plugin.util.project
7 | import com.omooo.plugin.util.variantImpl
8 |
9 | /**
10 | * Author: Omooo
11 | * Date: 2023/3/19
12 | * Desc: [AppFile] 清洗
13 | */
14 |
15 | internal fun List.clear(variant: Variant): List {
16 |
17 | val mappingFile = variant.variantImpl.artifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE)
18 | val clearList: List = listOf(
19 | ClassCleaner(mappingFile.get().asFile),
20 | ResourceCleaner(variant.project.buildDir, variant),
21 | TypeAssigningCleaner()
22 | )
23 | return clearList.flatMap { cleaner ->
24 | this.filter {
25 | cleaner.isApplicable(it)
26 | }.map {
27 | cleaner.clean(it)
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/apk/ClassCleaner.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.apk
2 |
3 | import com.android.tools.proguard.ProguardMap
4 | import com.omooo.plugin.reporter.common.AppFile
5 | import com.omooo.plugin.reporter.common.FileType
6 | import com.omooo.plugin.util.formatDollar
7 | import java.io.File
8 |
9 | /**
10 | * Author: Omooo
11 | * Date: 2023/3/17
12 | * Desc: 类文件解混淆
13 | */
14 | internal class ClassCleaner(mappingFile: File) : ICleaner {
15 |
16 | private val proguardMap = ProguardMap().apply {
17 | readFromFile(mappingFile)
18 | }
19 |
20 | override fun isApplicable(appFile: AppFile): Boolean {
21 | return appFile.fileType == FileType.CLASS
22 | }
23 |
24 | override fun clean(appFile: AppFile): AppFile {
25 | val className = appFile.name
26 | .removeSurrounding("L", ";")
27 | .removeSuffix(".class")
28 | .replace("/", ".")
29 | return appFile.apply {
30 | name = proguardMap.getClassName(className).formatDollar()
31 | fileType = FileType.CLASS
32 | }
33 | }
34 |
35 |
36 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/apk/ICleaner.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.apk
2 |
3 | import com.omooo.plugin.reporter.common.AppFile
4 |
5 | /**
6 | * Author: Omooo
7 | * Date: 2023/3/17
8 | * Desc: 解混淆、类型赋值等
9 | */
10 | internal interface ICleaner {
11 |
12 | fun isApplicable(appFile: AppFile): Boolean
13 |
14 | fun clean(appFile: AppFile): AppFile
15 |
16 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/apk/TypeAssigningCleaner.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.apk
2 |
3 | import com.omooo.plugin.reporter.common.AppFile
4 | import com.omooo.plugin.reporter.common.FileType
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2023/3/19
9 | * Desc: 类型赋值
10 | */
11 | internal class TypeAssigningCleaner : ICleaner {
12 |
13 | override fun isApplicable(appFile: AppFile): Boolean {
14 | return appFile.fileType == FileType.OTHER
15 | }
16 |
17 | override fun clean(appFile: AppFile): AppFile {
18 | return appFile.apply {
19 | if (name.startsWith("lib/")) {
20 | fileType = FileType.NATIVE_LIB
21 | }
22 | if (name.startsWith("assets/")) {
23 | fileType = FileType.ASSET
24 | }
25 | }
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/cha/ClassSetCache.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.cha
2 |
3 | import org.objectweb.asm.ClassReader
4 | import org.objectweb.asm.tree.ClassNode
5 | import java.io.File
6 | import java.io.FileInputStream
7 | import java.io.InputStream
8 | import java.util.zip.ZipInputStream
9 |
10 | /**
11 | * Author: Omooo
12 | * Date: 2023/3/12
13 | * Desc:
14 | */
15 | internal class ClassSetCache(private val file: File) {
16 |
17 | private val classCacheMap: Map by lazy {
18 | loadClasses(ZipInputStream(FileInputStream(file))).associateBy {
19 | it.name
20 | }
21 | }
22 |
23 | fun get(className: String): ClassNode? {
24 | return classCacheMap[className]
25 | }
26 |
27 | private fun loadClasses(zip: ZipInputStream): List {
28 | fun parse(input: InputStream): ClassNode = ClassNode().also { klass ->
29 | ClassReader(input.readBytes()).accept(klass, 0)
30 | }
31 | val classes = mutableListOf()
32 | while (true) {
33 | val entry = zip.nextEntry ?: break
34 | classes += when {
35 | entry.name.endsWith(".class", true) -> listOf(parse(zip))
36 | entry.name == "classes.jar" -> loadClasses(ZipInputStream(zip))
37 | else -> emptyList()
38 | }
39 | }
40 | return classes
41 | }
42 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/cha/ComponentHandler.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.cha
2 |
3 | import org.xml.sax.Attributes
4 | import org.xml.sax.helpers.DefaultHandler
5 | import java.io.File
6 | import javax.xml.parsers.SAXParserFactory
7 |
8 | /**
9 | * Author: Omooo
10 | * Date: 2023/3/12
11 | * Desc:
12 | */
13 | internal class ComponentHandler(private val manifest: File) : DefaultHandler() {
14 |
15 | val applications = mutableSetOf()
16 | val activities = mutableSetOf()
17 | val services = mutableSetOf()
18 | val providers = mutableSetOf()
19 | val receivers = mutableSetOf()
20 |
21 | /**
22 | * 获取组件列表
23 | *
24 | * @return {"com.xxx.MyApplication", "com.xxx.MainActivity"}
25 | */
26 | fun getComponentSet(): Set {
27 | SAXParserFactory.newInstance().newSAXParser().parse(manifest, this)
28 | return applications + activities + services + receivers
29 | }
30 |
31 | override fun startElement(
32 | uri: String,
33 | localName: String,
34 | qName: String,
35 | attributes: Attributes
36 | ) {
37 | val name: String = attributes.getValue(ATTR_NAME) ?: return
38 |
39 | when (qName) {
40 | "application" -> {
41 | applications.add(name)
42 | }
43 | "activity" -> {
44 | activities.add(name)
45 | }
46 | "service" -> {
47 | services.add(name)
48 | }
49 | "provider" -> {
50 | providers.add(name)
51 | }
52 | "receiver" -> {
53 | receivers.add(name)
54 | }
55 | }
56 | }
57 |
58 | }
59 |
60 | private const val ATTR_NAME = "android:name"
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/cha/LayoutHandler.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.cha
2 |
3 | import org.xml.sax.Attributes
4 | import org.xml.sax.helpers.DefaultHandler
5 | import java.io.File
6 | import javax.xml.parsers.SAXParserFactory
7 |
8 | /**
9 | * Author: Omooo
10 | * Date: 2023/3/13
11 | * Desc:
12 | */
13 | internal class LayoutHandler(private val layoutFile: File) : DefaultHandler() {
14 |
15 | private val views = mutableSetOf()
16 |
17 | fun getViews(): Set {
18 | SAXParserFactory.newInstance().newSAXParser().parse(layoutFile, this)
19 | return views
20 | }
21 |
22 | override fun startElement(
23 | uri: String,
24 | localName: String,
25 | qName: String,
26 | attributes: Attributes
27 | ) {
28 | views += qName
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/internal/cha/ReferenceAnalyser.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.internal.cha
2 |
3 | import org.objectweb.asm.tree.ClassNode
4 | import org.objectweb.asm.tree.FieldInsnNode
5 | import org.objectweb.asm.tree.MethodInsnNode
6 |
7 | /**
8 | * Author: Omooo
9 | * Date: 2023/3/13
10 | * Desc:
11 | */
12 | internal class ReferenceAnalyser(
13 | private val entryPoints: Set,
14 | private val classNodeMap: Map,
15 | ) {
16 |
17 | fun analyze() {
18 | entryPoints.forEach { entryPoint ->
19 | classNodeMap[entryPoint]?.methods?.forEach { methodNode ->
20 | methodNode.instructions.forEach { absInsnNode ->
21 | when (absInsnNode) {
22 | is MethodInsnNode -> {
23 | absInsnNode.name
24 | }
25 | is FieldInsnNode -> {
26 | absInsnNode.name
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
34 | private fun analyzeInternal(isMethod: Boolean, owner: String, name: String, desc: String) {
35 | if (isMethod) {
36 | classNodeMap[owner]
37 | } else {
38 |
39 | }
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/reporter/AarAnalyseReporter.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.reporter
2 |
3 | import com.omooo.plugin.reporter.common.AarFile
4 |
5 | /**
6 | * Author: Omooo
7 | * Date: 2023/7/14
8 | * Desc: AAR 分析报告
9 | */
10 | @kotlinx.serialization.Serializable
11 | internal data class AarAnalyseReporter(
12 | /** 描述信息 */
13 | val desc: String,
14 | /** 文档链接 */
15 | val documentLink: String,
16 | /** 包名 */
17 | var packageName: String,
18 | /** AAR 列表 */
19 | var aarList: ArrayList>>,
20 | /** 所属人映射 */
21 | var ownerMap: Map> = emptyMap(),
22 | )
23 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/reporter/AppReporter.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.reporter
2 |
3 | import com.omooo.plugin.reporter.common.AarFile
4 |
5 | /**
6 | * Author: Omooo
7 | * Date: 2023/2/3
8 | * Desc: App 报告类
9 | */
10 | @kotlinx.serialization.Serializable
11 | internal data class AppReporter(
12 | /** 描述信息 */
13 | val desc: String,
14 | /** 文档链接 */
15 | val documentLink: String,
16 | /** 版本号 */
17 | val versionName: String,
18 | /** 构建类型名 */
19 | val variantName: String,
20 | /** AAR 列表 */
21 | val aarList: List,
22 | )
23 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/reporter/HtmlReporter.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.reporter
2 |
3 | import groovy.json.JsonOutput
4 | import java.io.File
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2023/3/24
9 | * Desc: Html 报告生成器
10 | */
11 | internal class HtmlReporter {
12 |
13 | /**
14 | * 生成报告
15 | */
16 | fun generateReport(data: AppReporter, filePath: String): File {
17 | val file = File(filePath)
18 | if (file.exists()) {
19 | file.delete()
20 | }
21 | file.createNewFile()
22 | var html = readResourceFile("apkAnalyse-Template.html")
23 | html = html.replaceFirst("{key:\"REPLACE_ME\"}", "`${JsonOutput.toJson(data)}`")
24 | return file.apply {
25 | writeText(html)
26 | println("Reporter: ${toPath().toUri()}")
27 | }
28 | }
29 |
30 | /**
31 | * 生成 AAR 分析报告
32 | */
33 | fun generateAarAnalyseReport(data: AarAnalyseReporter, filePath: String): File {
34 | val file = File(filePath)
35 | if (file.exists()) {
36 | file.delete()
37 | }
38 | file.createNewFile()
39 | var html = readResourceFile("aarAnalyse-Template.html")
40 | html = html.replaceFirst("{key:\"REPLACE_ME\"}", "`${JsonOutput.toJson(data)}`")
41 | return file.apply {
42 | writeText(html)
43 | println("Reporter: ${toPath().toUri()}")
44 | }
45 | }
46 |
47 | private fun readResourceFile(fileName: String): String {
48 | val url = requireNotNull(javaClass.getResource("/$fileName"))
49 | return url.readText()
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/reporter/Insight.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.reporter
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/2/3
6 | * Desc: 可视化相关用到的常量
7 | */
8 |
9 | internal object Insight {
10 | /**
11 | * 文档链接
12 | */
13 | object DocumentLink {
14 | const val LIST_UNUSED_ASSETS = ""
15 | const val LIST_UNUSED_RES = ""
16 | const val INVOKE_CHECK = ""
17 | const val LIST_UNUSED_CLASS = ""
18 | const val APK_ANALYSE = ""
19 | const val DETECT_TRANSLUCENT_ACTIVITY = ""
20 | const val LIST_IMAGE = ""
21 | const val AAR_ANALYSE = ""
22 | const val CHECK_SCHEME_MODIFIED = ""
23 | const val CHECK_SERVICE_TYPE = ""
24 | const val CHECK_FRAGMENT_CONSTRUCT = ""
25 | }
26 |
27 | /**
28 | * 标题
29 | */
30 | object Title {
31 | const val LIST_UNUSED_ASSETS = "Lavender - List Unused Assets"
32 | const val LIST_UNUSED_RES = "Lavender - List Unused Res"
33 | const val INVOKE_CHECK = "Lavender - Invoke Check"
34 | const val LIST_UNUSED_CLASS = "Lavender - List Unused Class"
35 | const val APK_ANALYSE = "Lavender - Apk Analyse"
36 | const val DETECT_TRANSLUCENT_ACTIVITY = "Lavender - Detect Translucent Activity"
37 | const val LIST_IMAGE = "Lavender - List Image"
38 | const val AAR_ANALYSE = "Lavender - Aar Analyse"
39 | const val CHECK_SCHEME_MODIFIED = "Lavender - Check Scheme Modified"
40 | const val CHECK_SERVICE_TYPE = "Lavender - Check Service Type"
41 | const val CHECK_FRAGMENT_CONSTRUCT = ""
42 | }
43 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/reporter/common/AarFile.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.reporter.common
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/2/2
6 | * Desc:
7 | */
8 | @kotlinx.serialization.Serializable
9 | internal data class AarFile(
10 | val name: String,
11 | var size: Long,
12 | var owner: String,
13 | var fileList: MutableList = ArrayList(),
14 | )
15 |
16 | internal fun List.totalSize(): Long {
17 | if (isEmpty()) {
18 | return 0
19 | }
20 | return map { it.size }.reduce { acc, l -> acc + l }
21 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/reporter/common/AppFile.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.reporter.common
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/2/2
6 | * Desc: 表示一个 Apk 里的文件
7 | */
8 |
9 | @kotlinx.serialization.Serializable
10 | internal data class AppFile(
11 | var name: String,
12 | var size: Long = 0,
13 | var desc: String = "",
14 | var fileType: FileType = FileType.OTHER,
15 | )
16 |
17 | internal fun List.totalSize(): Long {
18 | if (isEmpty()) {
19 | return 0
20 | }
21 | return map { it.size }.reduce { acc, l -> acc + l }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/reporter/common/FileType.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.reporter.common
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/3/17
6 | * Desc: 文件类型
7 | */
8 | enum class FileType {
9 | CLASS,
10 | RESOURCE,
11 | ASSET,
12 | NATIVE_LIB,
13 | OTHER,
14 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/scan/BuildScan.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.scan
2 |
3 | import com.omooo.plugin.util.green
4 | import com.omooo.plugin.util.red
5 | import groovy.json.JsonOutput
6 | import org.gradle.api.Project
7 | import org.gradle.api.internal.GradleInternal
8 | import org.gradle.internal.operations.*
9 | import org.gradle.internal.operations.trace.BuildOperationTrace
10 |
11 | /**
12 | * Author: Omooo
13 | * Date: 2024/4/24
14 | * Desc:
15 | */
16 | internal class BuildScan {
17 |
18 | fun scan(project: Project) {
19 | (project.gradle as? GradleInternal)?.services?.get(BuildOperationListenerManager::class.java)
20 | ?.addListener(object : BuildOperationListener {
21 | override fun started(
22 | buildOperation: BuildOperationDescriptor,
23 | startEvent: OperationStartEvent
24 | ) {
25 | // ignore
26 | }
27 |
28 | override fun progress(
29 | operationIdentifier: OperationIdentifier,
30 | progressEvent: OperationProgressEvent
31 | ) {
32 | // ignore
33 | }
34 |
35 | override fun finished(
36 | buildOperation: BuildOperationDescriptor,
37 | finishEvent: OperationFinishEvent
38 | ) {
39 | val duration = finishEvent.endTime - finishEvent.startTime
40 | val details = BuildOperationTrace.toSerializableModel(buildOperation.details)
41 | val result = BuildOperationTrace.toSerializableModel(finishEvent.result)
42 | println("""
43 | ${green("Finished: ${buildOperation.name}")}
44 | ids: ${buildOperation.id} ${buildOperation.parentId}
45 | displayName: ${buildOperation.displayName}
46 | progressDisplayName: ${buildOperation.progressDisplayName}
47 | metadata: ${buildOperation.metadata}
48 | duration: $duration
49 | details: ${JsonOutput.toJson(details)}
50 | result: ${JsonOutput.toJson(result)}
51 | """.trimIndent())
52 | }
53 |
54 | })
55 | }
56 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/spi/VariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.spi
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.api.BaseVariant
5 | import org.gradle.api.Project
6 |
7 | /**
8 | * Author: Omooo
9 | * Date: 2019/9/27
10 | * Version: v0.1.0
11 | * Desc: Task 注册接口
12 | */
13 | interface VariantProcessor {
14 |
15 | @Deprecated(
16 | message = "BaseVariant is deprecated, please use process(variant: Variant) method instead",
17 | replaceWith = ReplaceWith(
18 | expression = "process(variant: Variant)"
19 | )
20 | )
21 | fun process(project: Project, variant: BaseVariant) = Unit
22 |
23 | fun process(variant: Variant) = Unit
24 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/AarAnalyseTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
5 | import com.omooo.plugin.internal.aar.AarAnalyse
6 | import com.omooo.plugin.reporter.HtmlReporter
7 | import com.omooo.plugin.reporter.common.AarFile
8 | import com.omooo.plugin.util.*
9 | import com.omooo.plugin.util.getArtifactIdFromAarName
10 | import com.omooo.plugin.util.getArtifactName
11 | import com.omooo.plugin.util.getOwnerShip
12 | import com.omooo.plugin.util.green
13 | import org.gradle.api.DefaultTask
14 | import org.gradle.api.tasks.Internal
15 | import org.gradle.api.tasks.TaskAction
16 | import java.io.File
17 |
18 | /**
19 | * Author: Omooo
20 | * Date: 2023/07/25
21 | * Desc: Aar 分析任务
22 | * Use: ./gradlew aarAnalyse
23 | * Output: projectDir/aarAnalyse.html
24 | */
25 | internal abstract class AarAnalyseTask : DefaultTask() {
26 | @get:Internal
27 | lateinit var variant: Variant
28 |
29 | @TaskAction
30 | fun doAction() {
31 | println(
32 | """
33 | *********************************************
34 | ********** -- AarAnalyseTask -- *************
35 | ***** -- projectDir/aarAnalyse.html -- ******
36 | *********************************************
37 | """.trimIndent()
38 | )
39 | val startTime = System.currentTimeMillis()
40 | val ownerShip = project.getOwnerShip()
41 | val aarList =
42 | variant.variantImpl.variantDependencies.getArtifactCollection(
43 | AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
44 | AndroidArtifacts.ArtifactScope.ALL,
45 | AndroidArtifacts.ArtifactType.AAR_OR_JAR
46 | ).artifacts.map { artifact ->
47 | AarFile(
48 | name = artifact.getArtifactName().removeVersionFromAarName(),
49 | size = File(artifact.file.absolutePath).length(),
50 | owner = ownerShip.getOrDefault(
51 | artifact.getArtifactName().getArtifactIdFromAarName(), "unknown"
52 | ),
53 | )
54 | }.toSet()
55 | val reporter = AarAnalyse(project).analyse(
56 | variant.variantImpl.applicationId.get(),
57 | Pair(variant.versionName, aarList.sortedByDescending { it.size })
58 | )
59 | HtmlReporter().generateAarAnalyseReport(reporter, "${project.parent?.projectDir}/aarAnalyse.html")
60 |
61 | println(green("Spend time: ${System.currentTimeMillis() - startTime}ms"))
62 | }
63 |
64 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/AarAnalyseTaskProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.omooo.plugin.spi.VariantProcessor
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.util.project
8 |
9 | /**
10 | * Author: Omooo
11 | * Date: 2023/07/25
12 | * Desc: 注册 [AarAnalyseTask]
13 | */
14 | @AutoService(VariantProcessor::class)
15 | class AarAnalyseTaskProcessor : VariantProcessor {
16 |
17 | override fun process(variant: Variant) {
18 | if (variant.project.tasks.findByName("aarAnalyse") != null) {
19 | return
20 | }
21 | variant.project.tasks.register("aarAnalyse", AarAnalyseTask::class.java) {
22 | it.variant = variant
23 | it.group = LAVENDER
24 | it.description = "Analyse the aar size in app project"
25 | }
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ApkAnalyseVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.spi.VariantProcessor
8 | import com.omooo.plugin.util.nameCapitalize
9 | import com.omooo.plugin.util.project
10 |
11 | /**
12 | * Author: Omooo
13 | * Date: 2023/3/17
14 | * Desc: 注册 [ApkAnalyseTask]
15 | */
16 | @AutoService(VariantProcessor::class)
17 | class ApkAnalyseVariantProcessor : VariantProcessor {
18 |
19 | override fun process(variant: Variant) {
20 | if (variant.name.contains("debug", true)) {
21 | return
22 | }
23 | variant.project.tasks.register(
24 | "apkAnalyseFor${variant.nameCapitalize()}",
25 | ApkAnalyseTask::class.java
26 | ) {
27 | it.variant = variant
28 | it.apkFileDir.set(variant.artifacts.get(SingleArtifact.APK))
29 | it.group = LAVENDER
30 | it.description = "Analyse the apk output from app project for ${variant.name}."
31 | it.outputs.upToDateWhen { false }
32 | }
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/CheckExportedTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.omooo.plugin.util.*
5 | import com.omooo.plugin.util.attributeMap
6 | import com.omooo.plugin.util.getArtifactName
7 | import com.omooo.plugin.util.toList
8 | import org.gradle.api.DefaultTask
9 | import org.gradle.api.artifacts.ArtifactCollection
10 | import org.gradle.api.file.RegularFileProperty
11 | import org.gradle.api.provider.Property
12 | import org.gradle.api.tasks.InputFile
13 | import org.gradle.api.tasks.Internal
14 | import org.gradle.api.tasks.TaskAction
15 | import java.io.File
16 | import javax.xml.parsers.DocumentBuilderFactory
17 |
18 | /**
19 | * Author: Omooo
20 | * Date: 2022/12/20
21 | * Desc: 检测声明了 的组件是否包含 android:exported 属性(Android 12 强制需要包含该属性)
22 | * Use: ./gradlew checkExported
23 | * Output: projectDir/checkExported.json
24 | */
25 | internal abstract class CheckExportedTask : DefaultTask() {
26 |
27 | @get:Internal
28 | lateinit var variant: Variant
29 |
30 | @get:Internal
31 | abstract val manifests: Property
32 |
33 | @get:InputFile
34 | abstract val mainManifest: Property
35 |
36 | // @get:InputFile
37 | // abstract val mergedManifest: RegularFileProperty
38 |
39 | /** 检测的节点列表 */
40 | private val checkNodeList = listOf("activity", "service", "receiver")
41 |
42 | @TaskAction
43 | fun run() {
44 | println(
45 | """
46 | *********************************************
47 | ********** -- CheckExportedTask -- **********
48 | **** -- projectDir/checkExported.json -- ****
49 | *********************************************
50 | """.trimIndent()
51 | )
52 | val appProjectResult = project.name to mainManifest.get().getComponentList()
53 | manifests.get().artifacts.associate { artifact ->
54 | artifact.getArtifactName() to artifact.file.getComponentList()
55 | }.plus(appProjectResult).filter {
56 | it.value.isNotEmpty()
57 | }.also {
58 | it.writeToJson("${project.parent?.projectDir}/checkExported.json")
59 | }
60 | }
61 |
62 | /**
63 | * 获取检测的组件列表
64 | *
65 | * 入参: Manifest 文件
66 | */
67 | private fun File.getComponentList(): List {
68 | return checkNodeList.asSequence().map {
69 | DocumentBuilderFactory.newInstance().newDocumentBuilder()
70 | .parse(this).getElementsByTagName(it).toList()
71 | }.flatten().filter {
72 | it.childNodes.toList().map { it.nodeName }.contains("intent-filter")
73 | }.filterNot {
74 | it.attributeMap().containsKey("android:exported")
75 | }.map {
76 | it.attributeMap().getOrDefault("android:name", "unknown")
77 | }.toList()
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/CheckExportedVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.Artifact
4 | import com.android.build.api.artifact.SingleArtifact
5 | import com.android.build.api.variant.Variant
6 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
7 | import com.android.build.gradle.internal.scope.InternalArtifactType
8 | import com.android.build.gradle.internal.tasks.factory.dependsOn
9 | import com.omooo.plugin.spi.VariantProcessor
10 | import com.google.auto.service.AutoService
11 | import com.omooo.plugin.bean.LAVENDER
12 | import com.omooo.plugin.util.getArtifactCollection
13 | import com.omooo.plugin.util.nameCapitalize
14 | import com.omooo.plugin.util.project
15 | import com.omooo.plugin.util.variantImpl
16 | import org.gradle.api.file.Directory
17 |
18 | /**
19 | * Author: Omooo
20 | * Date: 2022/12/20
21 | * Desc: 注册 [CheckExportedTask]
22 | */
23 | @AutoService(VariantProcessor::class)
24 | class CheckExportedVariantProcessor : VariantProcessor {
25 |
26 | override fun process(variant: Variant) {
27 | val project = variant.project
28 | project.tasks.register("checkExportedFor${variant.nameCapitalize()}", CheckExportedTask::class.java) {
29 | it.variant = variant
30 | it.manifests.set(variant.getArtifactCollection(AndroidArtifacts.ArtifactType.MANIFEST))
31 | // it.mergedManifest.set(variant.artifacts.get(SingleArtifact.MERGED_MANIFEST))
32 | it.mainManifest.set(variant.variantImpl.sources.manifestFile)
33 | it.group = LAVENDER
34 | it.description = "Check exported attribute in Manifest."
35 | it.outputs.upToDateWhen { false }
36 | }
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/CheckSchemeModifiedProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
6 | import com.android.build.gradle.internal.tasks.factory.dependsOn
7 | import com.google.auto.service.AutoService
8 | import com.omooo.plugin.bean.CheckSchemeModifiedExtension
9 | import com.omooo.plugin.bean.LAVENDER
10 | import com.omooo.plugin.spi.VariantProcessor
11 | import com.omooo.plugin.util.getArtifactCollection
12 | import com.omooo.plugin.util.nameCapitalize
13 | import com.omooo.plugin.util.project
14 | import org.gradle.api.UnknownTaskException
15 |
16 | /**
17 | * Author: Omooo
18 | * Date: 2023/8/21
19 | * Desc: 注册 [CheckSchemeModifiedTask]
20 | */
21 | @AutoService(VariantProcessor::class)
22 | class CheckSchemeModifiedProcessor : VariantProcessor {
23 |
24 | @Suppress("SwallowedException")
25 | override fun process(variant: Variant) {
26 | val project = variant.project
27 | val task = try {
28 | project.tasks.named("checkSchemeModified")
29 | } catch (e: UnknownTaskException) {
30 | project.tasks.register("checkSchemeModified") {
31 | it.group = LAVENDER
32 | it.description = "Check the schemes modified might trigger compile failure"
33 | }
34 | }
35 | project.tasks.register(
36 | "checkSchemeModifiedFor${variant.nameCapitalize()}",
37 | CheckSchemeModifiedTask::class.java
38 | ) {
39 | it.variant = variant
40 | it.manifests.set(variant.getArtifactCollection(AndroidArtifacts.ArtifactType.MANIFEST))
41 | it.mergedManifest.set(variant.artifacts.get(SingleArtifact.MERGED_MANIFEST))
42 | it.config = project.extensions.findByType(CheckSchemeModifiedExtension::class.java)
43 | ?: project.extensions.create(
44 | "checkSchemeModifiedConfig",
45 | CheckSchemeModifiedExtension::class.java
46 | )
47 | it.group = LAVENDER
48 | it.description =
49 | "Check the schemes modified might trigger compile failure for ${variant.name}."
50 | it.outputs.upToDateWhen { false }
51 | }.also {
52 | task.dependsOn(it)
53 | }
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/CheckServiceTypeVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
6 | import com.android.build.gradle.internal.tasks.factory.dependsOn
7 | import com.omooo.plugin.spi.VariantProcessor
8 | import com.google.auto.service.AutoService
9 | import com.omooo.plugin.bean.LAVENDER
10 | import com.omooo.plugin.util.getArtifactCollection
11 | import com.omooo.plugin.util.nameCapitalize
12 | import com.omooo.plugin.util.project
13 | import com.omooo.plugin.util.variantImpl
14 | import org.gradle.api.UnknownTaskException
15 |
16 | /**
17 | * Author: Omooo
18 | * Date: 2023/08/25
19 | * Desc: 注册 [CheckServiceTypeTask]
20 | */
21 | @AutoService(VariantProcessor::class)
22 | class CheckServiceTypeVariantProcessor : VariantProcessor {
23 |
24 | @Suppress("SwallowedException")
25 | override fun process(variant: Variant) {
26 | val project = variant.project
27 | val checkServiceTypeTask = try {
28 | project.tasks.named("checkServiceType")
29 | } catch (e: UnknownTaskException) {
30 | project.tasks.register("checkServiceType") {
31 | it.group = LAVENDER
32 | it.description = "Check set foreground service type attribute in Manifest."
33 | }
34 | }
35 | project.tasks.register("checkServiceTypeFor${variant.nameCapitalize()}", CheckServiceTypeTask::class.java) {
36 | it.variant = variant
37 | it.manifests.set(variant.getArtifactCollection(AndroidArtifacts.ArtifactType.MANIFEST))
38 | it.mergedManifest.set(variant.artifacts.get(SingleArtifact.MERGED_MANIFEST))
39 | it.mainManifest.set(variant.variantImpl.sources.manifestFile)
40 | it.group = LAVENDER
41 | it.description = "Check set foreground service type attribute in Manifest for ${variant.nameCapitalize()}"
42 | it.outputs.upToDateWhen { false }
43 | }.also {
44 | checkServiceTypeTask.dependsOn(it)
45 | }
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/DetectTranslucentActivityVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
6 | import com.android.build.gradle.internal.tasks.factory.dependsOn
7 | import com.google.auto.service.AutoService
8 | import com.omooo.plugin.bean.LAVENDER
9 | import com.omooo.plugin.spi.VariantProcessor
10 | import com.omooo.plugin.util.getArtifactCollection
11 | import com.omooo.plugin.util.project
12 | import com.omooo.plugin.util.variantImpl
13 | import org.gradle.api.model.ObjectFactory
14 |
15 | /**
16 | * Author: Omooo
17 | * Date: 2023/3/22
18 | * Desc: 注册 [DetectTranslucentActivityTask]
19 | */
20 | @AutoService(VariantProcessor::class)
21 | class DetectTranslucentActivityVariantProcessor : VariantProcessor {
22 |
23 | override fun process(variant: Variant) {
24 | val project = variant.project
25 | if (project.tasks.findByName("detectTranslucentActivity") != null) {
26 | return
27 | }
28 | project.tasks.register(
29 | "detectTranslucentActivity",
30 | DetectTranslucentActivityTask::class.java
31 | ) {
32 | it.variant = variant
33 | it.manifests.set(variant.getArtifactCollection(AndroidArtifacts.ArtifactType.MANIFEST))
34 | it.apkFileCollection = project.files(variant.artifacts.get(SingleArtifact.APK))
35 | it.group = LAVENDER
36 | it.description = "Detect the translucent activity from app project"
37 | it.outputs.upToDateWhen { false }
38 | }
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/FragmentNonConstructCheckVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.spi.VariantProcessor
8 | import com.omooo.plugin.util.project
9 |
10 | /**
11 | * Author: Omooo
12 | * Date: 2023/11/8
13 | * Desc: 注册 [FragmentNonConstructCheckTask]
14 | */
15 | @AutoService(VariantProcessor::class)
16 | class FragmentNonConstructCheckVariantProcessor : VariantProcessor {
17 |
18 | override fun process(variant: Variant) {
19 | val project = variant.project
20 | if (project.tasks.findByName("checkFragmentNonConstruct") != null) {
21 | return
22 | }
23 | project.tasks.register("checkFragmentNonConstruct", FragmentNonConstructCheckTask::class.java) {
24 | it.variant = variant
25 | it.apkFileCollection = project.files(variant.artifacts.get(SingleArtifact.APK))
26 | it.group = LAVENDER
27 | it.description = "Check Fragment non construct method in app project"
28 | }
29 |
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListAarSizeTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
5 | import com.omooo.plugin.util.getArtifactName
6 | import com.omooo.plugin.util.getGroupIdFromAarName
7 | import com.omooo.plugin.util.variantImpl
8 | import com.omooo.plugin.util.writeToJson
9 | import org.gradle.api.DefaultTask
10 | import org.gradle.api.tasks.Internal
11 | import org.gradle.api.tasks.TaskAction
12 | import java.io.File
13 |
14 | /**
15 | * Author: Omooo
16 | * Date: 2022/11/13
17 | * Desc: 统计依赖的 AAR 大小
18 | * Use: ./gradlew listAarSize
19 | * Output: projectDir/listAarSize.json
20 | */
21 | internal abstract class ListAarSizeTask : DefaultTask() {
22 | @get:Internal
23 | lateinit var variant: Variant
24 |
25 | @TaskAction
26 | fun doAction() {
27 | println(
28 | """
29 | *********************************************
30 | ********** -- ListAarSizeTask -- ************
31 | ***** -- projectDir/listAarSize.json -- *****
32 | *********************************************
33 | """.trimIndent()
34 | )
35 | var resultMap = mutableMapOf()
36 | variant.variantImpl.variantDependencies.getArtifactCollection(
37 | AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
38 | AndroidArtifacts.ArtifactScope.ALL,
39 | AndroidArtifacts.ArtifactType.AAR_OR_JAR
40 | ).artifacts.forEach { artifact ->
41 | val size = File(artifact.file.absolutePath).length() / 1024
42 | resultMap[artifact.getArtifactName()] = size
43 | }
44 |
45 | // 配置了 group-by 参数
46 | if (project.hasProperty("sortByGroupId")) {
47 | resultMap = resultMap
48 | .toList()
49 | .groupBy({ it.first.getGroupIdFromAarName() }) { it.second }
50 | .mapValues { (_, values) -> values.sum() }
51 | .toMutableMap()
52 | }
53 |
54 | resultMap.toList().asSequence().filter {
55 | if (project.hasProperty("groupIdPrefix")) {
56 | it.first.getGroupIdFromAarName().startsWith(project.properties["groupIdPrefix"].toString())
57 | } else {
58 | true
59 | }
60 | }.sortedByDescending { (_, value) ->
61 | value
62 | }.toMap().mapValues {
63 | "${it.value}kb"
64 | }.also {
65 | it.writeToJson("${project.parent?.projectDir}/listAarSize.json")
66 | }
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListAarSizeVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.omooo.plugin.spi.VariantProcessor
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.util.project
8 |
9 | /**
10 | * Author: Omooo
11 | * Date: 2022/11/13
12 | * Version: v0.1.0
13 | * Desc: 注册 [ListAarSizeTask]
14 | */
15 | @AutoService(VariantProcessor::class)
16 | class ListAarSizeVariantProcessor : VariantProcessor {
17 |
18 | override fun process(variant: Variant) {
19 | val project = variant.project
20 | if (project.tasks.findByName("listAarSize") != null) {
21 | return
22 | }
23 | project.tasks.register("listAarSize", ListAarSizeTask::class.java) {
24 | it.variant = variant
25 | it.group = LAVENDER
26 | it.description = "List the aar size in app project"
27 | }
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListAssetsTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
5 | import com.omooo.plugin.util.getAllChildren
6 | import com.omooo.plugin.util.getArtifactName
7 | import com.omooo.plugin.util.variantImpl
8 | import com.omooo.plugin.util.writeToJson
9 | import org.gradle.api.DefaultTask
10 | import org.gradle.api.tasks.Internal
11 | import org.gradle.api.tasks.TaskAction
12 |
13 | /**
14 | * Author: Omooo
15 | * Date: 2022/11/17
16 | * Desc: 输出所有的 Assets 资源
17 | * Use: ./gradlew listAssets
18 | * Output: projectDir/assets.json
19 | */
20 | internal abstract class ListAssetsTask : DefaultTask() {
21 | @get:Internal
22 | lateinit var variant: Variant
23 |
24 | @TaskAction
25 | fun doAction() {
26 | println(
27 | """
28 | *********************************************
29 | ********** -- ListAssetsTask -- *************
30 | ******* -- projectDir/assets.json -- ********
31 | *********************************************
32 | """.trimIndent()
33 | )
34 | getTotalAssets().writeToJson("${project.parent?.projectDir}/assets.json")
35 | }
36 |
37 | /**
38 | * 获取所有的 Assets 文件列表
39 | *
40 | * @return Map
41 | */
42 | private fun getTotalAssets(): Map> {
43 | return variant.variantImpl.variantDependencies.getArtifactCollection(
44 | AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
45 | AndroidArtifacts.ArtifactScope.ALL,
46 | AndroidArtifacts.ArtifactType.ASSETS
47 | ).artifacts.associate { artifact ->
48 | artifact.getArtifactName() to artifact.file.getAllChildren()
49 | .sortedByDescending { file ->
50 | file.length()
51 | }.map {
52 | AssetFile(it.absolutePath.substringAfterLast("out/"), it.length())
53 | }
54 | }.toMutableMap().also { map ->
55 | project.projectDir.resolve("src/main/assets").takeIf {
56 | it.isDirectory
57 | }?.getAllChildren()?.sortedByDescending { file ->
58 | file.length()
59 | }?.also {
60 | map[project.name] = it.map { file ->
61 | AssetFile(file.absolutePath.substringAfterLast("assets/"), file.length())
62 | }
63 | }
64 | }
65 | }
66 |
67 | /**
68 | * Asset 文件数据类
69 | */
70 | internal data class AssetFile(
71 | /** 文件名 */
72 | val fileName: String,
73 | /** 大小,单位 byte */
74 | val size: Long,
75 | )
76 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListAssetsVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.omooo.plugin.spi.VariantProcessor
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.util.project
8 |
9 | /**
10 | * Author: Omooo
11 | * Date: 2022/11/17
12 | * Version: v0.0.1
13 | * Desc: 注册 [ListAssetsTask]
14 | */
15 | @AutoService(VariantProcessor::class)
16 | class ListAssetsVariantProcessor : VariantProcessor {
17 |
18 | override fun process(variant: Variant) {
19 | val project = variant.project
20 | if (project.tasks.findByName("listAssets") != null) {
21 | return
22 | }
23 | project.tasks.register("listAssets", ListAssetsTask::class.java) {
24 | it.variant = variant
25 | it.group = LAVENDER
26 | it.description = "List all asset files in app project"
27 | }
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListClassOwnerMapTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.omooo.plugin.util.*
5 | import com.omooo.plugin.util.getOwnerShip
6 | import com.omooo.plugin.util.writeToJson
7 | import org.gradle.api.DefaultTask
8 | import org.gradle.api.file.FileCollection
9 | import org.gradle.api.tasks.InputFiles
10 | import org.gradle.api.tasks.Internal
11 | import org.gradle.api.tasks.TaskAction
12 |
13 | /**
14 | * Author: Omooo
15 | * Date: 2023/3/15
16 | * Desc: 输出类的归属者映射
17 | * Use: ./gradlew listClassOwnerMap
18 | * Output: projectDir/classOwnerMap.json
19 | */
20 | internal abstract class ListClassOwnerMapTask : DefaultTask() {
21 |
22 | @get:Internal
23 | lateinit var variant: Variant
24 | @get:InputFiles
25 | abstract var apkFileCollection: FileCollection
26 |
27 | @TaskAction
28 | fun run() {
29 | println(
30 | """
31 | *********************************************
32 | ******* -- ListClassOwnerMapTask -- *********
33 | **** -- projectDir/classOwnerMap.json -- ****
34 | *********************************************
35 | """.trimIndent()
36 | )
37 |
38 | val ownerShip = project.getOwnerShip()
39 | variant.getArtifactClassMap().mapValues {
40 | ownerShip.getOrDefault(it.value.first.getArtifactIdFromAarName(), "unknown")
41 | }.filterValues {
42 | it != "unknown"
43 | }.writeToJson("${project.parent?.projectDir}/classOwnerMap.json")
44 |
45 | }
46 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListClassOwnerMapVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.spi.VariantProcessor
8 | import com.omooo.plugin.util.project
9 |
10 | /**
11 | * Author: Omooo
12 | * Date: 2023/3/15
13 | * Desc: 注册 [ListClassOwnerMapTask]
14 | */
15 | @AutoService(VariantProcessor::class)
16 | class ListClassOwnerMapVariantProcessor : VariantProcessor {
17 |
18 | override fun process(variant: Variant) {
19 | val project = variant.project
20 | if (project.tasks.findByName("listClassOwnerMap") != null) {
21 | return
22 | }
23 | project.tasks.register("listClassOwnerMap", ListClassOwnerMapTask::class.java) {
24 | it.variant = variant
25 | it.apkFileCollection = project.files(variant.artifacts.get(SingleArtifact.APK))
26 | it.group = LAVENDER
27 | it.description = "List classes owner map in app project"
28 | }
29 |
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListImageTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
5 | import com.omooo.plugin.reporter.AppReporter
6 | import com.omooo.plugin.reporter.HtmlReporter
7 | import com.omooo.plugin.reporter.Insight
8 | import com.omooo.plugin.reporter.common.AarFile
9 | import com.omooo.plugin.reporter.common.AppFile
10 | import com.omooo.plugin.reporter.common.totalSize
11 | import com.omooo.plugin.util.getArtifactName
12 | import com.omooo.plugin.util.getArtifactIdFromAarName
13 | import com.omooo.plugin.util.isImageFile
14 | import com.omooo.plugin.util.getAllChildren
15 | import com.omooo.plugin.util.getOwnerShip
16 | import com.omooo.plugin.util.project
17 | import com.omooo.plugin.util.variantImpl
18 | import com.omooo.plugin.util.versionName
19 | import com.omooo.plugin.util.writeToJson
20 | import org.gradle.api.DefaultTask
21 | import org.gradle.api.tasks.Internal
22 | import org.gradle.api.tasks.TaskAction
23 |
24 | /**
25 | * Author: Omooo
26 | * Date: 2023/05/25
27 | * Desc: 输出所有的图片资源
28 | * Use: ./gradlew listImage
29 | * Output: projectDir/imageList.json
30 | */
31 | internal open class ListImageTask : DefaultTask() {
32 | @get:Internal
33 | lateinit var variant: Variant
34 |
35 | @TaskAction
36 | fun doAction() {
37 | println(
38 | """
39 | *********************************************
40 | ********** -- ListImageTask -- **************
41 | ****** -- projectDir/imageList.json -- ******
42 | *********************************************
43 | """.trimIndent()
44 | )
45 | AppReporter(
46 | desc = Insight.Title.LIST_IMAGE,
47 | documentLink = Insight.DocumentLink.LIST_IMAGE,
48 | versionName = variant.versionName,
49 | variantName = variant.name,
50 | aarList = getTotalImage(),
51 | ).apply {
52 | writeToJson("${project.parent?.projectDir}/imageList.json")
53 | HtmlReporter().generateReport(this, "${project.parent?.projectDir}/imageList.html")
54 | }
55 | }
56 |
57 | /**
58 | * 获取所有的图片
59 | */
60 | private fun getTotalImage(): List {
61 | val ownership = project.getOwnerShip()
62 | return variant.variantImpl.variantDependencies.getArtifactCollection(
63 | AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
64 | AndroidArtifacts.ArtifactScope.ALL,
65 | AndroidArtifacts.ArtifactType.ANDROID_RES
66 | ).artifacts.associate { artifact ->
67 | // 处理 App 的所有依赖
68 | artifact.getArtifactName() to artifact.file.getAllChildren()
69 | }.plus(
70 | // 加上 App 工程的
71 | project.name to project.projectDir.resolve("src/main/res").getAllChildren()
72 | ).mapValues { entry ->
73 | entry.value.filter {
74 | it.isImageFile()
75 | }.sortedByDescending {
76 | it.length()
77 | }.map {
78 | AppFile(it.name, it.length())
79 | }
80 | }.filterValues {
81 | it.isNotEmpty()
82 | }.map {
83 | val owner = ownership.getOrDefault(it.key.getArtifactIdFromAarName(), "unknown")
84 | AarFile(it.key, it.value.totalSize(), owner, it.value.toMutableList())
85 | }.sortedByDescending { it.size }
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListImageVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.tasks.factory.dependsOn
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.spi.VariantProcessor
8 | import com.omooo.plugin.util.nameCapitalize
9 | import com.omooo.plugin.util.project
10 | import org.gradle.api.UnknownTaskException
11 |
12 | /**
13 | * Author: Omooo
14 | * Date: 2023/5/25
15 | * Desc: 注册 [ListImageTask]
16 | */
17 | @Suppress("SwallowedException")
18 | @AutoService(VariantProcessor::class)
19 | class ListImageVariantProcessor : VariantProcessor {
20 |
21 | override fun process(variant: Variant) {
22 | val project = variant.project
23 | val listImageTask = try {
24 | project.tasks.named("listImage")
25 | } catch (e: UnknownTaskException) {
26 | project.tasks.register("listImage") {
27 | it.group = LAVENDER
28 | it.description = "List image in app project."
29 | }
30 | }
31 | project.tasks.register("listImageFor${variant.nameCapitalize()}", ListImageTask::class.java) {
32 | it.variant = variant
33 | it.group = LAVENDER
34 | it.description = "List image for ${variant.name}."
35 | it.outputs.upToDateWhen { false }
36 | }.also {
37 | listImageTask.dependsOn(it)
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListPackageNameTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.omooo.plugin.util.getArtifactClassMap
5 | import org.gradle.api.DefaultTask
6 | import org.gradle.api.file.FileCollection
7 | import org.gradle.api.tasks.InputFiles
8 | import org.gradle.api.tasks.Internal
9 | import org.gradle.api.tasks.TaskAction
10 | import java.io.File
11 |
12 | /**
13 | * Author: Omooo
14 | * Date: 2023/5/24
15 | * Desc: 输出业务模块的包名列表(用于 Robust 配置)
16 | * Use: ./gradlew listPackageName
17 | * Output: projectDir/packageNameList.xml
18 | */
19 | internal abstract class ListPackageNameTask : DefaultTask() {
20 |
21 | @get:Internal
22 | lateinit var variant: Variant
23 | @get:InputFiles
24 | abstract var apkFileCollection: FileCollection
25 |
26 | @TaskAction
27 | fun run() {
28 | println(
29 | """
30 | *********************************************
31 | ********* -- ListPackageNameTask -- *********
32 | **** -- projectDir/packageNameList.xml -- ***
33 | *********************************************
34 | """.trimIndent()
35 | )
36 | variant.getArtifactClassMap().keys.map {
37 | it.getPackageNameFromClassName()
38 | }.toSet().writeXml("${project.parent?.projectDir}/packageNameList.xml")
39 | }
40 |
41 | /**
42 | * 从类名中获取指定段数的包名(默认三段)
43 | *
44 | * "androidx.core.graphics.PaintKt"
45 | * n=3: "androidx.core.graphics"
46 | * n=2: "androidx.core"
47 | */
48 | private fun String.getPackageNameFromClassName(): String {
49 | val l = if (project.hasProperty("length"))
50 | project.properties["length"].toString().toInt() else DEFAULT_PACKAGE_LENGTH
51 | val segments = this.split(".")
52 | val endIndex = segments.size.coerceAtMost(l)
53 | return segments.subList(0, endIndex).joinToString(".")
54 | }
55 |
56 |
57 | /**
58 | * 写入 xml
59 | *
60 | * @param filePath 文件路径
61 | */
62 | private fun Set.writeXml(filePath: String) {
63 | val text = buildString {
64 | appendLine("""""")
65 | appendLine("")
66 | this@writeXml.forEach { s ->
67 | appendLine(" $s")
68 | }
69 |
70 | append("")
71 | }
72 | File(filePath).apply {
73 | if (exists()) {
74 | delete()
75 | }
76 | createNewFile()
77 | writeText(text)
78 | }
79 | }
80 |
81 | }
82 |
83 | private const val DEFAULT_PACKAGE_LENGTH = 3
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListPackageNameVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.android.build.gradle.internal.tasks.factory.dependsOn
6 | import com.google.auto.service.AutoService
7 | import com.omooo.plugin.bean.LAVENDER
8 | import com.omooo.plugin.spi.VariantProcessor
9 | import com.omooo.plugin.util.nameCapitalize
10 | import com.omooo.plugin.util.project
11 | import org.gradle.api.UnknownTaskException
12 |
13 | /**
14 | * Author: Omooo
15 | * Date: 2023/5/24
16 | * Desc: 注册 [ListPackageNameTask]
17 | */
18 | @Suppress("SwallowedException")
19 | @AutoService(VariantProcessor::class)
20 | class ListPackageNameVariantProcessor : VariantProcessor {
21 |
22 | override fun process(variant: Variant) {
23 | val listPackageNameTask = try {
24 | variant.project.tasks.named("listPackageName")
25 | } catch (e: UnknownTaskException) {
26 | variant.project.tasks.register("listPackageName") {
27 | it.group = LAVENDER
28 | it.description = "List package name in app project."
29 | }
30 | }
31 | variant.project.tasks.register("listPackageNameFor${variant.nameCapitalize()}", ListPackageNameTask::class.java) {
32 | it.variant = variant
33 | it.apkFileCollection = variant.project.files(variant.artifacts.get(SingleArtifact.APK))
34 | it.group = LAVENDER
35 | it.description = "List package name for ${variant.name}."
36 | it.outputs.upToDateWhen { false }
37 | }.also {
38 | listPackageNameTask.dependsOn(it)
39 | }
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListPermissionTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
5 | import com.omooo.plugin.util.getArtifactName
6 | import com.omooo.plugin.util.variantImpl
7 | import com.omooo.plugin.util.writeToJson
8 | import org.gradle.api.DefaultTask
9 | import org.gradle.api.tasks.Internal
10 | import org.gradle.api.tasks.TaskAction
11 | import java.util.regex.Pattern
12 |
13 | /**
14 | * Author: Omooo
15 | * Date: 2019/9/27
16 | * Version: v0.1.0
17 | * Desc: 输出 app 及其依赖的 aar 权限信息
18 | * Use: ./gradlew listPermissions
19 | * Output: projectDir/permissions.json
20 | */
21 | internal abstract class ListPermissionTask : DefaultTask() {
22 |
23 | @get:Internal
24 | lateinit var variant: Variant
25 |
26 | @TaskAction
27 | fun doAction() {
28 | println(
29 | """
30 | *********************************************
31 | ********* -- ListPermissionTask -- **********
32 | ***** -- projectDir/permissions.json -- *****
33 | *********************************************
34 | """.trimIndent()
35 | )
36 |
37 | val resultMap = HashMap>()
38 | // 获取 app 模块的权限
39 | getAppModulePermission(resultMap)
40 | // 获取 app 依赖的 aar 权限
41 | variant.variantImpl.variantDependencies.getArtifactCollection(
42 | AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
43 | AndroidArtifacts.ArtifactScope.ALL,
44 | AndroidArtifacts.ArtifactType.MANIFEST
45 | ).artifacts.asSequence().filter { artifact ->
46 | !resultMap.containsKey(artifact.getArtifactName())
47 | && matchPermission(artifact.file.readText()).isNotEmpty()
48 | }.forEach {
49 | resultMap[it.getArtifactName()] = matchPermission(it.file.readText())
50 | }.also {
51 | resultMap.writeToJson("${project.parent?.projectDir}/permissions.json")
52 | }
53 | if (project.hasProperty("simpleStyle")) {
54 | resultMap.values.flatten().toSet().sorted().apply {
55 | writeToJson("${project.parent?.projectDir}/permissionSet.json")
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * 获取 app 模块的权限信息
62 | */
63 | private fun getAppModulePermission(map: HashMap>) {
64 | val file = project.projectDir.resolve("src/main/AndroidManifest.xml")
65 | if (file.exists()) {
66 | map["app"] = matchPermission(file.readText())
67 | } else {
68 | println("App manifest is missing for path ${file.absolutePath}")
69 | }
70 | }
71 |
72 | /**
73 | * 根据 Manifest 文件匹配权限信息
74 | */
75 | private fun matchPermission(text: String): List {
76 | val list = ArrayList()
77 | val pattern = Pattern.compile("")
78 | val matcher = pattern.matcher(text)
79 | while (matcher.find()) {
80 | list.add(matcher.group())
81 | }
82 | return list
83 | }
84 |
85 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListPermissionVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.tasks.factory.dependsOn
5 | import com.omooo.plugin.spi.VariantProcessor
6 | import com.google.auto.service.AutoService
7 | import com.omooo.plugin.bean.LAVENDER
8 | import com.omooo.plugin.util.nameCapitalize
9 | import com.omooo.plugin.util.processManifestTaskProvider
10 | import com.omooo.plugin.util.project
11 | import org.gradle.api.UnknownTaskException
12 |
13 | /**
14 | * Author: Omooo
15 | * Date: 2019/9/27
16 | * Version: v0.1.0
17 | * Desc: 注册 ListPermissionTask
18 | * @see ListPermissionTask
19 | */
20 | @AutoService(VariantProcessor::class)
21 | class ListPermissionVariantProcessor : VariantProcessor {
22 |
23 | override fun process(variant: Variant) {
24 | val project = variant.project
25 | val listPermissionsTask = try {
26 | project.tasks.named("listPermissions")
27 | } catch (e: UnknownTaskException) {
28 | project.tasks.register("listPermissions") {
29 | it.group = LAVENDER
30 | it.description = "List the permission declared in AndroidManifest.xml"
31 | }
32 | }
33 | project.tasks.register("listPermissionsFor${variant.nameCapitalize()}", ListPermissionTask::class.java) {
34 | it.variant = variant
35 | it.group = LAVENDER
36 | it.description = "List the permission declared in AndroidManifest.xml for ${variant.name}."
37 | it.outputs.upToDateWhen { false }
38 | }.also {
39 | it.dependsOn(variant.processManifestTaskProvider)
40 | listPermissionsTask.dependsOn(it)
41 | }
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListSchemeTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.publishing.AndroidArtifacts
5 | import com.omooo.plugin.util.*
6 | import com.omooo.plugin.util.writeToJson
7 | import org.gradle.api.DefaultTask
8 | import org.gradle.api.file.FileCollection
9 | import org.gradle.api.tasks.InputFiles
10 | import org.gradle.api.tasks.Internal
11 | import org.gradle.api.tasks.TaskAction
12 |
13 | /**
14 | * Author: Omooo
15 | * Date: 2023/3/16
16 | * Desc: 输出 Manifest 里定义的 scheme 集合
17 | * Use: ./gradlew listSchemes
18 | * Output: projectDir/schemes.json
19 | */
20 | internal abstract class ListSchemeTask : DefaultTask() {
21 |
22 | @get:Internal
23 | lateinit var variant: Variant
24 | @get:InputFiles
25 | abstract var apkFileCollection: FileCollection
26 |
27 | @TaskAction
28 | fun run() {
29 | println(
30 | """
31 | *********************************************
32 | ********** -- ListSchemeTask -- *************
33 | ******* -- projectDir/schemes.json -- *******
34 | *********************************************
35 | """.trimIndent()
36 | )
37 |
38 | val startTime = System.currentTimeMillis()
39 | val ownerShip = project.getOwnerShip()
40 | val classOwnerMap = variant.getArtifactClassMap().mapValues {
41 | ownerShip.getOwner(it.value.first)
42 | }
43 | variant.getArtifactFiles(AndroidArtifacts.ArtifactType.MANIFEST)
44 | .map {
45 | it.parseSchemesFromManifest()
46 | }.filter {
47 | it.isNotEmpty()
48 | }.flatMap {
49 | it.entries
50 | }.associate {
51 | it.toPair()
52 | }.mapValues {
53 | Pair(classOwnerMap.getOrDefault(it.key, "unknown"), it.value)
54 | }.toSortedMap().writeToJson("${project.parent?.projectDir}/schemes.json")
55 | println(
56 | green("ListSchemeTask execute success in ${System.currentTimeMillis() - startTime}ms")
57 | )
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListSchemeVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.android.build.gradle.internal.tasks.factory.dependsOn
6 | import com.google.auto.service.AutoService
7 | import com.omooo.plugin.bean.LAVENDER
8 | import com.omooo.plugin.spi.VariantProcessor
9 | import com.omooo.plugin.util.nameCapitalize
10 | import com.omooo.plugin.util.processManifestTaskProvider
11 | import com.omooo.plugin.util.project
12 | import org.gradle.api.UnknownTaskException
13 |
14 | /**
15 | * Author: Omooo
16 | * Date: 2023/3/16
17 | * Desc: 注册 [ListSchemeTask]
18 | */
19 | @AutoService(VariantProcessor::class)
20 | class ListSchemeVariantProcessor : VariantProcessor {
21 |
22 | @Suppress("SwallowedException")
23 | override fun process(variant: Variant) {
24 | val project = variant.project
25 | val listPermissionsTask = try {
26 | project.tasks.named("listSchemes")
27 | } catch (e: UnknownTaskException) {
28 | project.tasks.register("listSchemes") {
29 | it.group = LAVENDER
30 | it.description = "List the schemes declared in AndroidManifest.xml"
31 | }
32 | }
33 | project.tasks.register("listSchemesFor${variant.nameCapitalize()}", ListSchemeTask::class.java) {
34 | it.variant = variant
35 | it.apkFileCollection = project.files(variant.artifacts.get(SingleArtifact.APK))
36 | it.group = LAVENDER
37 | it.description = "List the schemes declared in AndroidManifest.xml for ${variant.name}."
38 | it.outputs.upToDateWhen { false }
39 | }.also {
40 | // 因为要归属是负责该 scheme,所以需要依赖 JarTask
41 | it.dependsOn(variant.processManifestTaskProvider)
42 | listPermissionsTask.dependsOn(it)
43 | }
44 |
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListUnusedAssetsVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.android.build.gradle.internal.tasks.factory.dependsOn
6 | import com.omooo.plugin.spi.VariantProcessor
7 | import com.google.auto.service.AutoService
8 | import com.omooo.plugin.bean.LAVENDER
9 | import com.omooo.plugin.util.project
10 |
11 | /**
12 | * Author: Omooo
13 | * Date: 2022/01/11
14 | * Desc: 注册 [ListUnusedAssetsTask]
15 | */
16 | @AutoService(VariantProcessor::class)
17 | class ListUnusedAssetsVariantProcessor : VariantProcessor {
18 |
19 | override fun process(variant: Variant) {
20 | val project = variant.project
21 | if (variant.name.lowercase().contains("debug")) {
22 | return
23 | }
24 | if (project.tasks.findByName("listUnusedAssets") != null) {
25 | return
26 | }
27 | project.tasks.register("listUnusedAssets", ListUnusedAssetsTask::class.java) {
28 | it.variant = variant
29 | it.apkFileCollection = project.files(variant.artifacts.get(SingleArtifact.APK))
30 | it.group = LAVENDER
31 | it.description = "List unused assets in app project"
32 | }.also {
33 | // it.dependsOn(project.tasks.named("assembleRelease"))
34 | // it.get().mustRunAfter(project.tasks.named("assembleRelease").get())
35 | }
36 |
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListUnusedClassVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.spi.VariantProcessor
8 | import com.omooo.plugin.util.nameCapitalize
9 | import com.omooo.plugin.util.project
10 |
11 | /**
12 | * Author: Omooo
13 | * Date: 2023/3/8
14 | * Desc: 注册 [ListUnusedClassTask]
15 | */
16 | @AutoService(VariantProcessor::class)
17 | class ListUnusedClassVariantProcessor : VariantProcessor {
18 |
19 | override fun process(variant: Variant) {
20 | if (variant.name.contains("debug", true)){
21 | return
22 | }
23 | val project = variant.project
24 | project.tasks.register("listUnusedClassFor${variant.nameCapitalize()}", ListUnusedClassTask::class.java) {
25 | it.variant = variant
26 | it.apkFileCollection = variant.project.files(variant.artifacts.get(SingleArtifact.APK))
27 | it.group = LAVENDER
28 | it.description = "List unused class declared in application project for ${variant.name}."
29 | it.outputs.upToDateWhen { false }
30 | }
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/ListUnusedResVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.Variant
5 | import com.android.build.gradle.internal.tasks.factory.dependsOn
6 | import com.omooo.plugin.spi.VariantProcessor
7 | import com.google.auto.service.AutoService
8 | import com.omooo.plugin.bean.LAVENDER
9 | import com.omooo.plugin.util.nameCapitalize
10 | import com.omooo.plugin.util.project
11 | import org.gradle.api.DefaultTask
12 | import org.gradle.api.UnknownTaskException
13 | import org.gradle.api.tasks.Internal
14 | import org.gradle.api.tasks.TaskAction
15 | import java.io.File
16 |
17 | /**
18 | * Author: Omooo
19 | * Date: 2022/12/14
20 | * Desc: 注册 [ListUnusedResTask]
21 | */
22 | @AutoService(VariantProcessor::class)
23 | class ListUnusedResVariantProcessor : VariantProcessor {
24 |
25 | override fun process(variant: Variant) {
26 | if (variant.name.contains("debug", true)) {
27 | return
28 | }
29 | val project = variant.project
30 | val listUnusedResTask = try {
31 | project.tasks.named("listUnusedRes")
32 | } catch (e: UnknownTaskException) {
33 | project.tasks.register("listUnusedRes") {
34 | it.group = LAVENDER
35 | it.description = "List unused res in app project"
36 | }
37 | }
38 | project.tasks.register("listUnusedResFor${variant.nameCapitalize()}", ListUnusedResTask::class.java) {
39 | it.variant = variant
40 | it.apkFileCollection = project.files(variant.artifacts.get(SingleArtifact.APK))
41 | it.group = LAVENDER
42 | it.description = "List unused res for ${variant.name} in app project"
43 | }.also {
44 | listUnusedResTask.dependsOn(it)
45 | }
46 |
47 | if (project.properties.containsKey("strictMode")) {
48 | project.tasks.register("strictTaskInternalFor${variant.nameCapitalize()}", StrictTask::class.java) {
49 | it.variant = variant
50 | }.also {
51 | // project.tasks.named("shrink${variant.nameCapitalize()}Res").apply {
52 | // this.dependsOn(it)
53 | // this.get().mustRunAfter(it)
54 | // }
55 | }
56 | }
57 |
58 | }
59 |
60 | }
61 |
62 | /**
63 | * 设置 shrinkResources 严格模式 Task
64 | */
65 | internal open class StrictTask : DefaultTask() {
66 |
67 | @get:Internal
68 | lateinit var variant: Variant
69 |
70 | @TaskAction
71 | fun run() {
72 | val resDir = File("${project.buildDir.absolutePath}/intermediates/merged-not-compiled-resources/${variant.flavorName}/${variant.buildType}")
73 | if (!resDir.exists() && !resDir.isDirectory) {
74 | println("merged-not-compiled-resources/${variant.flavorName}/${variant.buildType} dir is not exists.")
75 | return
76 | }
77 | File("$resDir/xml").apply {
78 | if (!exists()) {
79 | mkdir()
80 | }
81 | File(this, "lavender-keep-${System.currentTimeMillis()}.xml")
82 | .writeText(KEEP_STRICT_RES_CONTENT)
83 | }
84 | }
85 |
86 | }
87 |
88 | private const val KEEP_STRICT_RES_CONTENT = """
89 |
92 | """
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/RepeatResDetectorTask.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.ANDROID_RES
5 | import com.omooo.plugin.util.encode
6 | import com.omooo.plugin.util.getArtifactFiles
7 | import com.omooo.plugin.util.writeToJson
8 | import org.gradle.api.DefaultTask
9 | import org.gradle.api.tasks.Internal
10 | import org.gradle.api.tasks.TaskAction
11 | import java.io.File
12 |
13 | /**
14 | * Author: Omooo
15 | * Date: 2019/9/27
16 | * Version: v0.1.0
17 | * Desc: 重复资源监测
18 | * Use: ./gradlew detectRepeatRes
19 | * Output: projectDir/repeatRes.json
20 | */
21 | internal abstract class RepeatResDetectorTask : DefaultTask() {
22 |
23 | @get:Internal
24 | lateinit var variant: Variant
25 |
26 | @TaskAction
27 | fun run() {
28 | println(
29 | """
30 | *********************************************
31 | ******** -- RepeatResDetectorTask -- ********
32 | ****** -- projectDir/repeatRes.json -- ******
33 | *********************************************
34 | """.trimIndent()
35 | )
36 |
37 | val resultMap = HashMap>()
38 | val prefix = if (project.properties["all"] != "true") "drawable-" else "drawable"
39 |
40 | variant.getArtifactFiles(ANDROID_RES).plus(project.projectDir.resolve("src/main/res")).forEach { resDir ->
41 | resDir.listFiles()?.filter {
42 | it.isDirectory && it.name.startsWith(prefix)
43 | }?.forEach { drawableDir ->
44 | drawableDir.listFiles()?.filter {
45 | !it.isDirectory
46 | }?.forEach { file ->
47 | resultMap.getOrDefault(file.readBytes().encode(), arrayListOf()).apply {
48 | add(file.absolutePath)
49 | }.also {
50 | resultMap[file.readBytes().encode()] = it
51 | }
52 | }
53 | }
54 | }
55 |
56 | var totalSize: Long = 0
57 | resultMap.filterValues { values ->
58 | values.size > 1
59 | }.apply {
60 | this.values.forEach {
61 | totalSize += File(it[0]).length()
62 | }
63 |
64 | println("Repeat Res count: ${keys.size}, total size: ${totalSize / 1000}kb")
65 | this.writeToJson("${project.parent?.projectDir}/repeatRes.json")
66 | }
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/task/RepeatResDetectorVariantProcessor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.task
2 |
3 | import com.android.build.api.variant.Variant
4 | import com.omooo.plugin.spi.VariantProcessor
5 | import com.google.auto.service.AutoService
6 | import com.omooo.plugin.bean.LAVENDER
7 | import com.omooo.plugin.util.project
8 |
9 | /**
10 | * Author: Omooo
11 | * Date: 2019/9/27
12 | * Version: v0.1.0
13 | * Desc: 注册 RepeatResDetectorTask
14 | * @see RepeatResDetectorTask
15 | */
16 | @AutoService(VariantProcessor::class)
17 | class RepeatResDetectorVariantProcessor : VariantProcessor {
18 | override fun process(variant: Variant) {
19 | val project = variant.project
20 | if (project.tasks.findByName("repeatRes") != null) {
21 | return
22 | }
23 | project.tasks.register("repeatRes", RepeatResDetectorTask::class.java) {
24 | it.variant = variant
25 | it.group = LAVENDER
26 | it.description = "Check repeat resources in app project"
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/BaseClassNode.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform
2 |
3 | import com.omooo.plugin.bean.ASM_VERSION
4 | import org.objectweb.asm.ClassVisitor
5 | import org.objectweb.asm.tree.ClassNode
6 |
7 | /**
8 | * Author: Omooo
9 | * Date: 2022/11/6
10 | * Desc: 使用 [ClassNode] 操作字节码基础类
11 | */
12 | internal abstract class BaseClassNode(private val classVisitor: ClassVisitor) :
13 | ClassNode(ASM_VERSION) {
14 |
15 | override fun visitEnd() {
16 | super.visitEnd()
17 | accept(classVisitor)
18 | transform()
19 | }
20 |
21 | abstract fun transform()
22 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/BaseClassVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform
2 |
3 | import com.omooo.plugin.bean.ASM_VERSION
4 | import org.objectweb.asm.ClassVisitor
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2022/11/6
9 | * Desc: 使用 [ClassVisitor] 操作字节码基础类
10 | */
11 | internal abstract class BaseClassVisitor(classVisitor: ClassVisitor) :
12 | ClassVisitor(ASM_VERSION, classVisitor) {
13 |
14 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/invoke/InvokeCheckClassNode.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform.invoke
2 |
3 | import com.omooo.plugin.transform.BaseClassNode
4 | import com.omooo.plugin.util.TransformReporter
5 | import org.objectweb.asm.ClassVisitor
6 | import org.objectweb.asm.tree.FieldInsnNode
7 | import org.objectweb.asm.tree.LdcInsnNode
8 | import org.objectweb.asm.tree.MethodInsnNode
9 | import org.objectweb.asm.tree.MethodNode
10 |
11 | /**
12 | * Author: Omooo
13 | * Date: 2022/11/6
14 | * Desc: 检测方法调用
15 | */
16 | internal class InvokeCheckClassNode(
17 | classVisitor: ClassVisitor,
18 | private val params: InvokeCheckParams
19 | ) : BaseClassNode(classVisitor) {
20 |
21 | override fun transform() {
22 | methods.forEach { methodNode ->
23 | methodNode.instructions.filterIsInstance().forEach { insnNode ->
24 | params.packageList.filter {
25 | !name.startsWith(it) && insnNode.owner.startsWith(it)
26 | }.forEach {
27 | report(it.replace("/", "."), methodNode.getMethodPlainText())
28 | }
29 | params.methodList.filter {
30 | // owner 是必须要有的,name 和 desc 可有可无
31 | insnNode.owner == it.first
32 | && (if (it.second.isNotEmpty()) insnNode.name == it.second else true)
33 | && (if (it.third.isNotEmpty()) insnNode.desc == it.third else true)
34 | }.map {
35 | if (it.second.isNotEmpty()) "${it.first.replace("/", ".")}#${it.second}${it.third}"
36 | else it.first.replace("/", ".")
37 | }.forEach {
38 | report(it, methodNode.getMethodPlainText())
39 | }
40 | }
41 |
42 | // 常量检测
43 | if (params.constantsList.isNotEmpty()) {
44 | methodNode.instructions.filterIsInstance().filter {
45 | params.constantsList.contains(it.cst)
46 | }.forEach {
47 | report(it.cst.toString(), methodNode.getMethodPlainText())
48 | }
49 | }
50 |
51 | // 字段检测
52 | if (params.fieldList.isNotEmpty()) {
53 | methodNode.instructions.filterIsInstance().forEach { fieldNode ->
54 | params.fieldList.filter {
55 | it.first == fieldNode.owner && it.second == fieldNode.name && it.third == fieldNode.desc
56 | }.map {
57 | "${it.first.replace("/", ".")}.${it.second}:${it.third}"
58 | }.forEach {
59 | report(it, methodNode.getMethodPlainText())
60 | }
61 | }
62 | }
63 | }
64 | }
65 |
66 | /**
67 | * 获取方法调用的文本描述
68 | *
69 | * ag: android.widget.Toast#show()V
70 | */
71 | private fun MethodNode.getMethodPlainText(): String {
72 | return "${this@InvokeCheckClassNode.name.replace("/", ".")}#${name}${desc}"
73 | }
74 |
75 | /**
76 | * 输出报告
77 | *
78 | * @param key 待检测的 method/package
79 | * @param text 调用方
80 | */
81 | private fun report(key: String, text: String) {
82 | TransformReporter.writeJsonLineByLine(REPORTER_FILE_NAME, key, text)
83 | }
84 |
85 | }
86 |
87 | /** 输出报告的文件名 */
88 | private const val REPORTER_FILE_NAME = "invokeCheckReport.json"
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/invoke/InvokeCheckCvFactory.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform.invoke
2 |
3 | import com.android.build.api.instrumentation.*
4 | import com.omooo.plugin.util.isRClass
5 | import com.omooo.plugin.util.isSystemClass
6 | import org.objectweb.asm.ClassVisitor
7 |
8 | /**
9 | * Author: Omooo
10 | * Date: 2022/11/5
11 | * Desc: 通用 ClassVisitorFactory
12 | */
13 | abstract class InvokeCheckCvFactory : AsmClassVisitorFactory {
14 |
15 | override fun createClassVisitor(
16 | classContext: ClassContext,
17 | nextClassVisitor: ClassVisitor
18 | ): ClassVisitor {
19 | if (classContext.currentClassData.isRClass()
20 | || classContext.currentClassData.isSystemClass()
21 | ) {
22 | return nextClassVisitor
23 | }
24 | return InvokeCheckClassNode(nextClassVisitor, parameters.get())
25 | }
26 |
27 | override fun isInstrumentable(classData: ClassData): Boolean {
28 | return true
29 | }
30 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/invoke/InvokeCheckParams.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform.invoke
2 |
3 | import com.android.build.api.instrumentation.InstrumentationParameters
4 | import org.gradle.api.tasks.Input
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2022/11/6
9 | * Desc: 检测方法、常量等调用的参数
10 | */
11 | internal interface InvokeCheckParams : InstrumentationParameters {
12 |
13 | @get:Input
14 | var methodList: List>
15 |
16 | @get:Input
17 | var packageList: List
18 |
19 | @get:Input
20 | var constantsList: List
21 |
22 | @get:Input
23 | var fieldList: List>
24 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/shrinkres/IdentifierCheckClassNode.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform.shrinkres
2 |
3 | import com.omooo.plugin.transform.BaseClassNode
4 | import com.omooo.plugin.util.TransformReporter
5 | import org.objectweb.asm.ClassVisitor
6 | import org.objectweb.asm.Opcodes
7 | import org.objectweb.asm.tree.AbstractInsnNode
8 | import org.objectweb.asm.tree.LdcInsnNode
9 | import org.objectweb.asm.tree.MethodInsnNode
10 | import org.objectweb.asm.tree.MethodNode
11 |
12 | /**
13 | * Author: Omooo
14 | * Date: 2022/12/9
15 | * Desc: resources.getIdentifier 调用检查
16 | */
17 | internal class IdentifierCheckClassNode(classVisitor: ClassVisitor) : BaseClassNode(classVisitor) {
18 |
19 | override fun transform() {
20 | methods.filter {
21 | it.name == "onCreate"
22 | }.forEach { methodNode ->
23 | methodNode.instructions.forEachIndexed { index, insnNode ->
24 | // 从 Resources#getIdentifier 调用点开始往前找到第一个 Idc 指令
25 | // 直到遇到 getResources() 调用点结束
26 | if (insnNode.isInvokeGetIdentifier()) {
27 | report(methodNode, "")
28 | for (i in index - 1 downTo 0) {
29 | val insn = methodNode.instructions[i]
30 | if (insn is LdcInsnNode && insn.cst is String) {
31 | report(methodNode, insn.cst.toString())
32 | }
33 | // if (insn.isInvokeGetResources()) {
34 | // break
35 | // }
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
42 | /**
43 | * 上报
44 | *
45 | * @param methodNode [MethodNode] 调用点
46 | * @param resName assets 文件名
47 | */
48 | private fun report(methodNode: MethodNode, resName: String) {
49 | TransformReporter.writeJsonLineByLine("resIdentifier.json", "$name#${methodNode.name}${methodNode.desc}", resName)
50 | }
51 |
52 | private fun AbstractInsnNode.isInvokeGetIdentifier(): Boolean {
53 | return this is MethodInsnNode
54 | && opcode == Opcodes.INVOKEVIRTUAL
55 | && owner == OWNER_RESOURCES
56 | && name == METHOD_NAME_GET_IDENTIFIER
57 | && desc == METHOD_DESC_GET_IDENTIFIER
58 | }
59 |
60 | private fun AbstractInsnNode.isInvokeGetResources(): Boolean {
61 | return this is MethodInsnNode
62 | && opcode == Opcodes.INVOKEVIRTUAL
63 | && name == METHOD_NAME_GET_RESOURCES
64 | && desc == METHOD_DESC_GET_RESOURCES
65 | }
66 | }
67 |
68 | private const val OWNER_RESOURCES = "android/content/res/Resources"
69 | private const val METHOD_NAME_GET_IDENTIFIER = "getIdentifier"
70 | private const val METHOD_DESC_GET_IDENTIFIER =
71 | "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"
72 |
73 | private const val METHOD_NAME_GET_RESOURCES = "getResources"
74 | private const val METHOD_DESC_GET_RESOURCES = "()Landroid/content/res/Resources;"
75 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/shrinkres/IdentifierCheckCvFactory.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform.shrinkres
2 |
3 | import com.android.build.api.instrumentation.AsmClassVisitorFactory
4 | import com.android.build.api.instrumentation.ClassContext
5 | import com.android.build.api.instrumentation.ClassData
6 | import com.android.build.api.instrumentation.InstrumentationParameters
7 | import com.omooo.plugin.util.isRClass
8 | import com.omooo.plugin.util.isSystemClass
9 | import org.objectweb.asm.ClassVisitor
10 |
11 | /**
12 | * Author: Omooo
13 | * Date: 2022/12/09
14 | * Desc: resources.getIdentifier 调用检查
15 | */
16 | abstract class IdentifierCheckCvFactory :
17 | AsmClassVisitorFactory {
18 |
19 | override fun createClassVisitor(
20 | classContext: ClassContext,
21 | nextClassVisitor: ClassVisitor
22 | ): ClassVisitor {
23 | if (classContext.currentClassData.isRClass()
24 | || classContext.currentClassData.isSystemClass()
25 | ) {
26 | return nextClassVisitor
27 | }
28 | return IdentifierCheckClassNode(nextClassVisitor)
29 | }
30 |
31 | override fun isInstrumentable(classData: ClassData): Boolean {
32 | return true
33 | }
34 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/systrace/SystraceClassVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform.systrace
2 |
3 | import com.omooo.plugin.bean.ASM_VERSION
4 | import com.omooo.plugin.transform.BaseClassVisitor
5 | import org.objectweb.asm.ClassVisitor
6 | import org.objectweb.asm.MethodVisitor
7 | import org.objectweb.asm.Opcodes
8 | import org.objectweb.asm.commons.AdviceAdapter
9 | import java.lang.reflect.Modifier
10 |
11 | /**
12 | * Author: Omooo
13 | * Date: 2022/12/28
14 | * Desc:
15 | */
16 | internal class SystraceClassVisitor(classVisitor: ClassVisitor) : BaseClassVisitor(classVisitor) {
17 |
18 | var traceClassFlag = false
19 |
20 | var className = ""
21 |
22 | override fun visit(
23 | version: Int,
24 | access: Int,
25 | name: String,
26 | signature: String?,
27 | superName: String?,
28 | interfaces: Array?
29 | ) {
30 | traceClassFlag = !Modifier.isAbstract(access) && !Modifier.isInterface(access)
31 | && !Modifier.isNative(access) && 0 == (access and Opcodes.ACC_ANNOTATION)
32 | className = name
33 | super.visit(version, access, name, signature, superName, interfaces)
34 | }
35 |
36 | override fun visitMethod(
37 | access: Int,
38 | name: String,
39 | descriptor: String,
40 | signature: String?,
41 | exceptions: Array?
42 | ): MethodVisitor {
43 | if (!traceClassFlag || Modifier.isAbstract(access) || Modifier.isInterface(access)
44 | || Modifier.isNative(access)
45 | || name == "" || name == ""
46 | ) {
47 | return super.visitMethod(access, name, descriptor, signature, exceptions)
48 | }
49 | val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
50 | return InternalMethodVisitor(className, methodVisitor, access, name, descriptor)
51 | }
52 |
53 | class InternalMethodVisitor(
54 | private val className: String,
55 | methodVisitor: MethodVisitor,
56 | access: Int,
57 | private val methodName: String,
58 | descriptor: String
59 | ) :
60 | AdviceAdapter(ASM_VERSION, methodVisitor, access, methodName, descriptor) {
61 |
62 | override fun onMethodEnter() {
63 | println("植入成功_: $className#$methodName")
64 | val sectionName = "$className#$methodName".let {
65 | if (it.length > 127) "${it.substring(0, 124)}..." else it
66 | }
67 | mv.visitLdcInsn(sectionName)
68 | mv.visitMethodInsn(
69 | Opcodes.INVOKESTATIC,
70 | "android/os/Trace",
71 | "beginSection",
72 | "(Ljava/lang/String;)V",
73 | false
74 | )
75 | super.onMethodEnter()
76 | }
77 |
78 | override fun onMethodExit(opcode: Int) {
79 | mv.visitMethodInsn(
80 | Opcodes.INVOKESTATIC,
81 | "android/os/Trace",
82 | "endSection",
83 | "()V",
84 | false
85 | )
86 | super.onMethodExit(opcode)
87 | }
88 |
89 | }
90 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/transform/systrace/SystraceCvFactory.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.transform.systrace
2 |
3 | import com.android.build.api.instrumentation.AsmClassVisitorFactory
4 | import com.android.build.api.instrumentation.ClassContext
5 | import com.android.build.api.instrumentation.ClassData
6 | import com.android.build.api.instrumentation.InstrumentationParameters
7 | import com.omooo.plugin.bean.ASM_VERSION
8 | import com.omooo.plugin.util.*
9 | import com.omooo.plugin.util.isInstanceMethod
10 | import com.omooo.plugin.util.isMethodReturn
11 | import com.omooo.plugin.util.isRClass
12 | import org.objectweb.asm.ClassVisitor
13 | import org.objectweb.asm.Opcodes
14 | import org.objectweb.asm.tree.*
15 | import java.lang.reflect.Modifier
16 |
17 | /**
18 | * Author: Omooo
19 | * Date: 2022/12/28
20 | * Desc: Systrace 自定义插桩
21 | */
22 | abstract class SystraceCvFactory :
23 | AsmClassVisitorFactory {
24 |
25 | override fun createClassVisitor(
26 | classContext: ClassContext,
27 | nextClassVisitor: ClassVisitor
28 | ): ClassVisitor {
29 | if (classContext.currentClassData.isRClass()
30 | || classContext.currentClassData.isSystemClass()
31 | || classContext.currentClassData.className.startsWith("org.bouncycastle")
32 | ) {
33 | return nextClassVisitor
34 | }
35 | return SystraceClassVisitor(nextClassVisitor)
36 | }
37 |
38 | override fun isInstrumentable(classData: ClassData): Boolean {
39 | return true
40 | }
41 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/util/ClassDataExt.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.util
2 |
3 | import com.android.build.api.instrumentation.ClassData
4 |
5 | /**
6 | * 是否是 R 文件
7 | *
8 | * @return true: R 文件
9 | */
10 | internal fun ClassData.isRClass(): Boolean {
11 | return className.split(".").lastOrNull()?.let {
12 | it == "R" || it.startsWith("R$")
13 | } ?: false
14 | }
15 |
16 | /**
17 | * 是否是系统类
18 | *
19 | * @return true: 系统类
20 | */
21 | internal fun ClassData.isSystemClass(): Boolean {
22 | val filterList = arrayOf(
23 | "kotlin.", "org.intellij.", "androidx.",
24 | "com.google.", "org.jetbrains.", "android.",
25 | )
26 | return filterList.find {
27 | className.startsWith(it)
28 | } != null
29 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/util/ClassNodeExt.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.util
2 |
3 | import org.objectweb.asm.Opcodes
4 | import org.objectweb.asm.tree.ClassNode
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2022/12/28
9 | * Desc: ClassNode 扩展方法
10 | */
11 |
12 | val ClassNode.className: String
13 | get() = name.replace('/', '.')
14 |
15 | val ClassNode.isAnnotation: Boolean
16 | get() = 0 != (access and Opcodes.ACC_ANNOTATION)
17 |
18 | val ClassNode.isInterface: Boolean
19 | get() = 0 != (access and Opcodes.ACC_INTERFACE)
20 |
21 | val ClassNode.isAbstract: Boolean
22 | get() = 0 != (access and Opcodes.ACC_ABSTRACT)
23 |
24 | val ClassNode.isPublic: Boolean
25 | get() = 0 != (access and Opcodes.ACC_PUBLIC)
26 |
27 | val ClassNode.isProtected: Boolean
28 | get() = 0 != (access and Opcodes.ACC_PROTECTED)
29 |
30 | val ClassNode.isPrivate: Boolean
31 | get() = 0 != (access and Opcodes.ACC_PRIVATE)
32 |
33 | val ClassNode.isStatic: Boolean
34 | get() = 0 != (access and Opcodes.ACC_STATIC)
35 |
36 | val ClassNode.isFinal: Boolean
37 | get() = 0 != (access and Opcodes.ACC_FINAL)
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/util/CollectionsExt.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.util
2 |
3 | import org.json.JSONArray
4 | import org.json.JSONObject
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2022/11/16
9 | * Desc: 集合相关扩展方法
10 | */
11 |
12 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/util/ConsoleExt.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.util
2 |
3 | /**
4 | * Author: Omooo
5 | * Date: 2023/3/12
6 | * Desc: 日志输出文本颜色设置
7 | */
8 |
9 | private const val ESC = '\u001B'
10 | private const val CSI_RESET = "$ESC[0m"
11 | private const val CSI_RED = "$ESC[31m"
12 | private const val CSI_GREEN = "$ESC[32m"
13 | private const val CSI_YELLOW = "$ESC[33m"
14 |
15 | internal fun red(s: Any) = "${CSI_RED}${s}${CSI_RESET}"
16 | internal fun green(s: Any) = "${CSI_GREEN}${s}${CSI_RESET}"
17 | internal fun yellow(s: Any) = "${CSI_YELLOW}${s}${CSI_RESET}"
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/util/InsnNodeExt.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.util
2 |
3 | import org.objectweb.asm.tree.MethodInsnNode
4 |
5 | /**
6 | * [MethodInsnNode] 转文本
7 | */
8 | internal fun MethodInsnNode.toPlainText(): String {
9 | return "$owner#$name$desc"
10 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/util/ManifestFileExt.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.util
2 |
3 | import org.w3c.dom.Element
4 | import java.io.File
5 | import javax.xml.parsers.DocumentBuilderFactory
6 |
7 | /**
8 | * Author: Omooo
9 | * Date: 2023/8/21
10 | * Desc: Manifest 文件解析相关扩展方法
11 | */
12 |
13 | /**
14 | * 解析 Manifest 文件
15 | *
16 | * @return { "com.xxx.SampleActivity": "scheme://home/mall, scheme://home/mine" }
17 | */
18 | @Suppress("NestedBlockDepth")
19 | internal fun File.parseSchemesFromManifest(): Map {
20 | val result = mutableMapOf()
21 | DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(this).apply {
22 | val manifestNode = getElementsByTagName("manifest").item(0) as Element
23 | val packageName = manifestNode.getAttribute("package")
24 |
25 | val activityNodes = getElementsByTagName("activity")
26 | for (i in 0 until activityNodes.length) {
27 | val activityNode = activityNodes.item(i) as Element
28 | val activityName = activityNode.getAttribute("android:name").let {
29 | if (it.startsWith(packageName) || !it.startsWith(".")) {
30 | it
31 | } else {
32 | "$packageName$it"
33 | }
34 | }
35 |
36 | val dataNodes = activityNode.getElementsByTagName("data")
37 | if (dataNodes.length > 0) {
38 | var scheme = ""
39 | for (j in 0 until dataNodes.length) {
40 | val dataNode = dataNodes.item(j) as Element
41 | val dataValue = dataNode.getAttribute("android:scheme") + "://" +
42 | dataNode.getAttribute("android:host") + dataNode.getAttribute("android:path")
43 | scheme = if (scheme.isEmpty()) dataValue else "$scheme, $dataValue"
44 | }
45 | result[activityName] = scheme
46 | }
47 | }
48 | }
49 | return result
50 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/util/OpcodesExt.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.util
2 |
3 | import org.objectweb.asm.Opcodes
4 | import java.lang.reflect.Modifier
5 |
6 | /**
7 | * Author: Omooo
8 | * Date: 2022/12/28
9 | * Desc: Opcodes 扩展函数
10 | */
11 |
12 | internal fun Int.isMethodReturn(): Boolean {
13 | return (this >= Opcodes.IRETURN && this <= Opcodes.RETURN)
14 | || this == Opcodes.ATHROW
15 | }
16 |
17 | /**
18 | * 是否是实例方法
19 | */
20 | internal fun Int.isInstanceMethod(): Boolean {
21 | return !Modifier.isNative(this) && !Modifier.isInterface(this)
22 | && !Modifier.isAbstract(this)
23 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/omooo/plugin/util/ProjectExt.kt:
--------------------------------------------------------------------------------
1 | package com.omooo.plugin.util
2 |
3 | import com.android.build.api.variant.Variant
4 | import org.gradle.api.Project
5 | import org.gradle.api.Task
6 | import org.gradle.api.tasks.TaskProvider
7 | import org.yaml.snakeyaml.Yaml
8 |
9 | /**
10 | * Author: Omooo
11 | * Date: 2023/2/10
12 | * Desc: [Project] 相关扩展函数
13 | */
14 |
15 | /**
16 | * 获取归属人映射关系
17 | *
18 | * @return Mapx
19 | */
20 | internal fun Project.getOwnerShip(): Map {
21 | val ownershipFile = parent?.projectDir?.resolve("$DIR_PLUGIN_FILES/$FILE_OWNERSHIP")
22 | if (ownershipFile?.exists() == true) {
23 | return Yaml().load