├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bintray.gradle ├── build.gradle ├── buildViaTravis.sh ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── initializer-gradle-plugin ├── .gitignore ├── build.gradle └── src │ └── main │ └── kotlin │ └── com │ └── chrnie │ └── initializer │ └── plugin │ ├── CodeGenerator.kt │ ├── Const.kt │ ├── InitializerPlugin.kt │ ├── InitializerTransform.kt │ └── Metadata.kt ├── initializer ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com.chrnie.initializer │ │ ├── DelayTest.java │ │ ├── Future.java │ │ ├── GraphNodeTest.java │ │ ├── InitializerTest.java │ │ ├── MainExecutorTest.java │ │ ├── MockGraphNode.java │ │ ├── MockTask.java │ │ ├── TaskNodeTest.java │ │ └── TaskTest.java │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── chrnie │ └── initializer │ ├── Action.java │ ├── Delay.java │ ├── GraphNode.java │ ├── Initializer.java │ ├── MainExecutor.java │ ├── Task.java │ ├── TaskLoader.java │ ├── TaskNode.java │ └── exception │ ├── CyclicDependencyException.java │ ├── DependencyNotFoundException.java │ └── DuplicateTaskException.java ├── sample ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── chrnie │ │ └── initializer │ │ └── sample │ │ ├── MainActivity.java │ │ └── task │ │ ├── ATask.java │ │ ├── BTask.java │ │ └── CTask.java ├── basic │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── chrnie │ │ │ └── initializer │ │ │ └── basic │ │ │ ├── App.java │ │ │ ├── AppExecutors.java │ │ │ ├── BaseTask.java │ │ │ ├── Tasks.java │ │ │ └── task │ │ │ └── UnimportantTask.java │ │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.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 │ │ └── strings.xml ├── build.gradle ├── component.gradle ├── config.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── module_a │ ├── .gitignore │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── chrnie │ │ └── initializer │ │ └── module │ │ └── a │ │ ├── AModuleActivity.java │ │ └── task │ │ ├── ATask.java │ │ ├── BTask.java │ │ └── CTask.java ├── module_b │ ├── .gitignore │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── chrnie │ │ └── initializer │ │ └── module │ │ └── b │ │ ├── BModuleActivity.java │ │ └── task │ │ └── ATask.java ├── release.key └── settings.gradle └── settings.gradle /.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 | 4 | android: 5 | components: 6 | - tools 7 | - platform-tools 8 | - android-28 9 | - android-22 10 | - sys-img-armeabi-v7a-android-22 11 | 12 | jdk: 13 | - oraclejdk8 14 | 15 | # as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ 16 | sudo: false 17 | 18 | before_install: 19 | # Install SDK license so Android Gradle plugin can install deps. 20 | - mkdir "$ANDROID_HOME/licenses" || true 21 | - echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license" 22 | 23 | before_script: 24 | # Emulator Management: Create, Start and Wait 25 | - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a 26 | - emulator -avd test -no-audio -no-window & 27 | - android-wait-for-emulator 28 | - adb shell input keyevent 82 & 29 | 30 | # script for build and release via Travis to Bintray 31 | script: 32 | - ./buildViaTravis.sh 33 | 34 | # cache between builds 35 | cache: 36 | directories: 37 | - $HOME/.gradle 38 | 39 | env: 40 | global: 41 | - secure: "LNE6zR6CENJjKerN8TXvP/5WXlD4Cz0f/3+IariDWWMmEUdJqbBKQieK/X6QzJdpJ7+kFqalHgwDJE6B18n2jHtO58e0RiafogVRcCQM9ri98giuJPekdvmC4fMS+KYz1jt67V4CIM7BErmk3WIw4eIKuD0q9IIuhivzesHL+DuiCQCKUahORnnWqCTm20oZqOPl4OLvp/2qB8D1sfPaKJEAbJymZYFMZ4V6wIKKoj20u6dqVkly6OEQYaqi6Y61d51Os7yXQo76vAifQKiKtgB2aLz6SQ/1WtZeZUADoadYSJe/O6HCV03FFzgBaVfp+g6zyjOPw+a8dwmOiNlYFr5RVJRPSOi+yc6/Sk4TyUl3J8n2IzL8RI9SrFfPFShrOKRxfDxNazgzCot5mmavOpjLz8Zc8/kmqFQrKtvCIKSQedM7b5aaqagawHiVUdtkaRD3CUTDtIMptOiemV3YxO2hN3kKPTyqmag3af2uZJsPtBPkpIn4mbSMZZgJ82uNAz9Auj2fLwNejSUlNmdCKkcJiCu2mznKxzj6Rx+zgExU6I1BVmk0UmTd4oUGJA/vtMaYNblhGi37MqPWKR1zixdfsTyzCCrnOFZGlAWNIGLrz4wRS/9e5pyeQWv66LFRtDrSDhlZFFhTXoATY0FJf1sRbouw75XbOOfQZijUaKM=" 42 | - secure: "Ef38eD399NaJHR2sQ2MsJgFDsI5eymJ9GUC4odAjnFN9ZkV+RKIya3BgxqWHGOPN9FpIyKfPXhq7LK6LeozXHVliy/JEdY0Oim8olIuWFcpFrr7FtP7TRsFPwYcKnzWHsjrcxuNQRN+EAlk30C9lScjMnc1j+4A7XtZkpOwORDSSLwRCGb0KMRpKMk7xDWAiIzflcy4C0LitG6/aZ2+06pAoDNZoai+vLfSQnvWjvWJw+w5yjdOuqPWlJCiIt0DFSRKiX4za9KmpLSAeR2qmkZ/9pBpZhF/ubH8nk9fo6qLikBQLaTj/jKpawv0YvYnlOcJci63ptFtoTVPtYwGupMAswoA6AgBJxnWiJI3rbAHJHYInrfAamgIOECb3J1o+z5r8ez9l9e2REyDCqFix2N3ORFuJ9oiRzMjYuBN+JkH6KoBDkZ456H/16RkgAD1gn2N5I4Qp4nI/FYS5PAVvEh3sU8Hx9qktqDnRYJ3m3qZaqm97WgJGqJnCo1vDC3/T6ZnCM1W1MoIkDNhmbgLG0DlCouE4KMCrQ8B7Gmm8YaY7R1T/XWR+MBlLynxyzI3HWOteMLqRLOpIvR+mQCxSKK52NOqBl2thRF1fJRk4wZ8QH3cYpzYETFz0bvln1iT9v3LuAuVtwWzg3oP75Ka5YzXoNHoT+Oo/qpn8paSw420=" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ChenRenJie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Initializer [![Build Status](https://travis-ci.org/cheie/initializer.svg?branch=master)](https://travis-ci.org/cheie/initializer) [![Download](https://api.bintray.com/packages/chrnie/initializer/initializer/images/download.svg)](https://bintray.com/chrnie/initializer/initializer/_latestVersion) 2 | 3 | A framework for initialize task of Android componentization. 4 | 5 | ## Feature 6 | 1. Auto discover tasks 7 | 2. Manage dependencies between tasks 8 | 3. Customize task running thread through executor 9 | 10 | ## Download 11 | Add gradle plugin: 12 | ``` 13 | // Add in root project build.gradle 14 | buildscript { 15 | dependencies { 16 | classpath "com.chrnie:initializer-gradle-plugin:$x.y.z" 17 | } 18 | } 19 | 20 | // Add in module project build.gradle 21 | apply plugin: 'com.chrnie.initializer' 22 | ``` 23 | 24 | ## Usage 25 | [Sample Project](./sample) 26 | 27 | ## Inspiration 28 | [知乎 Android 客户端组件化实践](https://zhuanlan.zhihu.com/p/45374964) 29 | 30 | ## License 31 | ``` 32 | MIT License 33 | 34 | Copyright (c) 2019 ChenRenJie 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a copy 37 | of this software and associated documentation files (the "Software"), to deal 38 | in the Software without restriction, including without limitation the rights 39 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 40 | copies of the Software, and to permit persons to whom the Software is 41 | furnished to do so, subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included in all 44 | copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 49 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 52 | SOFTWARE. 53 | ``` 54 | -------------------------------------------------------------------------------- /bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | def getPropertyOrNull(String prop) { 5 | return hasProperty(prop) ? getProperty(prop) : null 6 | } 7 | 8 | install { 9 | repositories.mavenInstaller { 10 | pom { 11 | project { 12 | name project.name 13 | description 'Android componentization initializer' 14 | url 'https://github.com/cheie/initializer' 15 | 16 | scm { 17 | url 'https://github.com/cheie/initializer' 18 | connection 'https://github.com/cheie/initializer.git' 19 | developerConnection 'https://github.com/cheie/initializer.git' 20 | } 21 | 22 | licenses { 23 | license { 24 | name 'MIT' 25 | url 'https://opensource.org/licenses/mit-license' 26 | distribution 'repo' 27 | } 28 | } 29 | 30 | developers { 31 | developer { 32 | id 'chrnie' 33 | name 'ChenRenJie' 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | bintray { 42 | user = getPropertyOrNull('bintrayUser') 43 | key = getPropertyOrNull('bintrayKey') 44 | configurations = ['archives'] 45 | 46 | pkg { 47 | repo = 'initializer' 48 | name = project.name 49 | licenses = ['MIT'] 50 | vcsUrl = 'https://github.com/cheie/initializer.git' 51 | } 52 | } 53 | 54 | if (project.plugins.hasPlugin('com.android.library')) { 55 | task androidSourcesJar(type: Jar) { 56 | classifier = 'sources' 57 | from android.sourceSets.main.java.source 58 | } 59 | 60 | artifacts { 61 | archives androidSourcesJar 62 | } 63 | } else { 64 | task sourcesJar(type: Jar, dependsOn: classes) { 65 | classifier = 'sources' 66 | from sourceSets.main.allSource 67 | } 68 | 69 | artifacts { 70 | archives sourcesJar 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: './config.gradle' 3 | 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | 9 | dependencies { 10 | classpath deps['android'] 11 | classpath deps['kotlin'] 12 | classpath deps['android-maven'] 13 | classpath deps['bintray'] 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /buildViaTravis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Auto build or deploy 3 | 4 | set -e 5 | 6 | if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then 7 | echo "Build for Release => Branch ["$TRAVIS_BRANCH"] Tag ["$TRAVIS_TAG"]" 8 | ./gradlew -PbintrayUser="$BINTRAY_USER" -PbintrayKey="$BINTRAY_KEY" clean bintrayUpload 9 | else 10 | echo "Build => Branch ["$TRAVIS_BRANCH"] Tag ["$TRAVIS_TAG"]" 11 | ./gradlew connectedAndroidTest 12 | fi -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | version = '1.3.1' 3 | group = 'com.chrnie' 4 | } 5 | 6 | ext { 7 | compileSdkVersion = 28 8 | buildToolsVersion = "28.0.3" 9 | minSdkVersion = 16 10 | targetSdkVersion = 28 11 | } 12 | 13 | def kotlin_version = '1.3.31' 14 | 15 | ext.deps = [ 16 | "android" : "com.android.tools.build:gradle:3.4.0", 17 | "android-maven": "com.github.dcendents:android-maven-gradle-plugin:2.1", 18 | "bintray" : "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4", 19 | "kotlin" : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version", 20 | "kotlin-stdlib": "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version", 21 | "asm" : "org.ow2.asm:asm:7.1", 22 | 23 | "test-core" : "androidx.test:core:1.1.0", 24 | "test-runner" : "androidx.test:runner:1.1.1", 25 | "test-junit" : "androidx.test.ext:junit:1.1.0", 26 | "mockito" : "org.mockito:mockito-android:2.27.0", 27 | ] 28 | -------------------------------------------------------------------------------- /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 | 15 | 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Feb 25 22:07:16 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.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 | -------------------------------------------------------------------------------- /initializer-gradle-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /initializer-gradle-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-gradle-plugin' 2 | apply plugin: 'kotlin' 3 | 4 | gradlePlugin { 5 | plugins { 6 | initializerPlugin { 7 | id = 'com.chrnie.initializer' 8 | implementationClass = 'com.chrnie.initializer.plugin.InitializerPlugin' 9 | } 10 | } 11 | } 12 | 13 | sourceCompatibility = JavaVersion.VERSION_1_8 14 | targetCompatibility = JavaVersion.VERSION_1_8 15 | 16 | dependencies { 17 | compileOnly deps['android'] 18 | implementation deps['kotlin-stdlib'] 19 | implementation deps['asm'] 20 | } 21 | 22 | apply from: "$rootDir/bintray.gradle" 23 | -------------------------------------------------------------------------------- /initializer-gradle-plugin/src/main/kotlin/com/chrnie/initializer/plugin/CodeGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.plugin 2 | 3 | import org.objectweb.asm.* 4 | import java.io.File 5 | import java.io.FileOutputStream 6 | import java.io.InputStream 7 | import java.nio.file.Files 8 | import java.nio.file.StandardCopyOption 9 | import java.util.jar.JarFile 10 | import java.util.jar.JarOutputStream 11 | import java.util.zip.ZipEntry 12 | 13 | class CodeGenerator(private val outputFile: File, private val taskNameList: List) { 14 | 15 | fun generate() { 16 | val optJar = File(outputFile.parent, "${outputFile.name}.opt") 17 | Files.deleteIfExists(optJar.toPath()) 18 | 19 | val jarOutputStream = JarOutputStream(FileOutputStream(optJar)) 20 | val jarFile = JarFile(outputFile) 21 | 22 | jarFile.entries().iterator() 23 | .forEach { jarEntry -> 24 | val entryName = jarEntry.name 25 | val zipEntry = ZipEntry(entryName) 26 | 27 | val inputStream = jarFile.getInputStream(zipEntry) 28 | jarOutputStream.putNextEntry(zipEntry) 29 | 30 | if (HOOK_CLASS_FILE_NAME == entryName) { 31 | val bytes = hackMethod(inputStream) 32 | jarOutputStream.write(bytes) 33 | } else { 34 | inputStream.copyTo(jarOutputStream) 35 | } 36 | 37 | inputStream.close() 38 | jarOutputStream.closeEntry() 39 | } 40 | 41 | jarOutputStream.close() 42 | jarFile.close() 43 | 44 | Files.move(optJar.toPath(), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING) 45 | } 46 | 47 | private fun hackMethod(inputStream: InputStream): ByteArray { 48 | val cr = ClassReader(inputStream) 49 | val cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS) 50 | val cv = GeneratorClassVisitor(cw) 51 | cr.accept(cv, 0) 52 | return cw.toByteArray() 53 | } 54 | 55 | private inner class GeneratorClassVisitor(cw: ClassWriter) : ClassVisitor(Opcodes.ASM5, cw) { 56 | override fun visitMethod( 57 | access: Int, 58 | name: String?, 59 | descriptor: String?, 60 | signature: String?, 61 | exceptions: Array? 62 | ): MethodVisitor { 63 | var mv = super.visitMethod(access, name, descriptor, signature, exceptions) 64 | if (HOOK_METHOD_NAME == name) { 65 | mv = GeneratorMethodVisitor(mv) 66 | } 67 | return mv 68 | } 69 | } 70 | 71 | private inner class GeneratorMethodVisitor(mv: MethodVisitor) : 72 | MethodVisitor(Opcodes.ASM5, mv) { 73 | override fun visitInsn(opcode: Int) { 74 | if (Opcodes.RETURN == opcode) { 75 | insertCode() 76 | } 77 | super.visitInsn(opcode) 78 | } 79 | 80 | private fun insertCode() { 81 | taskNameList.forEach { taskName -> 82 | mv.visitVarInsn(Opcodes.ALOAD, 0) 83 | mv.visitTypeInsn(Opcodes.NEW, taskName) 84 | mv.visitInsn(Opcodes.DUP) 85 | mv.visitMethodInsn( 86 | Opcodes.INVOKESPECIAL, 87 | taskName, 88 | "", 89 | "()V", 90 | false 91 | ) 92 | mv.visitMethodInsn( 93 | Opcodes.INVOKEINTERFACE, 94 | "java/util/List", 95 | "add", 96 | "(Ljava/lang/Object;)Z", 97 | true 98 | ) 99 | mv.visitInsn(Opcodes.POP) 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /initializer-gradle-plugin/src/main/kotlin/com/chrnie/initializer/plugin/Const.kt: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.plugin 2 | 3 | const val POM_GROUP_ID = "com.chrnie" 4 | const val POM_PLUGIN_ARTIFACT_ID = "initializer-gradle-plugin" 5 | const val POM_RUNTIME_ARTIFACT_ID = "initializer" 6 | const val TASK_CLASS_SUPER_NAME = "com/chrnie/initializer/Task" 7 | const val HOOK_CLASS_NAME = "com/chrnie/initializer/TaskLoader" 8 | const val HOOK_CLASS_FILE_NAME = "$HOOK_CLASS_NAME.class" 9 | const val HOOK_METHOD_NAME = "loadTasks" -------------------------------------------------------------------------------- /initializer-gradle-plugin/src/main/kotlin/com/chrnie/initializer/plugin/InitializerPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.plugin 2 | 3 | import com.android.build.gradle.AppExtension 4 | import com.android.build.gradle.AppPlugin 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | 8 | class InitializerPlugin : Plugin { 9 | 10 | override fun apply(project: Project) { 11 | addInitializerDependencyInProject(project) 12 | registerTransformIfAppProject(project) 13 | } 14 | 15 | private fun addInitializerDependencyInProject(project: Project) { 16 | fun getInitializerPluginVersion(): String { 17 | return project.rootProject 18 | .buildscript 19 | .configurations 20 | .getByName("classpath") 21 | .dependencies 22 | .firstOrNull { (it.group == POM_GROUP_ID) and (it.name == POM_PLUGIN_ARTIFACT_ID) } 23 | ?.let { it.version } 24 | ?: throw RuntimeException("can not found initializer gradle plugin in classpath") 25 | } 26 | 27 | project.dependencies.add( 28 | "implementation", 29 | "$POM_GROUP_ID:$POM_RUNTIME_ARTIFACT_ID:${getInitializerPluginVersion()}" 30 | ) 31 | } 32 | 33 | private fun registerTransformIfAppProject(project: Project) { 34 | val app = project.plugins.hasPlugin(AppPlugin::class.java) 35 | if (app) { 36 | val android = project.extensions.getByType(AppExtension::class.java) 37 | android.registerTransform(InitializerTransform(project)) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /initializer-gradle-plugin/src/main/kotlin/com/chrnie/initializer/plugin/InitializerTransform.kt: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.plugin 2 | 3 | import com.android.build.api.transform.Format 4 | import com.android.build.api.transform.QualifiedContent 5 | import com.android.build.api.transform.Transform 6 | import com.android.build.api.transform.TransformInvocation 7 | import com.android.build.gradle.internal.pipeline.TransformManager 8 | import org.gradle.api.Project 9 | import java.io.File 10 | import java.io.FileInputStream 11 | import java.util.jar.JarFile 12 | 13 | class InitializerTransform(private val project: Project) : Transform() { 14 | 15 | private val logger = project.logger 16 | 17 | override fun getName(): String = InitializerTransform::class.simpleName!! 18 | 19 | override fun getInputTypes(): MutableSet = 20 | TransformManager.CONTENT_CLASS 21 | 22 | override fun getScopes(): MutableSet = 23 | TransformManager.SCOPE_FULL_PROJECT 24 | 25 | override fun isIncremental(): Boolean = false 26 | 27 | override fun transform(invocation: TransformInvocation) { 28 | logger.quiet("Initializer register in project: ${project.name}") 29 | 30 | val inputs = invocation.inputs 31 | val outputProvider = invocation.outputProvider 32 | 33 | val classList = inputs.asSequence() 34 | .flatMap { input -> 35 | sequenceOf( 36 | input.jarInputs 37 | .asSequence() 38 | .map { jarInput -> 39 | outputProvider.getContentLocation( 40 | jarInput.name, 41 | jarInput.contentTypes, 42 | jarInput.scopes, 43 | Format.JAR 44 | ).also { jarInput.file.copyTo(it, true) } 45 | }.flatMap { findClassInJarFile(it) }, 46 | input.directoryInputs 47 | .asSequence() 48 | .map { directoryInput -> 49 | outputProvider.getContentLocation( 50 | directoryInput.name, 51 | directoryInput.contentTypes, 52 | directoryInput.scopes, 53 | Format.DIRECTORY 54 | ).also { directoryInput.file.copyRecursively(it, true) } 55 | }.flatMap { findClassInDirectory(it) } 56 | ).flatten() 57 | } 58 | .toList() 59 | 60 | val taskNameList = findAllTaskName(classList) 61 | logger.quiet("Found task class: $taskNameList") 62 | 63 | classList.firstOrNull { HOOK_CLASS_NAME == it.name } 64 | ?.let { CodeGenerator(it.outputFile, taskNameList) } 65 | ?.generate() 66 | ?: throw RuntimeException("can not found $HOOK_CLASS_FILE_NAME") 67 | } 68 | 69 | private fun findClassInJarFile(file: File): Sequence { 70 | return JarFile(file).use { jarFile -> 71 | jarFile.entries() 72 | .toList() 73 | .filter { !it.isDirectory and it.name.endsWith(".class") } 74 | .map { classEntry -> 75 | jarFile.getInputStream(classEntry).use { ClassMetadata(it, file) } 76 | } 77 | }.asSequence() 78 | } 79 | 80 | private fun findClassInDirectory(file: File): Sequence { 81 | return file.walkTopDown() 82 | .asSequence() 83 | .filter { it.isFile and it.name.endsWith(".class") } 84 | .map { classFile -> 85 | FileInputStream(classFile).use { ClassMetadata(it, file) } 86 | } 87 | } 88 | 89 | private fun findAllTaskName(list: List): List { 90 | fun traversTaskMetadata( 91 | superName: String, 92 | superNameToMetadata: Map> 93 | ): Sequence { 94 | return superNameToMetadata[superName] 95 | ?.asSequence() 96 | ?.flatMap { traversTaskMetadata(it.name, superNameToMetadata) + it } 97 | ?: emptySequence() 98 | } 99 | 100 | fun ensureTaskClassValid(classMetadata: ClassMetadata) { 101 | if (!classMetadata.isPublic) { 102 | throw RuntimeException("task class is not public access: ${classMetadata.name}") 103 | } 104 | 105 | if (classMetadata.isAbstract) { 106 | throw RuntimeException("task class is abstract: ${classMetadata.name}") 107 | } 108 | 109 | classMetadata.methods.firstOrNull { methodMetadata -> 110 | val constructor = methodMetadata.name == "" 111 | val noneParam = methodMetadata.descriptor == "()V" 112 | val public = methodMetadata.isPublic 113 | constructor and noneParam and public 114 | } 115 | ?: throw RuntimeException("task class has not public empty constructor: ${classMetadata.name}") 116 | } 117 | 118 | return list.asSequence() 119 | .filter { it.superName != null } 120 | .groupBy { it.superName!! } 121 | .let { traversTaskMetadata(TASK_CLASS_SUPER_NAME, it) } 122 | .filter { !it.isAbstract } 123 | .map { it.apply { ensureTaskClassValid(this) } } 124 | .map { it.name } 125 | .toList() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /initializer-gradle-plugin/src/main/kotlin/com/chrnie/initializer/plugin/Metadata.kt: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.plugin 2 | 3 | import org.objectweb.asm.ClassReader 4 | import org.objectweb.asm.ClassVisitor 5 | import org.objectweb.asm.MethodVisitor 6 | import org.objectweb.asm.Opcodes 7 | import java.io.File 8 | import java.io.InputStream 9 | 10 | interface ClassMetadata { 11 | val isPublic: Boolean 12 | val isAbstract: Boolean 13 | val name: String 14 | val superName: String? 15 | val outputFile: File 16 | val methods: List 17 | } 18 | 19 | fun ClassMetadata(inputStream: InputStream, outputFile: File): ClassMetadata { 20 | val cr = ClassReader(inputStream) 21 | return AsmClassMetadata(cr, outputFile) 22 | } 23 | 24 | interface MethodMetadata { 25 | val isPublic: Boolean 26 | val name: String 27 | val descriptor: String 28 | } 29 | 30 | private class AsmClassMetadata( 31 | private val cr: ClassReader, 32 | override val outputFile: File 33 | ) : ClassMetadata { 34 | override val isPublic: Boolean = cr.access and Opcodes.ACC_PUBLIC != 0 35 | override val isAbstract: Boolean = cr.access and Opcodes.ACC_ABSTRACT != 0 36 | override val name: String = cr.className 37 | override val superName: String? = cr.superName 38 | override val methods: List by lazy { 39 | MethodMetadataClassVisitor().methods(cr) 40 | } 41 | 42 | private class MethodMetadataClassVisitor : ClassVisitor(Opcodes.ASM5) { 43 | private var _methods: Sequence? = null 44 | 45 | fun methods(cr: ClassReader): List { 46 | if (_methods != null) { 47 | throw RuntimeException("only call once") 48 | } 49 | cr.accept(this, 0) 50 | return _methods!!.toList() 51 | } 52 | 53 | override fun visitMethod( 54 | access: Int, 55 | name: String, 56 | descriptor: String, 57 | signature: String?, 58 | exceptions: Array? 59 | ): MethodVisitor? { 60 | _methods = (_methods ?: emptySequence()) + AsmMethodMetadata( 61 | access and Opcodes.ACC_PUBLIC != 0, 62 | name, 63 | descriptor 64 | ) 65 | return null 66 | } 67 | } 68 | } 69 | 70 | private class AsmMethodMetadata( 71 | override val isPublic: Boolean, 72 | override val name: String, 73 | override val descriptor: String 74 | ) : MethodMetadata 75 | -------------------------------------------------------------------------------- /initializer/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /initializer/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.compileSdkVersion 5 | buildToolsVersion rootProject.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.minSdkVersion 9 | targetSdkVersion rootProject.targetSdkVersion 10 | 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | } 14 | 15 | dependencies { 16 | androidTestImplementation deps['test-core'] 17 | androidTestImplementation deps['test-runner'] 18 | androidTestImplementation deps['test-junit'] 19 | androidTestImplementation deps['mockito'] 20 | } 21 | 22 | apply from: "$rootDir/bintray.gradle" -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/DelayTest.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | import static org.junit.Assert.assertTrue; 6 | import static org.mockito.Mockito.spy; 7 | import static org.mockito.Mockito.verify; 8 | 9 | import androidx.test.ext.junit.runners.AndroidJUnit4; 10 | import java.util.concurrent.Executor; 11 | import java.util.concurrent.TimeUnit; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | @RunWith(AndroidJUnit4.class) 16 | public class DelayTest { 17 | 18 | @Test(expected = IllegalArgumentException.class) 19 | public void construct_failOnNegativeDuration() { 20 | new Delay(-1, TimeUnit.MILLISECONDS); 21 | } 22 | 23 | @Test(expected = NullPointerException.class) 24 | public void construct_failOnNullTimeUnit() { 25 | new Delay(0, null); 26 | } 27 | 28 | @Test 29 | public void equals() { 30 | Delay delay1 = new Delay(100, TimeUnit.MILLISECONDS); 31 | Delay delay2 = new Delay(100, TimeUnit.MILLISECONDS); 32 | assertEquals(delay1, delay2); 33 | assertEquals(delay1.hashCode(), delay2.hashCode()); 34 | 35 | Delay delay3 = new Delay(TimeUnit.MILLISECONDS.toNanos(100), TimeUnit.NANOSECONDS); 36 | assertEquals(delay1, delay3); 37 | assertEquals(delay1.hashCode(), delay2.hashCode()); 38 | 39 | assertNotEquals(Delay.NONE, delay1); 40 | 41 | Delay delay4 = new Delay(100, TimeUnit.MICROSECONDS); 42 | assertEquals(Delay.NONE, delay4); 43 | assertEquals(Delay.NONE, delay4); 44 | 45 | Delay delay5 = new Delay(100, TimeUnit.NANOSECONDS); 46 | assertEquals(Delay.NONE, delay5); 47 | assertEquals(Delay.NONE, delay5); 48 | } 49 | 50 | @Test 51 | public void runDelayed() { 52 | Delay delay = new Delay(100, TimeUnit.MILLISECONDS); 53 | 54 | Executor executor = spy(MainExecutor.get()); 55 | Future future = new Future() { 56 | @Override 57 | protected Long compute() { 58 | return System.nanoTime(); 59 | } 60 | }; 61 | 62 | long startTime = System.nanoTime(); 63 | delay.runDelayed(executor, future); 64 | Long runTime = future.get(); 65 | 66 | verify(executor).execute(future); 67 | long delayedTime = TimeUnit.NANOSECONDS.toMillis(runTime - startTime); 68 | assertTrue(delayedTime >= 100); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/Future.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import java.util.concurrent.SynchronousQueue; 4 | 5 | public abstract class Future implements Runnable { 6 | 7 | private final SynchronousQueue queue = new SynchronousQueue<>(); 8 | 9 | protected abstract V compute(); 10 | 11 | @Override 12 | public final void run() { 13 | V value = compute(); 14 | try { 15 | queue.put(value); 16 | } catch (InterruptedException e) { 17 | throw new AssertionError(e); 18 | } 19 | } 20 | 21 | public final V get() { 22 | try { 23 | return queue.take(); 24 | } catch (InterruptedException e) { 25 | throw new AssertionError(e); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/GraphNodeTest.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertNull; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import androidx.test.ext.junit.runners.AndroidJUnit4; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Set; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | @RunWith(AndroidJUnit4.class) 16 | public class GraphNodeTest { 17 | 18 | @Test 19 | public void defaultParent() { 20 | MockGraphNode node = new MockGraphNode(0); 21 | Set parent = node.getParent(); 22 | assertEquals(0, parent.size()); 23 | } 24 | 25 | @Test 26 | public void defaultChildren() { 27 | MockGraphNode node = new MockGraphNode(0); 28 | Set children = node.getChildren(); 29 | assertEquals(0, children.size()); 30 | } 31 | 32 | @Test 33 | public void addChild() { 34 | MockGraphNode node0 = new MockGraphNode(0); 35 | MockGraphNode node1 = new MockGraphNode(1); 36 | 37 | node0.addChild(node1); 38 | 39 | { 40 | Set children = node0.getChildren(); 41 | assertEquals(1, children.size()); 42 | assertTrue(children.contains(node1)); 43 | 44 | Set parent = node1.getParent(); 45 | assertEquals(1, parent.size()); 46 | assertTrue(parent.contains(node0)); 47 | } 48 | 49 | // test repeat add same child without change 50 | node0.addChild(node1); 51 | 52 | { 53 | Set children = node0.getChildren(); 54 | assertEquals(1, children.size()); 55 | assertTrue(children.contains(node1)); 56 | 57 | Set parent = node1.getParent(); 58 | assertEquals(1, parent.size()); 59 | assertTrue(parent.contains(node0)); 60 | } 61 | } 62 | 63 | @Test 64 | public void findCycle_graphWithoutCycle() { 65 | MockGraphNode node0 = new MockGraphNode(0); 66 | MockGraphNode node1 = new MockGraphNode(1); 67 | MockGraphNode node2 = new MockGraphNode(2); 68 | 69 | node0.addChild(node1); 70 | node1.addChild(node2); 71 | 72 | List cycle = node0.findCycle(); 73 | assertNull(cycle); 74 | } 75 | 76 | @Test 77 | public void findCycle_graphWithCycle() { 78 | MockGraphNode node0 = new MockGraphNode(0); 79 | MockGraphNode node1 = new MockGraphNode(1); 80 | MockGraphNode node2 = new MockGraphNode(2); 81 | 82 | node0.addChild(node1); 83 | node1.addChild(node2); 84 | node2.addChild(node0); 85 | 86 | List cycle = node0.findCycle(); 87 | assertNotNull(cycle); 88 | assertEquals(Arrays.asList(node0, node2, node1, node0), cycle); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/InitializerTest.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4; 4 | import com.chrnie.initializer.exception.DependencyNotFoundException; 5 | import com.chrnie.initializer.exception.DuplicateTaskException; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | 12 | @RunWith(AndroidJUnit4.class) 13 | public class InitializerTest { 14 | 15 | @Test(expected = DuplicateTaskException.class) 16 | public void buildTaskGraph_failOnDuplicateTask() { 17 | List taskList = Arrays.asList(new MockTask("test"), new MockTask("test")); 18 | Initializer.buildTaskGraph(taskList); 19 | } 20 | 21 | @Test(expected = DependencyNotFoundException.class) 22 | public void buildTaskGraph_failOnDependencyNotFound() { 23 | MockTask task = new MockTask("test") { 24 | @Override 25 | protected void config() { 26 | dependOn(""); 27 | } 28 | }; 29 | Initializer.buildTaskGraph(Collections.singletonList(task)); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/MainExecutorTest.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import android.os.Looper; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import java.util.concurrent.Executor; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | @RunWith(AndroidJUnit4.class) 12 | public class MainExecutorTest { 13 | 14 | @Test 15 | public void executeInMainThread() { 16 | Executor executor = MainExecutor.get(); 17 | 18 | final Future future = new Future() { 19 | @Override 20 | protected Thread compute() { 21 | return Thread.currentThread(); 22 | } 23 | }; 24 | 25 | executor.execute(future); 26 | 27 | Thread mainThread = Looper.getMainLooper().getThread(); 28 | Thread runThread = future.get(); 29 | assertEquals(mainThread, runThread); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/MockGraphNode.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | public class MockGraphNode extends GraphNode { 4 | 5 | private final int id; 6 | 7 | public MockGraphNode(int id) { 8 | this.id = id; 9 | } 10 | 11 | @Override 12 | public boolean equals(Object o) { 13 | if (this == o) { 14 | return true; 15 | } 16 | if (o == null || getClass() != o.getClass()) { 17 | return false; 18 | } 19 | 20 | MockGraphNode that = (MockGraphNode) o; 21 | 22 | return id == that.id; 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return id; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/MockTask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import android.content.Context; 4 | 5 | public class MockTask extends Task { 6 | 7 | public MockTask() { 8 | this(""); 9 | } 10 | 11 | public MockTask(String name) { 12 | super(name); 13 | } 14 | 15 | @Override 16 | public void run(Context context) { 17 | 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/TaskNodeTest.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | import static org.junit.Assert.assertNull; 6 | 7 | import android.content.Context; 8 | import androidx.test.ext.junit.runners.AndroidJUnit4; 9 | import com.chrnie.initializer.exception.CyclicDependencyException; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.Executor; 15 | import java.util.concurrent.Executors; 16 | import java.util.concurrent.TimeUnit; 17 | import org.junit.Test; 18 | import org.junit.runner.RunWith; 19 | 20 | @RunWith(AndroidJUnit4.class) 21 | public class TaskNodeTest { 22 | 23 | @Test(expected = IllegalStateException.class) 24 | public void execute_failOnMultipleCall() { 25 | TaskNode node = new TaskNode(); 26 | node.execute(null, false); 27 | node.execute(null,false); 28 | } 29 | 30 | @Test(expected = CyclicDependencyException.class) 31 | public void execute_failOnCyclicNode() { 32 | TaskNode node1 = new TaskNode("1", null, null, null); 33 | TaskNode node2 = new TaskNode("2", null, null, null); 34 | TaskNode node3 = new TaskNode("3", null, null, null); 35 | 36 | node1.addChild(node2); 37 | node2.addChild(node3); 38 | node3.addChild(node1); 39 | 40 | node1.execute(null, true); 41 | } 42 | 43 | @Test(expected = CyclicDependencyException.class) 44 | public void execute_failOnSelfCycle() { 45 | TaskNode node = new TaskNode("tset", null, null, null); 46 | node.addChild(node); 47 | node.execute(null, true); 48 | } 49 | 50 | @Test 51 | public void execute() { 52 | final List actionList = new ArrayList<>(); 53 | final CountDownLatch latch = new CountDownLatch(4); 54 | 55 | Executor executor = Executors.newFixedThreadPool(4); 56 | 57 | final Action action0 = new Action() { 58 | @Override 59 | public void run(Context context) { 60 | assertNull(context); 61 | actionList.add(this); 62 | latch.countDown(); 63 | } 64 | }; 65 | final TaskNode node0 = new TaskNode("0", Delay.NONE, executor, action0); 66 | 67 | final Action action1 = new Action() { 68 | @Override 69 | public void run(Context context) { 70 | assertNull(context); 71 | actionList.add(this); 72 | latch.countDown(); 73 | } 74 | }; 75 | TaskNode node1 = new TaskNode("1", Delay.NONE, executor, action1); 76 | 77 | final Action action2 = new Action() { 78 | @Override 79 | public void run(Context context) { 80 | assertNull(context); 81 | actionList.add(this); 82 | latch.countDown(); 83 | 84 | try { 85 | Thread.sleep(200); 86 | } catch (InterruptedException e) { 87 | throw new AssertionError(e); 88 | } 89 | } 90 | }; 91 | TaskNode node2 = new TaskNode("2", Delay.NONE, executor, action2); 92 | 93 | Action action3 = new Action() { 94 | @Override 95 | public void run(Context context) { 96 | assertNull(context); 97 | actionList.add(this); 98 | latch.countDown(); 99 | } 100 | }; 101 | TaskNode node3 = new TaskNode( 102 | "3", new Delay(100, TimeUnit.MILLISECONDS), executor, action3 103 | ); 104 | 105 | node0.addChild(node1); 106 | node0.addChild(node2); 107 | node0.addChild(node3); 108 | node2.addChild(node1); 109 | 110 | node0.execute(null,false); 111 | 112 | try { 113 | latch.await(); 114 | } catch (InterruptedException e) { 115 | throw new AssertionError(e); 116 | } 117 | 118 | assertEquals(Arrays.asList(action0, action2, action3, action1), actionList); 119 | } 120 | 121 | @Test 122 | public void equals() { 123 | TaskNode node1 = new TaskNode(); 124 | TaskNode node2 = new TaskNode(); 125 | assertEquals(node1, node2); 126 | assertEquals(node1.hashCode(), node2.hashCode()); 127 | 128 | TaskNode node3 = new TaskNode(null, null, null, null); 129 | assertEquals(node1, node3); 130 | assertEquals(node1.hashCode(), node3.hashCode()); 131 | 132 | TaskNode node4 = new TaskNode("test", null, null, null); 133 | assertNotEquals(node3, node4); 134 | 135 | TaskNode node5 = new TaskNode( 136 | "test", 137 | new Delay(100, TimeUnit.MILLISECONDS), 138 | Executors.newSingleThreadExecutor(), 139 | new Action() { 140 | @Override 141 | public void run(Context context) { 142 | 143 | } 144 | } 145 | ); 146 | assertEquals(node4, node5); 147 | assertEquals(node4.hashCode(), node5.hashCode()); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /initializer/src/androidTest/java/com.chrnie.initializer/TaskTest.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.mockito.Mockito.spy; 6 | import static org.mockito.Mockito.verify; 7 | 8 | import androidx.test.ext.junit.runners.AndroidJUnit4; 9 | import java.util.List; 10 | import java.util.concurrent.Executor; 11 | import java.util.concurrent.TimeUnit; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | @RunWith(AndroidJUnit4.class) 16 | public class TaskTest { 17 | 18 | @Test(expected = NullPointerException.class) 19 | public void construct_failOnNullTaskName() { 20 | new MockTask(null); 21 | } 22 | 23 | @Test 24 | public void construct_taskName() { 25 | MockTask task = new MockTask("test"); 26 | assertEquals("test", task.getName()); 27 | } 28 | 29 | @Test 30 | public void buildConfig_callConfigMethod() { 31 | MockTask task = new MockTask(); 32 | MockTask spyTask = spy(task); 33 | spyTask.buildConfig(); 34 | verify(spyTask).config(); 35 | } 36 | 37 | @Test 38 | public void defaultDependencies() { 39 | Task task = new MockTask(); 40 | task.buildConfig(); 41 | 42 | List dependencies = task.getDependencies(); 43 | assertEquals(0, dependencies.size()); 44 | } 45 | 46 | @Test(expected = IllegalStateException.class) 47 | public void dependOn_failOnCallNotInConfigMethod() { 48 | Task task = new MockTask(); 49 | task.dependOn(""); 50 | } 51 | 52 | @Test(expected = NullPointerException.class) 53 | public void dependOn_failOnDependNullTaskName() { 54 | Task task = new MockTask() { 55 | @Override 56 | protected void config() { 57 | dependOn(null); 58 | } 59 | }; 60 | 61 | task.buildConfig(); 62 | } 63 | 64 | @Test 65 | public void dependOn() { 66 | MockTask task = new MockTask() { 67 | @Override 68 | protected void config() { 69 | dependOn("test1"); 70 | dependOn("test2"); 71 | } 72 | }; 73 | 74 | task.buildConfig(); 75 | List dependencies = task.getDependencies(); 76 | 77 | assertEquals(2, dependencies.size()); 78 | assertTrue(dependencies.contains("test1")); 79 | assertTrue(dependencies.contains("test2")); 80 | } 81 | 82 | @Test 83 | public void defaultExecutor() { 84 | MockTask task = new MockTask(); 85 | task.buildConfig(); 86 | assertEquals(MainExecutor.get(), task.getExecutor()); 87 | } 88 | 89 | @Test(expected = IllegalStateException.class) 90 | public void setExecutor_failOnCallNotInConfigMethod() { 91 | Task task = new MockTask(); 92 | task.setExecutor(MainExecutor.get()); 93 | } 94 | 95 | @Test(expected = NullPointerException.class) 96 | public void setExecutor_failOnSetNullExecutor() { 97 | MockTask task = new MockTask() { 98 | @Override 99 | protected void config() { 100 | setExecutor(null); 101 | } 102 | }; 103 | 104 | task.buildConfig(); 105 | } 106 | 107 | @Test 108 | public void setExecutor() { 109 | final Executor executor = new Executor() { 110 | @Override 111 | public void execute(Runnable command) { 112 | 113 | } 114 | }; 115 | 116 | MockTask task = new MockTask() { 117 | @Override 118 | protected void config() { 119 | setExecutor(executor); 120 | } 121 | }; 122 | 123 | task.buildConfig(); 124 | assertEquals(executor, task.getExecutor()); 125 | } 126 | 127 | @Test 128 | public void defaultDelay() { 129 | MockTask task = new MockTask(); 130 | task.buildConfig(); 131 | assertEquals(Delay.NONE, task.getDelay()); 132 | } 133 | 134 | @Test(expected = IllegalStateException.class) 135 | public void setDelay_failOnCallNotInConfigMethod() { 136 | Task task = new MockTask(); 137 | task.setDelay(0, TimeUnit.MILLISECONDS); 138 | } 139 | 140 | @Test(expected = IllegalArgumentException.class) 141 | public void setDelay_failOnSetNegativeDelay() { 142 | MockTask task = new MockTask() { 143 | @Override 144 | protected void config() { 145 | setDelay(-1, TimeUnit.MILLISECONDS); 146 | } 147 | }; 148 | 149 | task.buildConfig(); 150 | } 151 | 152 | @Test(expected = NullPointerException.class) 153 | public void setDelay_failOnSetNullTimeUnit() { 154 | MockTask task = new MockTask() { 155 | @Override 156 | protected void config() { 157 | setDelay(0, null); 158 | } 159 | }; 160 | 161 | task.buildConfig(); 162 | } 163 | 164 | @Test 165 | public void setDelay() { 166 | MockTask task = new MockTask() { 167 | @Override 168 | protected void config() { 169 | setDelay(10, TimeUnit.MILLISECONDS); 170 | } 171 | }; 172 | 173 | task.buildConfig(); 174 | assertEquals(new Delay(10, TimeUnit.MILLISECONDS), task.getDelay()); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /initializer/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/Action.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import android.content.Context; 4 | 5 | interface Action { 6 | 7 | Action EMPTY = new Action() { 8 | @Override 9 | public void run(Context context) { 10 | // empty 11 | } 12 | }; 13 | 14 | void run(Context context); 15 | } 16 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/Delay.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import java.util.concurrent.Executor; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | final class Delay { 9 | 10 | static final Delay NONE = new Delay(0, TimeUnit.MILLISECONDS); 11 | 12 | private static final Handler HANDLER = new Handler(Looper.getMainLooper()); 13 | 14 | private final long millis; 15 | 16 | Delay(long duration, TimeUnit unit) { 17 | if (duration < 0) { 18 | throw new IllegalArgumentException("duration < 0"); 19 | } 20 | 21 | if (unit == null) { 22 | throw new NullPointerException("unit == null"); 23 | } 24 | 25 | this.millis = unit.toMillis(duration); 26 | } 27 | 28 | void runDelayed(final Executor executor, final Runnable r) { 29 | if (millis == 0L) { 30 | executor.execute(r); 31 | return; 32 | } 33 | 34 | final Runnable delayedRunnable = new Runnable() { 35 | @Override 36 | public void run() { 37 | executor.execute(r); 38 | } 39 | }; 40 | HANDLER.postDelayed(delayedRunnable, millis); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) { 46 | return true; 47 | } 48 | if (o == null || getClass() != o.getClass()) { 49 | return false; 50 | } 51 | 52 | Delay delay = (Delay) o; 53 | return delay.millis == millis; 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return (int) millis; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/GraphNode.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | abstract class GraphNode> { 9 | 10 | private final Set parent = new HashSet<>(); 11 | private final Set children = new HashSet<>(); 12 | 13 | @SuppressWarnings("unchecked") 14 | final void addChild(GraphNode child) { 15 | if (child == null) { 16 | throw new NullPointerException("child == null"); 17 | } 18 | 19 | child.parent.add((T) this); 20 | children.add((T) child); 21 | } 22 | 23 | final Set getParent() { 24 | return parent; 25 | } 26 | 27 | final Set getChildren() { 28 | return children; 29 | } 30 | 31 | final List findCycle() { 32 | Set> visited = new HashSet<>(); 33 | Set> inStack = new HashSet<>(); 34 | List cyclicNode = new ArrayList<>(); 35 | deepFirstTraverse(visited, inStack, cyclicNode); 36 | return cyclicNode.isEmpty() ? null : cyclicNode; 37 | } 38 | 39 | @SuppressWarnings("unchecked") 40 | private GraphNode deepFirstTraverse( 41 | Set> visited, 42 | Set> inStack, 43 | List cyclicNode) { 44 | 45 | if (visited.contains(this)) { 46 | return null; 47 | } 48 | 49 | if (inStack.contains(this)) { 50 | cyclicNode.add((T) this); 51 | return this; 52 | } 53 | 54 | inStack.add(this); 55 | 56 | for (GraphNode child : children) { 57 | GraphNode cycleOrigin = child.deepFirstTraverse(visited, inStack, cyclicNode); 58 | 59 | if (cyclicNode.isEmpty()) { 60 | continue; 61 | } 62 | 63 | if (cycleOrigin == null) { 64 | return null; 65 | } 66 | 67 | cyclicNode.add((T) this); 68 | return cycleOrigin.equals(this) ? null : cycleOrigin; 69 | } 70 | 71 | inStack.remove(this); 72 | visited.add(this); 73 | return null; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/Initializer.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.exception.DependencyNotFoundException; 5 | import com.chrnie.initializer.exception.DuplicateTaskException; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public final class Initializer { 11 | 12 | private static Initializer instance; 13 | 14 | public static void init(Context context) { 15 | init(context, true); 16 | } 17 | 18 | public synchronized static void init(Context context, boolean debug) { 19 | if(context == null){ 20 | throw new NullPointerException("context == null"); 21 | } 22 | 23 | if (instance != null) { 24 | return; 25 | } 26 | 27 | instance = new Initializer(debug); 28 | instance.initialize(context.getApplicationContext()); 29 | } 30 | 31 | public synchronized static boolean isDebug() { 32 | if (instance == null) { 33 | throw new IllegalStateException("call Initializer.init() first"); 34 | } 35 | return instance.debug; 36 | } 37 | 38 | private final boolean debug; 39 | 40 | private Initializer(boolean debug) { 41 | this.debug = debug; 42 | } 43 | 44 | private void initialize(Context context) { 45 | List taskList = TaskLoader.requestTaskList(); 46 | TaskNode rootNode = buildTaskGraph(taskList); 47 | rootNode.execute(context, debug); 48 | } 49 | 50 | static TaskNode buildTaskGraph(List taskList) { 51 | Map nameToNode = new HashMap<>(); 52 | 53 | for (Task task : taskList) { 54 | String taskName = task.getName(); 55 | if (nameToNode.containsKey(taskName)) { 56 | throw new DuplicateTaskException("duplicate task: " + taskName); 57 | } 58 | 59 | task.buildConfig(); 60 | TaskNode node = new TaskNode(taskName, task.getDelay(), task.getExecutor(), task); 61 | nameToNode.put(taskName, node); 62 | } 63 | 64 | TaskNode rootNode = new TaskNode(); 65 | for (Task task : taskList) { 66 | String taskName = task.getName(); 67 | TaskNode taskNode = nameToNode.get(taskName); 68 | rootNode.addChild(taskNode); 69 | 70 | List dependencies = task.getDependencies(); 71 | for (String parentName : dependencies) { 72 | TaskNode parentNode = nameToNode.get(parentName); 73 | if (parentNode == null) { 74 | throw new DependencyNotFoundException("can not found dependency task: " + parentName); 75 | } 76 | 77 | parentNode.addChild(taskNode); 78 | } 79 | } 80 | return rootNode; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/MainExecutor.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import android.os.Build.VERSION; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.os.Message; 7 | import java.util.concurrent.Executor; 8 | 9 | class MainExecutor implements Executor { 10 | 11 | private static final MainExecutor INSTANCE = new MainExecutor(); 12 | 13 | public static Executor get() { 14 | return INSTANCE; 15 | } 16 | 17 | private final Handler handler; 18 | 19 | private MainExecutor() { 20 | handler = new Handler(Looper.getMainLooper()); 21 | } 22 | 23 | @Override 24 | public void execute(Runnable command) { 25 | Message message = Message.obtain(handler, command); 26 | if (VERSION.SDK_INT >= 22) { 27 | message.setAsynchronous(true); 28 | } 29 | message.sendToTarget(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/Task.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.concurrent.Executor; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public abstract class Task implements Action { 10 | 11 | private final String name; 12 | 13 | private boolean buildingConfig = false; 14 | private Executor executor = MainExecutor.get(); 15 | private List dependencies = null; 16 | private Delay delay = Delay.NONE; 17 | 18 | public Task(String name) { 19 | if (name == null) { 20 | throw new NullPointerException("name == null"); 21 | } 22 | this.name = name; 23 | } 24 | 25 | public final String getName() { 26 | return name; 27 | } 28 | 29 | final Executor getExecutor() { 30 | return executor; 31 | } 32 | 33 | final List getDependencies() { 34 | return dependencies == null ? Collections.emptyList() : dependencies; 35 | } 36 | 37 | final Delay getDelay() { 38 | return delay; 39 | } 40 | 41 | final void buildConfig() { 42 | buildingConfig = true; 43 | config(); 44 | buildingConfig = false; 45 | } 46 | 47 | protected void config() { 48 | 49 | } 50 | 51 | protected final void dependOn(String taskName) { 52 | if (!buildingConfig) { 53 | throw new IllegalStateException("only call dependOn in config() method"); 54 | } 55 | 56 | if (taskName == null) { 57 | throw new NullPointerException("taskName == null"); 58 | } 59 | 60 | if (dependencies == null) { 61 | dependencies = new ArrayList<>(4); 62 | } 63 | 64 | dependencies.add(taskName); 65 | } 66 | 67 | protected final void setExecutor(Executor executor) { 68 | if (!buildingConfig) { 69 | throw new IllegalStateException("only call setExecutor in config() method"); 70 | } 71 | 72 | if (executor == null) { 73 | throw new NullPointerException("executor == null"); 74 | } 75 | 76 | this.executor = executor; 77 | } 78 | 79 | protected final void setDelay(long delay, TimeUnit unit) { 80 | if (!buildingConfig) { 81 | throw new IllegalStateException("only call setDelay in config() method"); 82 | } 83 | 84 | this.delay = new Delay(delay, unit); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/TaskLoader.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | final class TaskLoader { 7 | 8 | private static void loadTasks(List outList) { 9 | /* 10 | * hook method for generate code, for example: 11 | * outList.add(new XXTask()); 12 | */ 13 | } 14 | 15 | static List requestTaskList() { 16 | List result = new ArrayList<>(); 17 | loadTasks(result); 18 | return result; 19 | } 20 | 21 | private TaskLoader() { 22 | // util class 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/TaskNode.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import com.chrnie.initializer.exception.CyclicDependencyException; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | import java.util.concurrent.Executor; 10 | 11 | final class TaskNode extends GraphNode { 12 | 13 | private final String name; 14 | private final Delay delay; 15 | private final Executor executor; 16 | private final Action action; 17 | 18 | private Set dependencies = null; 19 | private boolean executed = false; 20 | 21 | TaskNode() { 22 | this(null, null, null, null); 23 | } 24 | 25 | TaskNode(String name, Delay delay, Executor executor, Action action) { 26 | this.name = name; 27 | this.delay = delay != null ? delay : Delay.NONE; 28 | this.executor = executor != null ? executor : MainExecutor.get(); 29 | this.action = action != null ? action : Action.EMPTY; 30 | } 31 | 32 | void execute(Context context, boolean checkCycle) { 33 | if (checkCycle) { 34 | ensureNoneCyclicDependency(); 35 | } 36 | executeInternal(context, this); 37 | } 38 | 39 | private synchronized void executeInternal(final Context context, TaskNode caller) { 40 | if (executed) { 41 | throw new IllegalStateException("task has been executed"); 42 | } 43 | 44 | if (dependencies == null) { 45 | this.dependencies = new HashSet<>(getParent()); 46 | } 47 | 48 | dependencies.remove(caller); 49 | if (!dependencies.isEmpty()) { 50 | return; 51 | } 52 | 53 | executed = true; 54 | 55 | final Runnable r = new Runnable() { 56 | @Override 57 | public void run() { 58 | action.run(context); 59 | 60 | for (TaskNode child : TaskNode.this.getChildren()) { 61 | child.executeInternal(context, TaskNode.this); 62 | } 63 | } 64 | }; 65 | 66 | delay.runDelayed(executor, r); 67 | } 68 | 69 | private void ensureNoneCyclicDependency() { 70 | List cyclicNode = findCycle(); 71 | if (cyclicNode == null) { 72 | return; 73 | } 74 | 75 | throw new CyclicDependencyException( 76 | "cyclic dependency: [" + TextUtils.join(" -> ", cyclicNode) + ']' 77 | ); 78 | } 79 | 80 | @Override 81 | public boolean equals(Object o) { 82 | if (this == o) { 83 | return true; 84 | } 85 | if (o == null || getClass() != o.getClass()) { 86 | return false; 87 | } 88 | 89 | TaskNode node = (TaskNode) o; 90 | 91 | return name != null ? name.equals(node.name) : node.name == null; 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | return name != null ? name.hashCode() : 0; 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return name != null ? name : ""; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/exception/CyclicDependencyException.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.exception; 2 | 3 | public class CyclicDependencyException extends RuntimeException { 4 | 5 | public CyclicDependencyException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/exception/DependencyNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.exception; 2 | 3 | public class DependencyNotFoundException extends RuntimeException { 4 | 5 | public DependencyNotFoundException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /initializer/src/main/java/com/chrnie/initializer/exception/DuplicateTaskException.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.exception; 2 | 3 | public class DuplicateTaskException extends RuntimeException { 4 | 5 | public DuplicateTaskException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /sample/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/component.gradle" 2 | 3 | dependencies { 4 | implementation project(':basic') 5 | implementation project(':module_a') 6 | implementation project(':module_b') 7 | } 8 | -------------------------------------------------------------------------------- /sample/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/chrnie/initializer/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.sample; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.widget.TextView; 6 | 7 | public class MainActivity extends Activity { 8 | 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | TextView tv = new TextView(this); 13 | tv.setText("App Activity"); 14 | setContentView(tv); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/chrnie/initializer/sample/task/ATask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.sample.task; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.basic.BaseTask; 5 | import com.chrnie.initializer.basic.Tasks; 6 | 7 | public class ATask extends BaseTask { 8 | 9 | public ATask() { 10 | super(Tasks.App.A); 11 | } 12 | 13 | @Override 14 | protected void config() { 15 | dependOn(Tasks.ModuleA.A); 16 | dependOn(Tasks.ModuleB.A); 17 | } 18 | 19 | @Override 20 | protected void onRun(Context context) { 21 | try { 22 | Thread.sleep(200); 23 | } catch (InterruptedException ignore) { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/chrnie/initializer/sample/task/BTask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.sample.task; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.basic.AppExecutors; 5 | import com.chrnie.initializer.basic.BaseTask; 6 | import com.chrnie.initializer.basic.Tasks; 7 | 8 | public class BTask extends BaseTask { 9 | 10 | public BTask() { 11 | super(Tasks.App.B); 12 | } 13 | 14 | @Override 15 | protected void config() { 16 | setExecutor(AppExecutors.COMPUTE); 17 | } 18 | 19 | @Override 20 | public void onRun(Context context) { 21 | try { 22 | Thread.sleep(1000); 23 | } catch (InterruptedException ignore) { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/chrnie/initializer/sample/task/CTask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.sample.task; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.basic.BaseTask; 5 | import com.chrnie.initializer.basic.Tasks; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class CTask extends BaseTask { 9 | 10 | public CTask() { 11 | super(Tasks.App.C); 12 | } 13 | 14 | @Override 15 | protected void config() { 16 | setDelay(5, TimeUnit.SECONDS); 17 | dependOn(Tasks.App.B); 18 | } 19 | 20 | @Override 21 | protected void onRun(Context context) { 22 | try { 23 | Thread.sleep(1000); 24 | } catch (InterruptedException e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/basic/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/basic/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.chrnie.initializer' 3 | 4 | android { 5 | compileSdkVersion rootProject.compileSdkVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.minSdkVersion 9 | targetSdkVersion rootProject.targetSdkVersion 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/basic/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/basic/src/main/java/com/chrnie/initializer/basic/App.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.basic; 2 | 3 | import android.app.Application; 4 | import com.chrnie.initializer.Initializer; 5 | 6 | public class App extends Application { 7 | 8 | @Override 9 | public void onCreate() { 10 | super.onCreate(); 11 | Initializer.init(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/basic/src/main/java/com/chrnie/initializer/basic/AppExecutors.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.basic; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.Executors; 5 | 6 | public class AppExecutors { 7 | 8 | public static final Executor COMPUTE = Executors.newFixedThreadPool(4); 9 | 10 | private AppExecutors() { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/basic/src/main/java/com/chrnie/initializer/basic/BaseTask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.basic; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import com.chrnie.initializer.Task; 6 | import java.util.Locale; 7 | 8 | public abstract class BaseTask extends Task { 9 | 10 | private static final String TAG = "Task"; 11 | 12 | public BaseTask(String name) { 13 | super(name); 14 | } 15 | 16 | @Override 17 | public final void run(Context context) { 18 | Log.i(TAG, String.format( 19 | Locale.ENGLISH, "start %s task in %s thread", getName(), Thread.currentThread().getName()) 20 | ); 21 | onRun(context); 22 | Log.i(TAG, String.format(Locale.ENGLISH, "end %s task", getName())); 23 | } 24 | 25 | protected abstract void onRun(Context context); 26 | } 27 | -------------------------------------------------------------------------------- /sample/basic/src/main/java/com/chrnie/initializer/basic/Tasks.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.basic; 2 | 3 | public interface Tasks { 4 | 5 | String UNIMPORTANT = "unimportant"; 6 | 7 | interface App { 8 | 9 | String A = "app-a"; 10 | String B = "app-b"; 11 | String C = "app-c"; 12 | 13 | } 14 | 15 | 16 | interface ModuleA { 17 | 18 | String A = "module-a-a"; 19 | String B = "module-a-b"; 20 | String C = "module-a-c"; 21 | 22 | } 23 | 24 | interface ModuleB { 25 | 26 | String A = "module-b-a"; 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /sample/basic/src/main/java/com/chrnie/initializer/basic/task/UnimportantTask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.basic.task; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.basic.BaseTask; 5 | import com.chrnie.initializer.basic.Tasks; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class UnimportantTask extends BaseTask { 9 | 10 | public UnimportantTask() { 11 | super(Tasks.UNIMPORTANT); 12 | } 13 | 14 | @Override 15 | protected void config() { 16 | setDelay(5, TimeUnit.SECONDS); 17 | } 18 | 19 | @Override 20 | protected void onRun(Context context) { 21 | // just trigger unimportant task 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/basic/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /sample/basic/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/basic/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/basic/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | InitializerSample 3 | 4 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: './config.gradle' 3 | 4 | repositories { 5 | mavenLocal() 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath deps['android'] 11 | classpath deps['initializer-plugin'] 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | google() 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /sample/component.gradle: -------------------------------------------------------------------------------- 1 | def run_module = 'app' 2 | 3 | if (project.name == run_module) { 4 | apply plugin: 'com.android.application' 5 | 6 | android { 7 | defaultConfig { 8 | applicationId "com.chrnie.initializer.sample" 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | 13 | signingConfigs { 14 | config { 15 | keyAlias 'sample' 16 | keyPassword 'sample' 17 | storeFile file("$rootDir/release.key") 18 | storePassword 'sample' 19 | } 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled true 25 | proguardFiles getDefaultProguardFile('proguard-android.txt') 26 | signingConfig signingConfigs.config 27 | } 28 | } 29 | } 30 | } else { 31 | apply plugin: 'com.android.library' 32 | } 33 | apply plugin: 'com.chrnie.initializer' 34 | 35 | android { 36 | compileSdkVersion rootProject.compileSdkVersion 37 | 38 | defaultConfig { 39 | minSdkVersion rootProject.minSdkVersion 40 | targetSdkVersion rootProject.targetSdkVersion 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sample/config.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | compileSdkVersion = 28 3 | minSdkVersion = 16 4 | targetSdkVersion = 28 5 | } 6 | 7 | def initializer_version = '1.3.1' 8 | 9 | ext.deps = [ 10 | "android" : "com.android.tools.build:gradle:3.4.0", 11 | "initializer-plugin": "com.chrnie:initializer-gradle-plugin:$initializer_version", 12 | ] -------------------------------------------------------------------------------- /sample/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 | 21 | -------------------------------------------------------------------------------- /sample/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 03 01:07:00 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip 7 | -------------------------------------------------------------------------------- /sample/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 | -------------------------------------------------------------------------------- /sample/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 | -------------------------------------------------------------------------------- /sample/module_a/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/module_a/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/component.gradle" 2 | 3 | dependencies { 4 | implementation project(':basic') 5 | implementation project(':module_b') 6 | } 7 | -------------------------------------------------------------------------------- /sample/module_a/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/module_a/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/module_a/src/main/java/com/chrnie/initializer/module/a/AModuleActivity.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.module.a; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.widget.TextView; 6 | 7 | public class AModuleActivity extends Activity { 8 | 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | TextView tv = new TextView(this); 13 | tv.setText("A Module Activity"); 14 | setContentView(tv); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/module_a/src/main/java/com/chrnie/initializer/module/a/task/ATask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.module.a.task; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.basic.BaseTask; 5 | import com.chrnie.initializer.basic.Tasks; 6 | 7 | public class ATask extends BaseTask { 8 | 9 | public ATask() { 10 | super(Tasks.ModuleA.A); 11 | } 12 | 13 | @Override 14 | protected void onRun(Context context) { 15 | try { 16 | Thread.sleep(100); 17 | } catch (InterruptedException ignore) { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/module_a/src/main/java/com/chrnie/initializer/module/a/task/BTask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.module.a.task; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.basic.AppExecutors; 5 | import com.chrnie.initializer.basic.BaseTask; 6 | import com.chrnie.initializer.basic.Tasks; 7 | 8 | public class BTask extends BaseTask { 9 | 10 | public BTask() { 11 | super(Tasks.ModuleA.B); 12 | } 13 | 14 | @Override 15 | protected void config() { 16 | setExecutor(AppExecutors.COMPUTE); 17 | } 18 | 19 | @Override 20 | public void onRun(Context context) { 21 | try { 22 | Thread.sleep(500); 23 | } catch (InterruptedException ignore) { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/module_a/src/main/java/com/chrnie/initializer/module/a/task/CTask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.module.a.task; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.basic.AppExecutors; 5 | import com.chrnie.initializer.basic.BaseTask; 6 | import com.chrnie.initializer.basic.Tasks; 7 | 8 | public class CTask extends BaseTask { 9 | 10 | public CTask() { 11 | super(Tasks.ModuleA.C); 12 | } 13 | 14 | @Override 15 | protected void config() { 16 | setExecutor(AppExecutors.COMPUTE); 17 | dependOn(Tasks.UNIMPORTANT); 18 | dependOn(Tasks.ModuleA.A); 19 | dependOn(Tasks.ModuleB.A); 20 | } 21 | 22 | @Override 23 | public void onRun(Context context) { 24 | try { 25 | Thread.sleep(1000); 26 | } catch (InterruptedException ignore) { 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sample/module_b/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/module_b/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/component.gradle" 2 | 3 | dependencies { 4 | implementation project(":basic") 5 | } -------------------------------------------------------------------------------- /sample/module_b/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/module_b/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/module_b/src/main/java/com/chrnie/initializer/module/b/BModuleActivity.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.module.b; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.widget.TextView; 6 | 7 | public class BModuleActivity extends Activity { 8 | 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | TextView tv = new TextView(this); 13 | tv.setText("B Module Activity"); 14 | setContentView(tv); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /sample/module_b/src/main/java/com/chrnie/initializer/module/b/task/ATask.java: -------------------------------------------------------------------------------- 1 | package com.chrnie.initializer.module.b.task; 2 | 3 | import android.content.Context; 4 | import com.chrnie.initializer.basic.BaseTask; 5 | import com.chrnie.initializer.basic.Tasks; 6 | 7 | public class ATask extends BaseTask { 8 | 9 | public ATask() { 10 | super(Tasks.ModuleB.A); 11 | } 12 | 13 | @Override 14 | protected void onRun(Context context) { 15 | try { 16 | Thread.sleep(100); 17 | } catch (InterruptedException e) { 18 | e.printStackTrace(); 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sample/release.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b7woreo/initializer/6eff864bef1a228205423bc428e023d43a28b16f/sample/release.key -------------------------------------------------------------------------------- /sample/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':basic', ':app', ':module_a', ':module_b' 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':initializer', ':initializer-gradle-plugin' 2 | --------------------------------------------------------------------------------