├── .gitignore ├── .travis.yml ├── README.md ├── annotation ├── .gitignore ├── build.gradle └── src │ └── main │ └── kotlin │ └── com │ └── ehi │ └── annotation │ └── MethodTrace.kt ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── singer └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ehi │ │ └── plugin │ │ ├── MainActivity.kt │ │ └── MyApplication.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xxhdpi │ └── bg.jpg │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── buildSrc ├── .gitignore ├── build.gradle └── src │ └── main │ └── kotlin │ └── com │ └── ehi │ └── buildsrc │ └── Config.kt ├── fps_detector ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ehi │ │ └── plugin │ │ └── fps_detector │ │ ├── Audience.java │ │ ├── FPSDetector.java │ │ ├── LifecycleListener.java │ │ └── MyFrameCallback.java │ └── res │ ├── layout │ └── stage.xml │ └── values │ └── strings.xml ├── gradle.properties ├── gradle ├── plugin.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── permissions.json ├── plugin ├── .gitignore ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── ehi │ │ └── plugin │ │ └── ext │ │ └── Convert2WebpExtension.java │ ├── kotlin │ └── com │ │ └── ehi │ │ └── plugin │ │ ├── EHiPlugin.kt │ │ ├── bean │ │ └── WebpToolBean.kt │ │ ├── spi │ │ └── VariantProcessor.kt │ │ ├── task │ │ ├── Convert2WebpTask.kt │ │ ├── Convert2WebpVariantProcessor.kt │ │ ├── ListPermissionTask.kt │ │ ├── ListPermissionVariantProcessor.kt │ │ ├── RepeatResDetectorTask.kt │ │ └── RepeatResDetectorVariantProcessor.kt │ │ └── util │ │ ├── ImageUtil.kt │ │ ├── Utils.kt │ │ └── WebpToolUtil.kt │ └── resources │ └── META-INF │ └── gradle-plugins │ └── com.ehi.plugin.properties ├── repeatRes.json ├── settings.gradle └── tools └── cwebp ├── mac └── cwebp └── windows └── cwebp.exe /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /.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 | EHiPlugin 3 | --- 4 | 5 | 概述: 6 | 7 | | 功能 | 使用 | 输出 | 主要实现类 | 8 | | ---------------------------- | ------------------------- | --------------------------- | --------------------- | 9 | | 输出 app 及其依赖的 aar 权限 | ./gradlew listPermissions | projectDir/permissions.json | ListPermissionTask | 10 | | 重复资源监测 | ./gradlew repeatRes | projectDir/repeatRes.json | RepeatResDetectorTask | 11 | | 资源压缩(png2webp) | 打包时自动执行 | 简单日志输出 | Convert2WebpTask | 12 | 13 | #### ListPermissionTask 14 | 15 | 原理是参考 ProcessApplicationManifest#730 的实现: 16 | 17 | ``` 18 | // This includes the dependent libraries. 19 | task.manifests = variantScope.getArtifactCollection(RUNTIME_CLASSPATH, ALL, MANIFEST); 20 | ``` 21 | 22 | 根据注释可知,这是拿到的是包含第三方依赖的,所以我们构造处理 VariantScope 就好了。 23 | 24 | #### RepeatResDetectorTask 25 | 26 | 基础使用: 27 | 28 | ``` 29 | ./gradlew repeatRes 30 | ``` 31 | 32 | 这只会扫描 drawable- 目录里面的资源,也就是图片资源。 33 | 34 | 但是实际发现,drawable 目录下竟然也有重复的文件,这里都是一些 shape、selector,所以如果你也想扫描 drawable 下的文件,可以使用: 35 | 36 | ``` 37 | ./gradlew -Pall=true repeatRes 38 | ``` 39 | 40 | 实现原理: 41 | 42 | 扫描指定目录下的文件,生成 MD5 值比对即可。 43 | 44 | 效果: 45 | 46 | v6.3.2 减少包体积 133kb。 47 | 48 | #### Convert2WebpTask 49 | 50 | 可以在打包前(比如 assembleDebug、assembleRelease)自动去转化所有的 png 图片,包括第三方依赖库里面的。该 Task 的执行时机其实是依赖于 MergeResources Task。 51 | 52 | 同时支持检查大图片,图片大小可配置。 53 | 54 | ```groovy 55 | convert2WebpConfig{ 56 | enableWhenDebug true 57 | maxSize 1024*1024 // 1M 58 | whiteList ["xxx.png","xxx.png"] 59 | //... 60 | } 61 | ``` 62 | 63 | 使用的是 cwebp 转化工具,这个东东放到了项目的根目录下的 /tools/cwebp 目录下了。 64 | 65 | 相关参数见:[https://developers.google.com/speed/webp/docs/cwebp](https://developers.google.com/speed/webp/docs/cwebp) 66 | 67 | 该 Task 的核心是怎么拿到所有的 res 资源呢,其实也很简单,就是一个 Gradle API,看下下面参考库的文档即可。 68 | 69 | 该 Task 参考自 [https://github.com/smallSohoSolo/McImage](https://github.com/smallSohoSolo/McImage) ,因为我们的 minSDK = 19,所以我去掉了一些代码。 -------------------------------------------------------------------------------- /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.ehi.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 | //} -------------------------------------------------------------------------------- /annotation/src/main/kotlin/com/ehi/annotation/MethodTrace.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.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: 'kotlin-android-extensions' 6 | 7 | //apply plugin: 'com.ehi.plugin' 8 | //convert2WebpConfig{ 9 | // enableWhenDebug false 10 | //} 11 | 12 | android { 13 | compileSdkVersion 29 14 | buildToolsVersion "29.0.2" 15 | defaultConfig { 16 | applicationId "com.ehi.plugin" 17 | minSdkVersion 23 18 | targetSdkVersion 29 19 | versionCode 1 20 | versionName "1.0" 21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 22 | } 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72" 34 | implementation 'androidx.appcompat:appcompat:1.2.0' 35 | implementation 'androidx.core:core-ktx:1.3.1' 36 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1' 37 | 38 | implementation project(":fps_detector") 39 | } 40 | -------------------------------------------------------------------------------- /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/singer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/singer -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/ehi/plugin/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | 6 | class MainActivity : AppCompatActivity() { 7 | 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_main) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/ehi/plugin/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin 2 | 3 | import android.app.Application 4 | import android.graphics.Color 5 | import android.view.Gravity 6 | import com.ehi.plugin.fps_detector.FPSDetector 7 | 8 | /** 9 | * @author Omooo 10 | * @version v1.0 11 | * @Date 2020/03/11 16:46 12 | * desc : 13 | */ 14 | class MyApplication : Application() { 15 | override fun onCreate() { 16 | super.onCreate() 17 | FPSDetector.prepare(this) 18 | .alpha(0.5f) 19 | .color(Color.WHITE) 20 | .gravity(Gravity.TOP or Gravity.END) 21 | .interval(250) 22 | .size(12f) 23 | } 24 | } -------------------------------------------------------------------------------- /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/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/drawable-xxhdpi/bg.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /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/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | EHiPlugin 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.3.72' 5 | repositories { 6 | maven { 7 | url './repo' 8 | } 9 | google() 10 | jcenter() 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:4.0.1' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | 18 | // classpath "com.ehi:plugin:0.1.5" 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | maven { 25 | url './repo' 26 | } 27 | google() 28 | jcenter() 29 | } 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | mavenLocal() 5 | google() 6 | mavenCentral() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | apply plugin: 'java' 15 | apply plugin: 'maven' 16 | apply plugin: 'kotlin' 17 | 18 | repositories { 19 | mavenLocal() 20 | google() 21 | mavenCentral() 22 | jcenter() 23 | } 24 | 25 | dependencies { 26 | implementation gradleApi() 27 | implementation localGroovy() 28 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 29 | implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 30 | testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" 31 | testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" 32 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/ehi/buildsrc/Config.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.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.3.50" 12 | } 13 | } -------------------------------------------------------------------------------- /fps_detector/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /fps_detector/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.1" 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 19 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles 'consumer-rules.pro' 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'androidx.appcompat:appcompat:1.1.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test:runner:1.2.0' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 34 | } 35 | -------------------------------------------------------------------------------- /fps_detector/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/fps_detector/consumer-rules.pro -------------------------------------------------------------------------------- /fps_detector/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 | -------------------------------------------------------------------------------- /fps_detector/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /fps_detector/src/main/java/com/ehi/plugin/fps_detector/Audience.java: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.fps_detector; 2 | 3 | /** 4 | * @author Omooo 5 | * @version v1.0 6 | * @Date 2020/03/11 16:41 7 | * desc : 8 | */ 9 | public interface Audience { 10 | void heartbeat(double fps); 11 | } -------------------------------------------------------------------------------- /fps_detector/src/main/java/com/ehi/plugin/fps_detector/FPSDetector.java: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.fps_detector; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.graphics.Color; 7 | import android.graphics.PixelFormat; 8 | import android.net.Uri; 9 | import android.os.Build; 10 | import android.provider.Settings; 11 | import android.view.Gravity; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.WindowManager; 15 | import android.view.WindowManager.LayoutParams; 16 | import android.widget.RelativeLayout; 17 | import android.widget.TextView; 18 | 19 | import androidx.annotation.FloatRange; 20 | 21 | import java.text.DecimalFormat; 22 | 23 | /** 24 | * @author Omooo 25 | * @version v1.0 26 | * @Date 2020/03/11 16:42 27 | * desc : 28 | */ 29 | public class FPSDetector { 30 | 31 | private final static Program PROGRAM = new Program(); 32 | 33 | private FPSDetector() { 34 | 35 | } 36 | 37 | public static Program prepare(Application application) { 38 | return PROGRAM.prepare(application); 39 | } 40 | 41 | public static class Program implements LifecycleListener.LifecycleCallbackListener { 42 | 43 | private MyFrameCallback mMyFrameCallback; 44 | private boolean isPlaying = false; 45 | 46 | private Application app; 47 | private WindowManager wm; 48 | private View stageView; 49 | private TextView fpsText; 50 | private WindowManager.LayoutParams lp; 51 | 52 | private final DecimalFormat decimal = new DecimalFormat("#.0' fps'"); 53 | 54 | private Program prepare(Application application) { 55 | mMyFrameCallback = new MyFrameCallback(); 56 | lp = new WindowManager.LayoutParams(); 57 | lp.width = WindowManager.LayoutParams.WRAP_CONTENT; 58 | lp.height = WindowManager.LayoutParams.WRAP_CONTENT; 59 | application.registerActivityLifecycleCallbacks(new LifecycleListener(this)); 60 | 61 | if (isOverlayApiDeprecated()) { 62 | lp.type = LayoutParams.TYPE_APPLICATION_OVERLAY; 63 | } else { 64 | lp.type = LayoutParams.TYPE_TOAST; 65 | } 66 | lp.flags = LayoutParams.FLAG_KEEP_SCREEN_ON | LayoutParams.FLAG_NOT_FOCUSABLE 67 | | LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_TOUCHABLE; 68 | lp.format = PixelFormat.TRANSLUCENT; 69 | lp.gravity = Gravity.TOP | Gravity.END; 70 | lp.x = 10; 71 | 72 | app = application; 73 | wm = (WindowManager) application.getSystemService(Context.WINDOW_SERVICE); 74 | stageView = LayoutInflater.from(app).inflate(R.layout.stage, new RelativeLayout(app)); 75 | fpsText = stageView.findViewById(R.id.tv_fps); 76 | 77 | listener(new Audience() { 78 | @Override 79 | public void heartbeat(double fps) { 80 | if (fps > 100) { 81 | fpsText.setTextColor(Color.RED); 82 | } else { 83 | fpsText.setTextColor(Color.WHITE); 84 | } 85 | fpsText.setText(decimal.format(fps)); 86 | } 87 | }); 88 | return this; 89 | } 90 | 91 | public Program listener(Audience audience) { 92 | mMyFrameCallback.addListener(audience); 93 | return this; 94 | } 95 | 96 | @Override 97 | public void onAppForeground() { 98 | play(); 99 | } 100 | 101 | @Override 102 | public void onAppBackground() { 103 | stop(); 104 | } 105 | 106 | private void play() { 107 | if (!hasOverlayPermission()) { 108 | startOverlaySettingActivity(); 109 | return; 110 | } 111 | mMyFrameCallback.start(); 112 | if (!isPlaying) { 113 | wm.addView(stageView, lp); 114 | isPlaying = true; 115 | } 116 | } 117 | 118 | private void stop() { 119 | mMyFrameCallback.stop(); 120 | 121 | if (isPlaying) { 122 | wm.removeView(stageView); 123 | isPlaying = false; 124 | } 125 | } 126 | 127 | public Program color(int color) { 128 | fpsText.setTextColor(color); 129 | return this; 130 | } 131 | 132 | public Program size(float size) { 133 | fpsText.setTextSize(size); 134 | return this; 135 | } 136 | 137 | public Program alpha(@FloatRange(from = 0.0, to = 1.0) float alpha) { 138 | fpsText.setAlpha(alpha); 139 | return this; 140 | } 141 | 142 | public Program interval(int ms) { 143 | mMyFrameCallback.setInterval(ms); 144 | return this; 145 | } 146 | 147 | public Program gravity(int gravity) { 148 | lp.gravity = gravity; 149 | return this; 150 | } 151 | 152 | private boolean isOverlayApiDeprecated() { 153 | return Build.VERSION.SDK_INT > 26; 154 | } 155 | 156 | private void startOverlaySettingActivity() { 157 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 158 | app.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 159 | Uri.parse("package:" + app.getPackageName())).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 160 | } 161 | } 162 | 163 | private boolean hasOverlayPermission() { 164 | return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(app); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /fps_detector/src/main/java/com/ehi/plugin/fps_detector/LifecycleListener.java: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.fps_detector; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | /** 11 | * @author Omooo 12 | * @version v1.0 13 | * @Date 2020/03/11 16:41 14 | * desc : 15 | */ 16 | public class LifecycleListener implements Application.ActivityLifecycleCallbacks { 17 | 18 | private int startedActivityCounter = 0; 19 | private LifecycleCallbackListener mListener; 20 | 21 | LifecycleListener(LifecycleCallbackListener listener) { 22 | mListener = listener; 23 | } 24 | 25 | public interface LifecycleCallbackListener { 26 | void onAppForeground(); 27 | 28 | void onAppBackground(); 29 | } 30 | 31 | @Override 32 | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { 33 | 34 | } 35 | 36 | @Override 37 | public void onActivityStarted(@NonNull Activity activity) { 38 | synchronized (this) { 39 | startedActivityCounter++; 40 | if (startedActivityCounter == 1 && mListener != null) { 41 | mListener.onAppForeground(); 42 | } 43 | } 44 | } 45 | 46 | @Override 47 | public void onActivityResumed(@NonNull Activity activity) { 48 | 49 | } 50 | 51 | @Override 52 | public void onActivityPaused(@NonNull Activity activity) { 53 | 54 | } 55 | 56 | @Override 57 | public void onActivityStopped(@NonNull Activity activity) { 58 | synchronized (this) { 59 | startedActivityCounter--; 60 | if (startedActivityCounter == 0 && mListener != null) { 61 | mListener.onAppBackground(); 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { 68 | 69 | } 70 | 71 | @Override 72 | public void onActivityDestroyed(@NonNull Activity activity) { 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /fps_detector/src/main/java/com/ehi/plugin/fps_detector/MyFrameCallback.java: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.fps_detector; 2 | 3 | import android.view.Choreographer; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * @author Omooo 11 | * @version v1.0 12 | * @Date 2020/03/11 16:42 13 | * desc : 14 | */ 15 | public class MyFrameCallback implements Choreographer.FrameCallback { 16 | private Choreographer mChoreographer; 17 | 18 | private long frameStartTime = 0; 19 | private int framesRendered = 0; 20 | 21 | private List listeners = new ArrayList<>(); 22 | private int interval = 500; 23 | 24 | public MyFrameCallback() { 25 | mChoreographer = Choreographer.getInstance(); 26 | } 27 | 28 | public void start() { 29 | mChoreographer.postFrameCallback(this); 30 | } 31 | 32 | public void stop() { 33 | frameStartTime = 0; 34 | framesRendered = 0; 35 | mChoreographer.removeFrameCallback(this); 36 | } 37 | 38 | public void addListener(Audience l) { 39 | listeners.add(l); 40 | } 41 | 42 | public void setInterval(int interval) { 43 | this.interval = interval; 44 | } 45 | 46 | @Override 47 | public void doFrame(long frameTimeNanos) { 48 | long currentTimeMills = TimeUnit.NANOSECONDS.toMillis(frameTimeNanos); 49 | if (frameStartTime > 0) { 50 | final long timeSpan = currentTimeMills - frameStartTime; 51 | framesRendered++; 52 | 53 | if (timeSpan > interval) { 54 | final double fps = framesRendered * 1000 / (double) timeSpan; 55 | frameStartTime = currentTimeMills; 56 | framesRendered = 0; 57 | for (Audience audience : listeners) { 58 | audience.heartbeat(fps); 59 | } 60 | } 61 | } else { 62 | frameStartTime = currentTimeMills; 63 | } 64 | 65 | mChoreographer.postFrameCallback(this); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /fps_detector/src/main/res/layout/stage.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | -------------------------------------------------------------------------------- /fps_detector/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | fps_detector 3 | 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/plugin.gradle: -------------------------------------------------------------------------------- 1 | import com.ehi.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_1_8 27 | } 28 | 29 | compileTestKotlin { 30 | kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8 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 | testImplementation "org.jetbrains.kotlin:kotlin-test:${Config.kotlin_version}" 37 | testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${Config.kotlin_version}" 38 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 25 15:20:20 GMT+08:00 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /permissions.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": [ 3 | "", 4 | "", 5 | "", 6 | "" 7 | ], 8 | ":fps_detector": [ 9 | "" 10 | ] 11 | } -------------------------------------------------------------------------------- /plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/plugin.gradle' 2 | 3 | apply plugin: 'groovy' 4 | apply plugin: 'maven' 5 | apply plugin: 'java' 6 | 7 | dependencies { 8 | 9 | implementation localGroovy() 10 | implementation gradleApi() 11 | 12 | kapt "com.google.auto.service:auto-service:1.0-rc4" 13 | implementation "com.google.auto.service:auto-service:1.0-rc4" 14 | compileOnly "com.android.tools.build:gradle:4.0.1" 15 | testCompileOnly "com.android.tools.build:gradle:4.0.1" 16 | } 17 | 18 | sourceCompatibility = "8" 19 | targetCompatibility = "8" 20 | 21 | sourceSets { 22 | main { 23 | groovy { 24 | srcDir '../plugin/src/main/groovy' 25 | } 26 | 27 | java { 28 | srcDir "../plugin/src/main/java" 29 | } 30 | 31 | kotlin { 32 | srcDir '../plugin/src/main/kotlin' 33 | } 34 | 35 | resources { 36 | srcDir '../plugin/src/main/resources' 37 | } 38 | } 39 | } 40 | 41 | // upload to Local 42 | 43 | group "com.ehi" 44 | version "0.1.5" 45 | 46 | uploadArchives { 47 | repositories { 48 | mavenDeployer { 49 | repository(url: uri('../repo')) 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/ehi/plugin/ext/Convert2WebpExtension.java: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.ext; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Author: Omooo 7 | * Date: 2020/2/11 8 | * Version: v0.1.1 9 | * Desc: Convert2WebpTask 配置 10 | */ 11 | public class Convert2WebpExtension { 12 | 13 | public boolean enableWhenDebug = false; 14 | public boolean isCheckSize = true; 15 | public String[] whiteList = new String[]{}; 16 | public String[] bigImageWhiteList = new String[]{}; 17 | public String cwebpToolsDir = ""; 18 | public float maxSize = 500 * 1024; 19 | 20 | public boolean isEnableWhenDebug() { 21 | return enableWhenDebug; 22 | } 23 | 24 | public void setEnableWhenDebug(boolean enableWhenDebug) { 25 | this.enableWhenDebug = enableWhenDebug; 26 | } 27 | 28 | public boolean isCheckSize() { 29 | return isCheckSize; 30 | } 31 | 32 | public void setCheckSize(boolean checkSize) { 33 | isCheckSize = checkSize; 34 | } 35 | 36 | public String[] getWhiteList() { 37 | return whiteList; 38 | } 39 | 40 | public void setWhiteList(String[] whiteList) { 41 | this.whiteList = whiteList; 42 | } 43 | 44 | public String[] getBigImageWhiteList() { 45 | return bigImageWhiteList; 46 | } 47 | 48 | public void setBigImageWhiteList(String[] bigImageWhiteList) { 49 | this.bigImageWhiteList = bigImageWhiteList; 50 | } 51 | 52 | public String getCwebpToolsDir() { 53 | return cwebpToolsDir; 54 | } 55 | 56 | public void setCwebpToolsDir(String cwebpToolsDir) { 57 | this.cwebpToolsDir = cwebpToolsDir; 58 | } 59 | 60 | public float getMaxSize() { 61 | return maxSize; 62 | } 63 | 64 | public void setMaxSize(float maxSize) { 65 | this.maxSize = maxSize; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "Convert2WebpExtension{" + 71 | "enableWhenDebug=" + enableWhenDebug + 72 | ", isCheckSize=" + isCheckSize + 73 | ", whiteList=" + Arrays.toString(whiteList) + 74 | ", bigImageWhiteList=" + Arrays.toString(bigImageWhiteList) + 75 | ", cwebpToolsDir='" + cwebpToolsDir + '\'' + 76 | ", maxSize=" + maxSize + 77 | '}'; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/EHiPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin 2 | 3 | import com.android.build.gradle.AppExtension 4 | import com.android.build.gradle.LibraryExtension 5 | import com.ehi.plugin.ext.Convert2WebpExtension 6 | import com.ehi.plugin.spi.VariantProcessor 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | import java.util.* 10 | 11 | /** 12 | * Author: Omooo 13 | * Date: 2019/9/27 14 | * Version: v0.1.0 15 | * Desc: Plugin 16 | */ 17 | class EHiPlugin : Plugin { 18 | 19 | override fun apply(project: Project) { 20 | 21 | println("apply plugin: 'com.ehi.plugin'") 22 | 23 | // project.repositories.maven { 24 | // it.url = URI("http://192.168.9.230:8081/repository/app-releases/") 25 | // } 26 | // project.dependencies.add("api", "com.ehi.plugin:annotation:0.1.1") 27 | 28 | // Extension 29 | project.extensions.create("convert2WebpConfig", Convert2WebpExtension::class.java) 30 | 31 | when { 32 | project.plugins.hasPlugin("com.android.application") -> project.extensions.getByType( 33 | AppExtension::class.java 34 | ).let { android -> 35 | project.afterEvaluate { 36 | ServiceLoader.load(VariantProcessor::class.java, javaClass.classLoader) 37 | .toList().let { processes -> 38 | android.applicationVariants.forEach { variant -> 39 | processes.forEach { 40 | it.process(variant) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | project.plugins.hasPlugin("com.android.library") -> project.extensions.getByType( 48 | LibraryExtension::class.java 49 | ).let { android -> 50 | project.afterEvaluate { 51 | ServiceLoader.load(VariantProcessor::class.java, javaClass.classLoader) 52 | .toList().let { processes -> 53 | android.libraryVariants.forEach { variant -> 54 | processes.forEach { 55 | it.process(variant) 56 | } 57 | } 58 | } 59 | 60 | } 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/bean/WebpToolBean.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.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/ehi/plugin/spi/VariantProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.spi 2 | 3 | import com.android.build.gradle.api.BaseVariant 4 | 5 | /** 6 | * Author: Omooo 7 | * Date: 2019/9/27 8 | * Version: v0.1.0 9 | * Desc: Task 注册接口 10 | */ 11 | interface VariantProcessor { 12 | 13 | fun process(variant: BaseVariant) 14 | 15 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/task/Convert2WebpTask.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.task 2 | 3 | import com.android.build.gradle.AppExtension 4 | import com.android.build.gradle.LibraryExtension 5 | import com.android.build.gradle.internal.api.BaseVariantImpl 6 | import com.ehi.plugin.bean.WebpToolBean 7 | import com.ehi.plugin.ext.Convert2WebpExtension 8 | import com.ehi.plugin.util.ImageUtil 9 | import org.gradle.api.DefaultTask 10 | import org.gradle.api.GradleException 11 | import org.gradle.api.tasks.TaskAction 12 | import java.io.File 13 | import java.util.concurrent.Callable 14 | import java.util.concurrent.Executors 15 | import java.util.concurrent.Future 16 | 17 | internal open class Convert2WebpTask : DefaultTask() { 18 | 19 | private lateinit var config: Convert2WebpExtension 20 | 21 | var bigImageList = ArrayList() 22 | 23 | private var oldSize = 0L 24 | private var newSize = 0L 25 | 26 | @TaskAction 27 | fun doAction() { 28 | config = project.extensions 29 | .findByType(Convert2WebpExtension::class.java) ?: return 30 | 31 | val hasAppPlugin = project.plugins.hasPlugin("com.android.application") 32 | val variants = if (hasAppPlugin) { 33 | project.extensions.getByType(AppExtension::class.java).applicationVariants 34 | } else { 35 | project.extensions.getByType(LibraryExtension::class.java).libraryVariants 36 | } 37 | 38 | variants.all { variant -> 39 | 40 | variant as BaseVariantImpl 41 | 42 | checkCwebpTools() 43 | 44 | if (!config.enableWhenDebug) { 45 | return@all 46 | } 47 | 48 | println("----- Convert2WebpTask run... -----") 49 | 50 | val dir = variant.allRawAndroidResources.files 51 | val cacheList = ArrayList() 52 | val imageFileList = ArrayList() 53 | 54 | for (channelDir in dir) { 55 | traverseResDir(channelDir, imageFileList, cacheList, object : IBigImage { 56 | override fun onBigImage(file: File) { 57 | bigImageList.add(file.absolutePath) 58 | } 59 | }) 60 | } 61 | checkBigImage() 62 | 63 | println("Should handle image count: ${imageFileList.size}") 64 | var size = 0L 65 | for (file in imageFileList) { 66 | // println("--- ${file.absolutePath}") 67 | size += file.length() 68 | } 69 | println("Total Image Size: ${size / 1024}kb") 70 | val startTime = System.currentTimeMillis() 71 | 72 | dispatchOptimizeTask(imageFileList) 73 | 74 | println("Before optimize Size: ${oldSize / 1024}kb") 75 | println("After optimize Size: ${newSize / 1024}kb") 76 | println("Optimize Size: ${(oldSize - newSize) / 1024}kb") 77 | 78 | println("CostTotalTime: ${System.currentTimeMillis() - startTime}ms") 79 | println("------------------------------------") 80 | } 81 | } 82 | 83 | private fun dispatchOptimizeTask(imageFileList: java.util.ArrayList) { 84 | if (imageFileList.size == 0 || bigImageList.isNotEmpty()) { 85 | return 86 | } 87 | val coreNum = Runtime.getRuntime().availableProcessors() 88 | if (imageFileList.size < coreNum) { 89 | for (file in imageFileList) { 90 | optimizeImage(file) 91 | } 92 | } else { 93 | val results = ArrayList>() 94 | val pool = Executors.newFixedThreadPool(coreNum) 95 | val part = imageFileList.size / coreNum 96 | for (i in 0 until coreNum) { 97 | val from = i * part 98 | val to = if (i == coreNum - 1) imageFileList.size - 1 else (i + 1) * part - 1 99 | results.add(pool.submit(Callable { 100 | for (index in from..to) { 101 | optimizeImage(imageFileList[index]) 102 | } 103 | })) 104 | } 105 | for (f in results) { 106 | try { 107 | f.get() 108 | } catch (e: Exception) { 109 | println("EHiPlugin Convert2WebpTask#dispatchOptimizeTask() execute wrong.") 110 | } 111 | } 112 | } 113 | } 114 | 115 | private fun optimizeImage(file: File) { 116 | val path: String = file.path 117 | if (File(path).exists()) { 118 | oldSize += File(path).length() 119 | } 120 | ImageUtil.convert2Webp(file) 121 | calcNewSize(path) 122 | } 123 | 124 | private fun calcNewSize(path: String) { 125 | if (File(path).exists()) { 126 | newSize += File(path).length() 127 | } else { 128 | val indexOfDot = path.lastIndexOf(".") 129 | val webpPath = path.substring(0, indexOfDot) + ".webp" 130 | if (File(webpPath).exists()) { 131 | newSize += File(webpPath).length() 132 | } else { 133 | println("EHiPlugin Convert2Webp Task was wrong.") 134 | } 135 | } 136 | } 137 | 138 | private fun checkBigImage() { 139 | if (bigImageList.size != 0) { 140 | val stringBuffer = StringBuffer("Big Image Detector! ") 141 | .append("ImageSize can't over ${config.maxSize / 1024}kb.\n") 142 | .append("To fix this exception, you can increase maxSize or config them in bigImageWhiteList\n") 143 | .append("Big Image List: \n") 144 | for (fileName in bigImageList) { 145 | stringBuffer.append(fileName) 146 | stringBuffer.append("\n") 147 | } 148 | throw GradleException(stringBuffer.toString()) 149 | } 150 | } 151 | 152 | private fun traverseResDir( 153 | file: File, 154 | imageFileList: ArrayList, 155 | cacheList: ArrayList, 156 | iBigImage: IBigImage 157 | ) { 158 | 159 | if (cacheList.contains(file.absolutePath)) { 160 | return 161 | } else { 162 | cacheList.add(file.absolutePath) 163 | } 164 | 165 | if (file.isDirectory) { 166 | file.listFiles()?.forEach { 167 | if (it.isDirectory) { 168 | traverseResDir(it, imageFileList, cacheList, iBigImage) 169 | } else { 170 | filterImage(it, imageFileList, iBigImage) 171 | } 172 | } 173 | } else { 174 | filterImage(file, imageFileList, iBigImage) 175 | } 176 | } 177 | 178 | private fun filterImage(file: File, imageFileList: ArrayList, iBigImage: IBigImage) { 179 | if (config.whiteList.contains(file.name) || !ImageUtil.isImage(file)) { 180 | return 181 | } 182 | if ((config.isCheckSize && ImageUtil.isBigSizeImage(file, config.maxSize)) 183 | && !config.bigImageWhiteList.contains(file.name) 184 | ) { 185 | iBigImage.onBigImage(file) 186 | } 187 | imageFileList.add(file) 188 | } 189 | 190 | private fun checkCwebpTools() { 191 | if (config.cwebpToolsDir.isBlank()) { 192 | WebpToolBean.setRootDir(project.rootDir.path) 193 | } else { 194 | WebpToolBean.setRootDir(config.cwebpToolsDir) 195 | } 196 | if (!WebpToolBean.getToolsDir().exists()) { 197 | throw GradleException("EHiPlugin 'convert2Webp' task need cwebp tool.") 198 | } 199 | } 200 | 201 | interface IBigImage { 202 | fun onBigImage(file: File) 203 | } 204 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/task/Convert2WebpVariantProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.task 2 | 3 | import com.android.build.gradle.api.BaseVariant 4 | import com.android.build.gradle.internal.api.ApplicationVariantImpl 5 | import com.ehi.plugin.spi.VariantProcessor 6 | import com.google.auto.service.AutoService 7 | 8 | /** 9 | * Author: Omooo 10 | * Date: 2020/2/11 11 | * Version: v0.1.1 12 | * Desc: 注册 Convert2WebpTask 13 | * @see Convert2WebpTask 14 | */ 15 | @AutoService(VariantProcessor::class) 16 | class Convert2WebpVariantProcessor : VariantProcessor { 17 | 18 | override fun process(variant: BaseVariant) { 19 | 20 | val variantData = (variant as ApplicationVariantImpl).variantData 21 | val tasks = variantData.scope.globalScope.project.tasks 22 | val convert2WebpTask = tasks.findByName("convert2Webp") ?: tasks.create( 23 | "convert2Webp", 24 | Convert2WebpTask::class.java 25 | ) 26 | val mergeResourcesTask = variant.mergeResourcesProvider.get() 27 | mergeResourcesTask.dependsOn(convert2WebpTask) 28 | } 29 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/task/ListPermissionTask.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.task 2 | 3 | import com.android.build.gradle.api.BaseVariant 4 | import com.android.build.gradle.internal.api.ApplicationVariantImpl 5 | import com.android.build.gradle.internal.dependency.ArtifactCollectionWithExtraArtifact 6 | import com.android.build.gradle.internal.publishing.AndroidArtifacts 7 | import com.android.build.gradle.internal.tasks.CheckManifest 8 | import com.ehi.plugin.util.writeToJson 9 | import groovy.json.JsonOutput 10 | import org.gradle.api.DefaultTask 11 | import org.gradle.api.artifacts.component.ModuleComponentIdentifier 12 | import org.gradle.api.artifacts.component.ProjectComponentIdentifier 13 | import org.gradle.api.artifacts.result.ResolvedArtifactResult 14 | import org.gradle.api.tasks.TaskAction 15 | import org.gradle.internal.component.local.model.OpaqueComponentArtifactIdentifier 16 | import java.io.File 17 | import java.util.regex.Pattern 18 | 19 | /** 20 | * Author: Omooo 21 | * Date: 2019/9/27 22 | * Version: v0.1.0 23 | * Desc: 输出 app 及其依赖的 aar 权限信息 24 | * Use: ./gradlew listPermissions 25 | * Output: projectDir/permissions.json 26 | */ 27 | internal open class ListPermissionTask : DefaultTask() { 28 | 29 | lateinit var variant: BaseVariant 30 | 31 | @TaskAction 32 | fun doAction() { 33 | println( 34 | """ 35 | ********************************************* 36 | ********* -- ListPermissionTask -- ********** 37 | ***** -- projectDir/permissions.json -- ***** 38 | ********************************************* 39 | """.trimIndent() 40 | ) 41 | 42 | val map = HashMap>() 43 | 44 | // 获取 app 模块的权限 45 | val checkManifestTask = variant.checkManifestProvider.get() as CheckManifest 46 | if (checkManifestTask.isManifestRequiredButNotPresent()) { 47 | println("App manifest is missing for variant ${variant.name}") 48 | getAppModulePermission(map) 49 | } else { 50 | val manifest = checkManifestTask.fakeOutputDir.asFile.get() 51 | if (manifest.exists()) { 52 | map["app"] = matchPermission(manifest.readText()) 53 | } else { 54 | println("App manifest is missing for variant ${variant.name}, Expected path: ${manifest.absolutePath}") 55 | getAppModulePermission(map) 56 | } 57 | } 58 | 59 | // 获取 app 依赖的 aar 权限 60 | val variantData = (variant as ApplicationVariantImpl).variantData 61 | val manifests = variantData.scope.getArtifactCollection( 62 | AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, 63 | AndroidArtifacts.ArtifactScope.ALL, 64 | AndroidArtifacts.ArtifactType.MANIFEST 65 | ) 66 | 67 | val artifacts = manifests.artifacts 68 | for (artifact in artifacts) { 69 | if (!map.containsKey(getArtifactName(artifact)) 70 | && matchPermission(artifact.file.readText()).isNotEmpty() 71 | ) { 72 | map[getArtifactName(artifact)] = matchPermission(artifact.file.readText()) 73 | } 74 | } 75 | 76 | map.writeToJson("${project.parent?.projectDir}/permissions.json") 77 | } 78 | 79 | private fun getAppModulePermission(map: HashMap>){ 80 | val file = project.projectDir.resolve("src/main/AndroidManifest.xml") 81 | if (file.exists()) { 82 | map["app"] = matchPermission(file.readText()) 83 | } else { 84 | println("App manifest is missing for path ${file.absolutePath}") 85 | } 86 | } 87 | 88 | /** 89 | * 根据 Manifest 文件匹配权限信息 90 | */ 91 | private fun matchPermission(text: String): List { 92 | val list = ArrayList() 93 | val pattern = Pattern.compile("") 94 | val matcher = pattern.matcher(text) 95 | while (matcher.find()) { 96 | list.add(matcher.group()) 97 | } 98 | return list 99 | } 100 | 101 | private fun getArtifactName(artifact: ResolvedArtifactResult): String { 102 | return when (val id = artifact.id.componentIdentifier) { 103 | is ProjectComponentIdentifier -> id.projectPath 104 | is ModuleComponentIdentifier -> id.group + ":" + id.module + ":" + id.version 105 | is OpaqueComponentArtifactIdentifier -> id.getDisplayName() 106 | is ArtifactCollectionWithExtraArtifact.ExtraComponentIdentifier -> id.getDisplayName() 107 | else -> throw RuntimeException("Unsupported type of ComponentIdentifier") 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/task/ListPermissionVariantProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.task 2 | 3 | import com.android.build.gradle.api.BaseVariant 4 | import com.android.build.gradle.internal.api.ApplicationVariantImpl 5 | import com.ehi.plugin.spi.VariantProcessor 6 | import com.google.auto.service.AutoService 7 | 8 | /** 9 | * Author: Omooo 10 | * Date: 2019/9/27 11 | * Version: v0.1.0 12 | * Desc: 注册 ListPermissionTask 13 | * @see ListPermissionTask 14 | */ 15 | @AutoService(VariantProcessor::class) 16 | class ListPermissionVariantProcessor : VariantProcessor { 17 | override fun process(variant: BaseVariant) { 18 | val variantData = (variant as ApplicationVariantImpl).variantData 19 | val tasks = variantData.scope.globalScope.project.tasks 20 | val listPermission = tasks.findByName("listPermissions") ?: tasks.create("listPermissions") 21 | tasks.create( 22 | "list${variant.name.capitalize()}Permissions", 23 | ListPermissionTask::class.java 24 | ) { 25 | it.variant = variant 26 | // 如果闭包返回 false,则不能重用此任务的以前输出,并且将执行该任务 27 | // 这意味着任务已经过期,不会从构建缓存加载任何输出 28 | it.outputs.upToDateWhen { false } 29 | }.also { 30 | listPermission.dependsOn(it) 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/task/RepeatResDetectorTask.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.task 2 | 3 | import com.ehi.plugin.util.encode 4 | import com.ehi.plugin.util.writeToJson 5 | import org.gradle.api.DefaultTask 6 | import org.gradle.api.tasks.TaskAction 7 | import java.io.File 8 | 9 | /** 10 | * Author: Omooo 11 | * Date: 2019/9/27 12 | * Version: v0.1.0 13 | * Desc: 重复资源监测 14 | * Use: ./gradlew detectRepeatRes 15 | * Output: projectDir/repeatRes.json 16 | */ 17 | internal open class RepeatResDetectorTask : DefaultTask() { 18 | 19 | @TaskAction 20 | fun run() { 21 | println( 22 | """ 23 | ********************************************* 24 | ******** -- RepeatResDetectorTask -- ******** 25 | ****** -- projectDir/repeatRes.json -- ****** 26 | ********************************************* 27 | """.trimIndent() 28 | ) 29 | 30 | val map = HashMap>() 31 | val prefix = if (project.properties["all"] != "true") "drawable-" else "drawable" 32 | project.projectDir.resolve("src/main/res").listFiles()?.filter { 33 | it.name.startsWith(prefix) 34 | }?.forEach { dir -> 35 | if (dir.isDirectory) { 36 | dir.listFiles()?.forEach { file -> 37 | val key = file.readBytes().encode() 38 | val value = arrayListOf() 39 | val list = map[key] 40 | list?.let { it1 -> value.addAll(it1) } 41 | value.add(file.absolutePath) 42 | map[key] = value 43 | } 44 | } 45 | } 46 | var length: Long = 0 47 | map.filterValues { values -> 48 | values.size > 1 49 | }.apply { 50 | this.values.forEach { 51 | length += File(it[0]).length() 52 | } 53 | println("Repeat Res size: ${length / 1000}kb ") 54 | 55 | this.writeToJson("${project.parent?.projectDir}/repeatRes.json") 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/task/RepeatResDetectorVariantProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.task 2 | 3 | import com.android.build.gradle.api.BaseVariant 4 | import com.android.build.gradle.internal.api.ApplicationVariantImpl 5 | import com.ehi.plugin.spi.VariantProcessor 6 | import com.google.auto.service.AutoService 7 | 8 | /** 9 | * Author: Omooo 10 | * Date: 2019/9/27 11 | * Version: v0.1.0 12 | * Desc: 注册 RepeatResDetectorTask 13 | * @see RepeatResDetectorTask 14 | */ 15 | @AutoService(VariantProcessor::class) 16 | class RepeatResDetectorVariantProcessor : VariantProcessor { 17 | override fun process(variant: BaseVariant) { 18 | val variantData = (variant as ApplicationVariantImpl).variantData 19 | val tasks = variantData.scope.globalScope.project.tasks 20 | tasks.findByName("repeatRes") ?: tasks.create( 21 | "repeatRes", 22 | RepeatResDetectorTask::class.java 23 | ) 24 | } 25 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/util/ImageUtil.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.util 2 | 3 | import java.io.File 4 | 5 | /** 6 | * Created by Omooo 7 | * Date: 2020-02-12 8 | * Desc: ImageUtil 9 | */ 10 | class ImageUtil { 11 | 12 | companion object { 13 | 14 | fun isImage(file: File): Boolean { 15 | return (file.name.endsWith(".jpg") 16 | || file.name.endsWith(".png") 17 | || file.name.endsWith(".jpeg")) 18 | && !file.name.endsWith(".9.png") 19 | } 20 | 21 | fun isBigSizeImage(file: File, maxSize: Float): Boolean { 22 | if (isImage(file)) { 23 | if (file.length() >= maxSize) { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | 30 | fun convert2Webp(imgFile: File) { 31 | if (isImage(imgFile)) { 32 | val webpFile = 33 | File("${imgFile.path.substring(0, imgFile.path.lastIndexOf("."))}.webp") 34 | WebpToolUtil.cmd("cwebp", "${imgFile.path} -o ${webpFile.path} -m 6 -quiet") 35 | if (webpFile.length() < imgFile.length()) { 36 | if (imgFile.exists()) { 37 | imgFile.delete() 38 | } 39 | } else { 40 | if (webpFile.exists()) { 41 | webpFile.delete() 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/util/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.util 2 | 3 | import groovy.json.JsonOutput 4 | import java.io.File 5 | import java.security.MessageDigest 6 | 7 | /** 8 | * Author: Omooo 9 | * Date: 2019/10/8 10 | * Version: v0.1.1 11 | * Desc: 一系列 Kotlin 扩展函数 12 | */ 13 | 14 | /** 15 | * 生成 ByteArray 的 MD5 值 16 | */ 17 | fun ByteArray.encode(): String { 18 | val instance: MessageDigest = MessageDigest.getInstance("MD5") 19 | val digest: ByteArray = instance.digest(this) 20 | val sb = StringBuffer() 21 | for (b in digest) { 22 | val i: Int = b.toInt() and 0xff 23 | var hexString = Integer.toHexString(i) 24 | if (hexString.length < 2) hexString = "0$hexString" 25 | sb.append(hexString) 26 | } 27 | return sb.toString() 28 | } 29 | 30 | /** 31 | * 将 Map 以 Json 文件输出 32 | */ 33 | fun Map.writeToJson(path: String) { 34 | val jsonFile = File(path) 35 | if (jsonFile.exists()) { 36 | jsonFile.delete() 37 | } 38 | jsonFile.createNewFile() 39 | val json = JsonOutput.toJson(this) 40 | jsonFile.writeText(JsonOutput.prettyPrint(json), Charsets.UTF_8) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/ehi/plugin/util/WebpToolUtil.kt: -------------------------------------------------------------------------------- 1 | package com.ehi.plugin.util 2 | 3 | import com.ehi.plugin.bean.WebpToolBean 4 | 5 | /** 6 | * Created by Omooo 7 | * Date: 2020-02-13 8 | * Desc: 9 | */ 10 | class WebpToolUtil { 11 | 12 | companion object { 13 | 14 | fun cmd(cmd: String, params: String) { 15 | val system = System.getProperty("os.name") 16 | val cmdStr = when (system) { 17 | "Windows" -> 18 | "${WebpToolBean.getToolsDirPath()}/windows/$cmd $params" 19 | "Mac OS X" -> 20 | "${WebpToolBean.getToolsDirPath()}/mac/$cmd $params" 21 | else -> "" 22 | } 23 | if (cmd == "") { 24 | println("Cwebp can't support this system.") 25 | return 26 | } 27 | outputMessage(cmdStr) 28 | } 29 | 30 | private fun outputMessage(cmdStr: String) { 31 | val process = Runtime.getRuntime().exec(cmdStr) 32 | process.waitFor() 33 | } 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /plugin/src/main/resources/META-INF/gradle-plugins/com.ehi.plugin.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.ehi.plugin.EHiPlugin -------------------------------------------------------------------------------- /repeatRes.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':plugin', ':annotation', ':fps_detector' 2 | -------------------------------------------------------------------------------- /tools/cwebp/mac/cwebp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/tools/cwebp/mac/cwebp -------------------------------------------------------------------------------- /tools/cwebp/windows/cwebp.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroKibaco/Lavender/01aee4fbd1dcd7ecf6cd192f1fcb1a9a6275b26e/tools/cwebp/windows/cwebp.exe --------------------------------------------------------------------------------