├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── ktlint.xml
├── misc.xml
└── sonarlint
│ └── issuestore
│ └── index.pb
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── me
│ │ └── hacket
│ │ └── appinit
│ │ ├── App.kt
│ │ ├── MainActivity.kt
│ │ ├── MainAppInit.kt
│ │ └── MainAppInit2.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── values-night
│ └── themes.xml
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── appinit-annotation
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── hacket
│ └── appinit
│ └── annotation
│ ├── AppInitTask.kt
│ ├── AppInitTaskInfo.kt
│ ├── AppInitTaskRegister.kt
│ └── ITask.kt
├── appinit-api
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── hacket
│ └── appinit
│ └── api
│ ├── AppInit.kt
│ ├── AppInitAutoRegister.kt
│ ├── AppInitTaskManager.kt
│ ├── IAppInitTask.kt
│ ├── Warehouse.kt
│ ├── facade
│ └── ILogger.kt
│ ├── thread
│ ├── CancelableCountDownLatch.java
│ ├── DefaultPoolExecutor.java
│ └── DefaultThreadFactory.java
│ └── utils
│ ├── ClassUtils.java
│ ├── Consts.kt
│ ├── Logger.kt
│ ├── PackageUtils.kt
│ ├── ProcessUtils.kt
│ └── TextUtils.java
├── appinit-auto-register
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── groovy
│ └── me
│ │ └── hacket
│ │ └── appinit
│ │ └── autoregister
│ │ ├── AppInitAutoRegisterPlugin.groovy
│ │ ├── AppInitAutoRegisterTransform.groovy
│ │ ├── RegisterCodeGenerator.groovy
│ │ └── ScanUtil.groovy
│ └── resources
│ └── META-INF
│ └── gradle-plugins
│ └── appinit-auto-register.properties
├── appinit-compiler-ksp
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── me
│ │ └── hacket
│ │ └── appinit
│ │ └── compiler
│ │ ├── AppInitSymbolProcessor.kt
│ │ ├── AppInitSymbolProcessorProvider.kt
│ │ ├── L.kt
│ │ └── Utils.kt
│ └── resources
│ └── META-INF
│ └── services
│ └── com.google.devtools.ksp.processing.SymbolProcessorProvider
├── appinit-compiler
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── me
│ │ └── hacket
│ │ └── appinit
│ │ └── compiler
│ │ ├── AppInitTaskProcessor.kt
│ │ ├── L.kt
│ │ └── Utils.kt
│ └── resources
│ └── META-INF
│ ├── gradle
│ └── incremental.annotation.processors
│ └── services
│ └── javax.annotation.processing.Processor
├── appinit-startup
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── hacket
│ └── appinit
│ └── startup
│ └── AppInitInitializer.kt
├── archives_publish.gradle
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── mylibrary1
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── hacket
│ └── mylibrary1
│ └── MyLib1InitTask.kt
├── mylibrary2
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── hacket
│ └── mylibrary2
│ └── MyLib2InitTask.kt
├── secret.gpg
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/gradle.xml
44 | .idea/assetWizardSettings.xml
45 | .idea/dictionaries
46 | .idea/libraries
47 | # Android Studio 3 in .gitignore file.
48 | .idea/caches
49 | .idea/modules.xml
50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
51 | .idea/navEditor.xml
52 |
53 | # Keystore files
54 | # Uncomment the following lines if you do not want to check your keystore files in.
55 | #*.jks
56 | #*.keystore
57 |
58 | # External native build folder generated in Android Studio 2.2 and later
59 | .externalNativeBuild
60 | .cxx/
61 |
62 | # Google Services (e.g. APIs or Firebase)
63 | # google-services.json
64 |
65 | # Freeline
66 | freeline.py
67 | freeline/
68 | freeline_project_description.json
69 |
70 | # fastlane
71 | fastlane/report.xml
72 | fastlane/Preview.html
73 | fastlane/screenshots
74 | fastlane/test_output
75 | fastlane/readme.md
76 |
77 | # Version control
78 | vcs.xml
79 |
80 | # lint
81 | lint/intermediates/
82 | lint/generated/
83 | lint/outputs/
84 | lint/tmp/
85 | # lint/reports/
86 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/.idea/ktlint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/index.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/.idea/sonarlint/issuestore/index.pb
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AppInit
2 | Android初始化框架,支持组件化
3 |
4 | ## 特性
5 | 1. 支持Application生命周期分发
6 | 2. 支持异步
7 | 3. 支持组件化
8 | 4. 支持任务依赖
9 | 5. 支持任务的优先级
10 | 6. 支持指定任意进程初始化
11 | 7. 支持[App StartUp](https://developer.android.com/topic/libraries/app-startup)
12 | 8. 支持KAPT/KSP
13 |
14 | ## 引入
15 | 在需要初始化的组件module的build.gradle中引入
16 | ```gradle
17 | plugins {
18 | // ...
19 | id 'kotlin-kapt'
20 | }
21 | kapt {
22 | arguments {
23 | arg("APPINIT_MODULE_NAME", project.name)
24 | }
25 | }
26 | dependencies {
27 | implementation 'io.github.hacket:appinit-api:1.0.1'
28 | kapt 'io.github.hacket:appinit-compiler:1.0.1'
29 | }
30 | ```
31 | 也可以用KSP,速度比KAPT要快不少
32 | ```
33 | // root build.gradle
34 | plugins {
35 | id 'com.google.devtools.ksp' version '1.7.20-1.0.7' apply(false)
36 | }
37 |
38 | // app build.gradle
39 | plugins {
40 | // ...
41 | id 'com.google.devtools.ksp'
42 | }
43 | ksp {
44 | arg("APPINIT_MODULE_NAME", project.name)
45 | }
46 | dependencies {
47 | implementation 'io.github.hacket:appinit-api:1.0.1'
48 | kapt 'io.github.hacket:appinit-compiler-ksp:1.0.1'
49 | }
50 | ```
51 |
52 | ## 基本使用
53 | **定义任务**:
54 | 实现`IAppInitTask`接口,用注解`@AppInitTask`标记初始化任务的属性,如后台线程、进程、优先级和依赖等
55 | ```kotlin
56 | @AppInitTask(
57 | id = "main2",
58 | background = false,
59 | process = [AppInitTask.PROCESS_MAIN],
60 | priority = AppInitTask.PRIORITY_NORMAL,
61 | dependencies = ["main1"]
62 | )
63 | class MainAppInit : IAppInitTask {
64 | override fun execute(application: Application) {
65 | AppInit.logger().info(Consts.TAG, "main2 execute start, sleep 2000L.")
66 | SystemClock.sleep(2000L)
67 | AppInit.logger().info(Consts.TAG, "main2 execute end.")
68 | }
69 | }
70 | ```
71 | - id 任务的id
72 | - background 任务是否运行在后台线程;默认为false
73 | - process 任务运行在哪个进程;默认可在所有进程任务
74 | - priority 任务优先级,值越小优先级越高;默认AppInitTask.PRIORITY_NORMAL
75 | - dependencies 当前任务依赖的任务,需等待依赖的任务初始化后再初始化该任务
76 |
77 | **Gradle配置**
78 | ```gradle
79 | kapt {
80 | arguments {
81 | arg("APPINIT_MODULE_NAME", project.name)
82 | }
83 | }
84 | ```
85 |
86 | **Application初始化**
87 | ```kotlin
88 | class App : Application() {
89 | override fun onCreate() {
90 | super.onCreate()
91 | AppInit.openDebug()
92 | AppInit.init(this, onTaskComplete = {
93 | Log.i(Consts.TAG, "task complete: $it")
94 | }) {
95 | Log.i(Consts.TAG, "all task complete")
96 | }
97 | }
98 | }
99 | ```
100 |
101 | **支持在各个组件中响应Application的生命周期回调**
102 | ```kotlin
103 | @AppInitTask(
104 | id = "main2",
105 | background = false,
106 | process = [AppInitTask.PROCESS_MAIN],
107 | priority = AppInitTask.PRIORITY_MAX,
108 | dependencies = ["main1"]
109 | )
110 | class MainAppInit : IAppInitTask {
111 | override fun onCreate(application: Application) {
112 | AppInit.logger().info(Consts.TAG, "main2 execute start, sleep 2000L.")
113 | SystemClock.sleep(2000L)
114 | AppInit.logger().info(Consts.TAG, "main2 execute end.")
115 | }
116 |
117 | override fun onConfigurationChanged(application: Application, newConfig: Configuration) {
118 | super.onConfigurationChanged(application, newConfig)
119 | AppInit.logger().debug(Consts.TAG, "main2 onConfigurationChanged:$newConfig.")
120 | }
121 |
122 | override fun onLowMemory(application: Application) {
123 | super.onLowMemory(application)
124 | AppInit.logger().warning(Consts.TAG, "main2 onLowMemory.")
125 | }
126 |
127 | override fun onTrimMemory(application: Application, level: Int) {
128 | super.onTrimMemory(application, level)
129 | AppInit.logger().warning(Consts.TAG, "main2 onTrimMemory, level=$level")
130 | }
131 | }
132 | ```
133 |
134 | **其他的配置**
135 | 1. 开启debug模式
136 | ```kotlin
137 | AppInit.openDebug()
138 | ```
139 | 2. 自定义log
140 | ```kotlin
141 | AppInit.setLogger(ILogger)
142 | ```
143 |
144 |
145 | **支持App StartUp**
146 | ```gradle
147 | implementation 'io.github.hacket:appinit-startup:1.0.0'
148 | ```
149 |
150 | ## appinit-auto-register插件
151 |
152 | 利用`appinit-auto-register`插件在编译时完成注入,避免运行时扫描dex影响性能。
153 |
154 | 在root build.gradle:
155 | ```gradle
156 | buildscript {
157 | repositories {
158 | mavenCentral()
159 | }
160 | dependencies {
161 | // ...
162 | classpath "io.github.hacket:appinit-auto-register:1.0.0"
163 | }
164 | }
165 | ```
166 | 在app build.gradle:
167 | ```gradle
168 | plugins {
169 | id 'com.android.application'
170 | id 'appinit-auto-register' // 一定要有插件'com.android.application'
171 | // ...
172 | }
173 | ```
174 |
175 | ## License
176 |
177 | Copyright 2020 hacket
178 |
179 | Licensed under the Apache License, Version 2.0 (the "License");
180 | you may not use this file except in compliance with the License.
181 | You may obtain a copy of the License at
182 |
183 | http://www.apache.org/licenses/LICENSE-2.0
184 |
185 | Unless required by applicable law or agreed to in writing, software
186 | distributed under the License is distributed on an "AS IS" BASIS,
187 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
188 | See the License for the specific language governing permissions and
189 | limitations under the License.
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'appinit-auto-register'
5 | // id 'kotlin-kapt'
6 | id 'com.google.devtools.ksp'
7 | }
8 |
9 | android {
10 | namespace 'me.hacket.appinit'
11 | compileSdk 31
12 |
13 | defaultConfig {
14 | applicationId "me.hacket.appinit"
15 | minSdk 21
16 | targetSdk 31
17 | versionCode 1
18 | versionName "1.0"
19 | }
20 |
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | targetCompatibility JavaVersion.VERSION_1_8
30 | }
31 | kotlinOptions {
32 | jvmTarget = '1.8'
33 | }
34 | }
35 |
36 | //kapt {
37 | // arguments {
38 | // arg("APPINIT_MODULE_NAME", project.name)
39 | // }
40 | //}
41 | ksp {
42 | arg("APPINIT_MODULE_NAME", project.name)
43 | }
44 |
45 | dependencies {
46 |
47 | implementation project(':appinit-startup')
48 | // implementation 'io.github.hacket:appinit-startup:1.0.0'
49 | // implementation 'io.github.hacket:appinit-api:1.0.0'
50 | // kapt 'io.github.hacket:appinit-compiler:1.0.0'
51 | ksp project(':appinit-compiler-ksp')
52 |
53 | implementation project(':mylibrary1')
54 | implementation project(':mylibrary2')
55 |
56 | implementation 'androidx.core:core-ktx:1.7.0'
57 | implementation 'androidx.appcompat:appcompat:1.4.1'
58 | implementation 'com.google.android.material:material:1.5.0'
59 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
60 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/me/hacket/appinit/App.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit
2 |
3 | import android.app.Application
4 |
5 | /**
6 | *
7 | * Created by hacket at 2022/10/31
8 | */
9 | class App : Application() {
10 | override fun onCreate() {
11 | super.onCreate()
12 | // Log.d(Consts.TAG, "Application onCreate")
13 | // AppInit.openDebug()
14 | // AppInit.init(this, onTaskComplete = {
15 | // Log.i(Consts.TAG, "task complete: $it")
16 | // }) {
17 | // Log.i(Consts.TAG, "all task complete")
18 | // }
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/hacket/appinit/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 |
6 | class MainActivity : AppCompatActivity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | super.onCreate(savedInstanceState)
9 | setContentView(R.layout.activity_main)
10 | }
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/hacket/appinit/MainAppInit.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit
2 |
3 | import android.app.Application
4 | import android.content.res.Configuration
5 | import android.os.SystemClock
6 | import me.hacket.appinit.annotation.AppInitTask
7 | import me.hacket.appinit.api.AppInit
8 | import me.hacket.appinit.api.IAppInitTask
9 | import me.hacket.appinit.api.utils.Consts
10 |
11 | @AppInitTask(
12 | id = "main2",
13 | background = false,
14 | process = [AppInitTask.PROCESS_MAIN],
15 | priority = AppInitTask.PRIORITY_MAX,
16 | dependencies = ["main1"]
17 | )
18 | class MainAppInit : IAppInitTask {
19 | override fun onCreate(application: Application) {
20 | AppInit.logger().info(Consts.TAG, "main2 execute start, sleep 2000L.")
21 | SystemClock.sleep(2000L)
22 | AppInit.logger().info(Consts.TAG, "main2 execute end.")
23 | }
24 |
25 | override fun onConfigurationChanged(application: Application, newConfig: Configuration) {
26 | super.onConfigurationChanged(application, newConfig)
27 | AppInit.logger().debug(Consts.TAG, "main2 onConfigurationChanged:$newConfig.")
28 | }
29 |
30 | override fun onLowMemory(application: Application) {
31 | super.onLowMemory(application)
32 | AppInit.logger().warning(Consts.TAG, "main2 onLowMemory.")
33 | }
34 |
35 | override fun onTrimMemory(application: Application, level: Int) {
36 | super.onTrimMemory(application, level)
37 | AppInit.logger().warning(Consts.TAG, "main2 onTrimMemory, level=$level")
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/me/hacket/appinit/MainAppInit2.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit
2 |
3 | import android.app.Application
4 | import android.content.res.Configuration
5 | import android.os.SystemClock
6 | import me.hacket.appinit.annotation.AppInitTask
7 | import me.hacket.appinit.api.AppInit
8 | import me.hacket.appinit.api.IAppInitTask
9 | import me.hacket.appinit.api.utils.Consts
10 |
11 | @AppInitTask(
12 | id = "main1",
13 | background = true,
14 | process = [AppInitTask.PROCESS_MAIN],
15 | priority = AppInitTask.PRIORITY_NORMAL,
16 | dependencies = []
17 | )
18 | class MainAppInit2 : IAppInitTask {
19 | override fun onCreate(application: Application) {
20 | AppInit.logger().debug(Consts.TAG, "main1 execute start, sleep 5000L.")
21 | SystemClock.sleep(5000L)
22 | AppInit.logger().debug(Consts.TAG, "main1 execute end.")
23 | }
24 |
25 | override fun onConfigurationChanged(application: Application, newConfig: Configuration) {
26 | super.onConfigurationChanged(application, newConfig)
27 | AppInit.logger().debug(Consts.TAG, "main1 onConfigurationChanged:$newConfig")
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AppInit
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/appinit-annotation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/appinit-annotation/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'kotlin'
3 | }
4 |
5 | dependencies {
6 |
7 | }
--------------------------------------------------------------------------------
/appinit-annotation/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/appinit-annotation/consumer-rules.pro
--------------------------------------------------------------------------------
/appinit-annotation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/appinit-annotation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/appinit-annotation/src/main/java/me/hacket/appinit/annotation/AppInitTask.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.annotation
2 |
3 | @Target(AnnotationTarget.CLASS)
4 | @Retention(AnnotationRetention.RUNTIME)
5 | annotation class AppInitTask(
6 | /**
7 | * task id
8 | */
9 | val id: String = "",
10 | /**
11 | * task run on background thread?
12 | */
13 | val background: Boolean = false,
14 | /**
15 | * task priority; The smaller the priority, the higher the priority
16 | *
17 | * @see AppInitTask.PRIORITY_MAX
18 | * @see AppInitTask.PRIORITY_HIGH
19 | * @see AppInitTask.PRIORITY_NORMAL
20 | * @see AppInitTask.PRIORITY_LOW
21 | * @see AppInitTask.PRIORITY_MIN
22 | */
23 | val priority: Int = PRIORITY_NORMAL,
24 | /**
25 | * task run on process
26 | *
27 | * @see AppInitTask.PROCESS_MAIN
28 | * @see AppInitTask.PROCESS_NOT_MAIN
29 | * @see AppInitTask.PROCESS_ALL
30 | */
31 | val process: Array = [PROCESS_ALL],
32 | /**
33 | * task dependencies
34 | */
35 | val dependencies: Array = []
36 | ) {
37 | companion object {
38 | const val PRIORITY_MAX = Int.MIN_VALUE
39 | const val PRIORITY_HIGH = -1000
40 | const val PRIORITY_NORMAL = 0
41 | const val PRIORITY_LOW = 1000
42 | const val PRIORITY_MIN = Int.MAX_VALUE
43 |
44 | const val PROCESS_MAIN = "MAIN"
45 | const val PROCESS_NOT_MAIN = "NOT_MAIN"
46 | const val PROCESS_ALL = "ALL"
47 | }
48 | }
--------------------------------------------------------------------------------
/appinit-annotation/src/main/java/me/hacket/appinit/annotation/AppInitTaskInfo.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.annotation
2 |
3 | class AppInitTaskInfo(
4 | val id: String,
5 | val background: Boolean,
6 | private val priority: Int,
7 | process: Array,
8 | dependencies: Array,
9 | val task: ITask
10 | ) : Comparable {
11 | val depends: Set
12 | val process: Set
13 | val children: MutableSet = mutableSetOf()
14 |
15 | init {
16 | this.depends = HashSet(listOf(*dependencies))
17 | this.process = HashSet(listOf(*process))
18 | }
19 |
20 | override fun hashCode(): Int {
21 | return id.hashCode()
22 | }
23 |
24 | override fun equals(other: Any?): Boolean {
25 | return other is AppInitTaskInfo && other.id == id
26 | }
27 |
28 | override fun compareTo(other: AppInitTaskInfo): Int {
29 | return priority.compareTo(other.priority)
30 | }
31 |
32 | override fun toString(): String {
33 | return "AppInitTaskInfo(id='$id', background=$background, priority=$priority, task=$task, depends=$depends, process=$process, children=$children)"
34 | }
35 | }
--------------------------------------------------------------------------------
/appinit-annotation/src/main/java/me/hacket/appinit/annotation/AppInitTaskRegister.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.annotation
2 |
3 | interface AppInitTaskRegister {
4 | fun register(taskInfoList: MutableSet)
5 | }
6 |
--------------------------------------------------------------------------------
/appinit-annotation/src/main/java/me/hacket/appinit/annotation/ITask.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.annotation
2 |
3 | interface ITask
--------------------------------------------------------------------------------
/appinit-api/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/appinit-api/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'me.hacket.appinit.api'
8 | compileSdk 31
9 |
10 | defaultConfig {
11 | minSdk 21
12 | targetSdk 31
13 |
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | kotlinOptions {
28 | jvmTarget = '1.8'
29 | }
30 | }
31 |
32 | dependencies {
33 | api project(':appinit-annotation')
34 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
35 | }
--------------------------------------------------------------------------------
/appinit-api/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/appinit-api/consumer-rules.pro
--------------------------------------------------------------------------------
/appinit-api/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/appinit-api/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/AppInit.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api
2 |
3 | import android.app.Application
4 | import me.hacket.appinit.api.facade.ILogger
5 | import me.hacket.appinit.api.utils.Consts
6 | import me.hacket.appinit.api.utils.Logger
7 | import me.hacket.appinit.api.utils.ProcessUtils
8 |
9 | object AppInit {
10 |
11 | private const val TAG = Consts.TAG
12 |
13 | @Volatile
14 | var debuggable = false
15 | private var _logger: ILogger = Logger(Consts.TAG)
16 |
17 | @Synchronized
18 | fun openDebug() {
19 | debuggable = true
20 | }
21 |
22 | @JvmStatic
23 | fun debuggable(): Boolean {
24 | return debuggable
25 | }
26 |
27 | @JvmStatic
28 | fun setLogger(userLogger: ILogger?) {
29 | if (null != userLogger) {
30 | _logger = userLogger
31 | }
32 | }
33 |
34 | @JvmStatic
35 | fun logger(): ILogger {
36 | return _logger
37 | }
38 |
39 | /**
40 | * 启动任务
41 | *
42 | * @param processName 当前进程名
43 | * @param onTaskComplete 单个任务执行完成,在任务所在线程回调
44 | * @param onAllTaskComplete 所有任务执行完成,在最后一个任务所在线程回调
45 | */
46 | @JvmStatic
47 | @JvmOverloads
48 | fun init(
49 | app: Application,
50 | processName: String = ProcessUtils.getProcessName(app),
51 | onTaskComplete: ((String) -> Unit)? = null,
52 | onAllTaskComplete: (() -> Unit)? = null
53 | ) {
54 | AppInitTaskManager.init(app, processName, onTaskComplete, onAllTaskComplete)
55 | }
56 |
57 | @JvmStatic
58 | @Synchronized
59 | fun destroy() {
60 | if (debuggable()) {
61 | AppInit.logger().info(TAG, "AppInit destroy success!")
62 | Warehouse.clear()
63 | } else {
64 | AppInit.logger().error(TAG, "Destroy can be used in debug mode only!")
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/AppInitAutoRegister.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api
2 |
3 | import android.text.TextUtils
4 | import me.hacket.appinit.annotation.AppInitTaskRegister
5 | import me.hacket.appinit.api.utils.Consts
6 |
7 | object AppInitAutoRegister {
8 |
9 | var registerByPlugin = false
10 |
11 | init {
12 | load()
13 | }
14 |
15 | /**
16 | * AutoRegister plugin will generate code inside this method ,
17 | * call this method to register all taskInfo.
18 | */
19 | @JvmStatic
20 | fun load() {
21 | registerByPlugin = false
22 | // auto generate register code by gradle plugin: AutoRegister
23 | // looks like below:
24 | // register(AppInitTaskRegister$lib1());
25 | // AppInit.logger().info(Consts.TAG, "load(), registerByPlugin=$registerByPlugin")
26 | }
27 |
28 | // // called by ASM
29 | // @JvmStatic
30 | // fun register(register: AppInitTaskRegister) {
31 | // AppInit.logger().info(
32 | // Consts.TAG,
33 | // "register, taskInfoSet(${Warehouse.taskInfoSet.size})=${Warehouse.taskInfoSet}."
34 | // )
35 | // register.register(Warehouse.taskInfoSet)
36 | // registerByPlugin = true
37 | // }
38 |
39 | /**
40 | * [called by ASM] register by class name
41 | * Sacrificing a bit of efficiency to solve
42 | * the problem that the main dex file size is too large
43 | * @author hacket [Contact me.](mailto:shengfanzeng@gmail.com)
44 | * @param className class name
45 | */
46 | @JvmStatic
47 | fun register(className: String) {
48 | if (!TextUtils.isEmpty(className)) {
49 | try {
50 | val clazz = Class.forName(className)
51 | val obj = clazz.getConstructor().newInstance()
52 | if (obj is AppInitTaskRegister) {
53 | obj.register(Warehouse.taskInfoSet)
54 | markRegisteredByPlugin()
55 | } else {
56 | AppInit.logger().error(
57 | Consts.TAG,
58 | "register failed, class name: " + className +
59 | " should implements one of ${AppInitTaskRegister::class.java.canonicalName}."
60 | )
61 | }
62 | } catch (e: Exception) {
63 | AppInit.logger().error(Consts.TAG, "register class error:$className")
64 | }
65 | }
66 | }
67 |
68 | /**
69 | * mark already registered by appinit-auto-register plugin
70 | * @author hacket [Contact me.](mailto:shengfanzeng@gmail.com)
71 | */
72 | private fun markRegisteredByPlugin() {
73 | if (!registerByPlugin) {
74 | registerByPlugin = true
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/AppInitTaskManager.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api
2 |
3 | import android.app.Application
4 | import android.content.ComponentCallbacks2
5 | import android.content.Context
6 | import android.content.res.Configuration
7 | import kotlinx.coroutines.*
8 | import me.hacket.appinit.annotation.AppInitTask
9 | import me.hacket.appinit.annotation.AppInitTaskInfo
10 | import me.hacket.appinit.annotation.AppInitTaskRegister
11 | import me.hacket.appinit.api.utils.ClassUtils
12 | import me.hacket.appinit.api.utils.Consts
13 | import me.hacket.appinit.api.utils.PackageUtils
14 | import me.hacket.appinit.api.utils.ProcessUtils
15 | import kotlin.system.measureTimeMillis
16 |
17 | internal class AppInitTaskManager(
18 | private val app: Application,
19 | private val processName: String,
20 | private val taskList: MutableSet,
21 | private val onTaskComplete: ((String) -> Unit)?,
22 | private val onAllTaskComplete: (() -> Unit)?
23 | ) {
24 |
25 | private val completedTasks: MutableSet = mutableSetOf()
26 |
27 | @OptIn(DelicateCoroutinesApi::class)
28 | private val scope = GlobalScope
29 |
30 | companion object {
31 |
32 | @JvmStatic
33 | @JvmOverloads
34 | fun init(
35 | app: Application,
36 | processName: String = ProcessUtils.getProcessName(app),
37 | onTaskComplete: ((String) -> Unit)? = null,
38 | onAllTaskComplete: (() -> Unit)? = null
39 | ) {
40 | if (!AppInitAutoRegister.registerByPlugin) {
41 | AppInit.logger().debug(Consts.TAG, "init scan dex.")
42 | var startInit = System.currentTimeMillis()
43 | var appInitTaskSet: Set = mutableSetOf()
44 |
45 | // It will rebuild router map every times when debuggable.
46 | if (AppInit.debuggable() || PackageUtils.isNewVersion(app)) {
47 | AppInit.logger().info(
48 | Consts.TAG,
49 | "Run with debug mode or new install, rebuild appinit task meta."
50 | )
51 | // These class was generated by appinit-compiler.
52 | appInitTaskSet =
53 | ClassUtils.getFileNameByPackageName(app, Consts.APP_INIT_SCAN_ROOT_PACKAGE)
54 | if (appInitTaskSet.isNotEmpty()) {
55 | app.getSharedPreferences(
56 | Consts.APP_INIT_SP_FILE_NAME,
57 | Context.MODE_PRIVATE
58 | )
59 | .edit().putStringSet(Consts.APP_INIT_SP_KEY_MAP, appInitTaskSet).apply()
60 | }
61 | PackageUtils.updateVersion(app) // Save new version name when appinit task meta update finishes.
62 | } else {
63 | AppInit.logger().info(Consts.TAG, "Load appinit task meta from cache.")
64 | val set = app.getSharedPreferences(
65 | Consts.APP_INIT_SP_FILE_NAME,
66 | Context.MODE_PRIVATE
67 | ).getStringSet(Consts.APP_INIT_SP_KEY_MAP, HashSet())
68 | set?.let {
69 | appInitTaskSet = HashSet(it)
70 | }
71 | }
72 | AppInit.logger().info(
73 | Consts.TAG,
74 | "Find appinit task meta finished, task size = ${appInitTaskSet.size}, cost ${(System.currentTimeMillis() - startInit)} ms, appInitTaskSet(${Warehouse.taskInfoSet.size})=$appInitTaskSet"
75 | )
76 | startInit = System.currentTimeMillis()
77 |
78 | appInitTaskSet.forEach {
79 | // me.hacket.appinit.apt.taskregister.AppInitTaskRegister$app
80 | val clazz = Class.forName(it)
81 | val taskRegister = clazz.newInstance() as? AppInitTaskRegister
82 | taskRegister?.register(Warehouse.taskInfoSet)
83 | }
84 | AppInit.logger().info(
85 | Consts.TAG,
86 | "Load appinit task meta finished, cost " + (System.currentTimeMillis() - startInit) + " ms, taskInfoSet(${Warehouse.taskInfoSet.size})=${Warehouse.taskInfoSet}."
87 | )
88 | } else {
89 | AppInit.logger().info(
90 | Consts.TAG,
91 | "init by auto-register plugin. taskInfoSet(${Warehouse.taskInfoSet.size})=${Warehouse.taskInfoSet}."
92 | )
93 | }
94 | AppInitTaskManager(
95 | app,
96 | processName,
97 | Warehouse.taskInfoSet,
98 | onTaskComplete,
99 | onAllTaskComplete
100 | ).start()
101 | }
102 | }
103 |
104 | private fun isMatchedTheProgress(task: AppInitTaskInfo): Boolean {
105 | val mainProgressName = app.applicationInfo.processName
106 | task.process.forEach {
107 | if (it == AppInitTask.PROCESS_ALL) {
108 | return true
109 | } else if (it == AppInitTask.PROCESS_MAIN) {
110 | if (processName == mainProgressName) {
111 | return true
112 | }
113 | } else if (it == AppInitTask.PROCESS_NOT_MAIN) {
114 | if (processName != mainProgressName) {
115 | return true
116 | }
117 | } else if (it.startsWith(":")) {
118 | if (processName == mainProgressName + it) {
119 | return true
120 | }
121 | } else {
122 | if (it == processName) {
123 | return true
124 | }
125 | }
126 | }
127 | return false
128 | }
129 |
130 | private fun checkTaskDependenciesCircular(
131 | chain: List,
132 | depends: Set,
133 | taskMap: Map
134 | ) {
135 | depends.forEach { depend ->
136 | check(chain.contains(depend).not()) {
137 | "Found circular dependency chain: $chain -> $depend"
138 | }
139 | taskMap[depend]?.let { task ->
140 | checkTaskDependenciesCircular(chain + depend, task.depends, taskMap)
141 | }
142 | }
143 | }
144 |
145 | @OptIn(DelicateCoroutinesApi::class)
146 | private fun start() {
147 | val sortedList = taskList.sorted()
148 | val taskMap = sortedList.associateBy { it.id }
149 | val singleSyncTasks: MutableSet = mutableSetOf()
150 | val singleAsyncTasks: MutableSet = mutableSetOf()
151 | sortedList.forEach { task ->
152 | when {
153 | task.depends.isNotEmpty() -> {
154 | checkTaskDependenciesCircular(listOf(task.id), task.depends, taskMap)
155 | task.depends.forEach {
156 | val depend = taskMap[it]
157 | checkNotNull(depend) {
158 | "Can not find task [$it] which depend by task [${task.id}]"
159 | }
160 | depend.children.add(task)
161 | }
162 | }
163 | task.background -> {
164 | singleAsyncTasks.add(task)
165 | }
166 | else -> {
167 | singleSyncTasks.add(task)
168 | }
169 | }
170 | }
171 | registerComponentCallbacks(sortedList)
172 |
173 | // 无依赖的异步任务
174 | singleAsyncTasks.forEach { task ->
175 | scope.launch(Dispatchers.Default) { execute(task) }
176 | }
177 |
178 | // 无依赖的同步任务
179 | scope.launch(Dispatchers.Main.immediate) {
180 | singleSyncTasks.forEach { execute(it) }
181 | }
182 | }
183 |
184 | private fun registerComponentCallbacks(sortedTaskList: List) {
185 | app.registerComponentCallbacks(object : ComponentCallbacks2 {
186 | override fun onConfigurationChanged(configuration: Configuration) {
187 | for (item in sortedTaskList) {
188 | (item.task as IAppInitTask).onConfigurationChanged(app, configuration)
189 | }
190 | }
191 |
192 | override fun onLowMemory() {
193 | for (item in sortedTaskList) {
194 | (item.task as IAppInitTask).onLowMemory(app)
195 | }
196 | }
197 |
198 | override fun onTrimMemory(level: Int) {
199 | for (item in sortedTaskList) {
200 | (item.task as IAppInitTask).onTrimMemory(app, level)
201 | }
202 | }
203 | })
204 | }
205 |
206 | private fun execute(task: AppInitTaskInfo) {
207 | if (isMatchedTheProgress(task)) {
208 | val cost = measureTimeMillis {
209 | kotlin.runCatching {
210 | (task.task as IAppInitTask).onCreate(app)
211 | }.onFailure {
212 | it.printStackTrace()
213 | AppInit.logger().error(
214 | Consts.TAG,
215 | "executing task [${task.id}] error. ${it.message}"
216 | )
217 | }
218 | onTaskComplete?.invoke(task.id)
219 | }
220 | AppInit.logger().debug(
221 | Consts.TAG,
222 | "Execute task [${task.id}] complete in process [$processName] " +
223 | "thread [${Thread.currentThread().name}], cost: ${cost}ms"
224 | )
225 | } else {
226 | AppInit.logger().warning(
227 | Consts.TAG,
228 | "Skip task [${task.id}] cause the process [$processName] not match"
229 | )
230 | }
231 | afterExecute(task.id, task.children)
232 | }
233 |
234 | @OptIn(DelicateCoroutinesApi::class)
235 | private fun afterExecute(name: String, children: Set) {
236 | val allowTasks = synchronized(completedTasks) {
237 | completedTasks.add(name)
238 | children.filter { completedTasks.containsAll(it.depends) }
239 | }.sorted()
240 | allowTasks.forEach {
241 | val dispatcher = if (it.background) Dispatchers.Default else Dispatchers.Main.immediate
242 | scope.launch(dispatcher) { execute(it) }
243 | }
244 | if (taskList.size == completedTasks.size) {
245 | onAllTaskComplete?.invoke()
246 | }
247 | }
248 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/IAppInitTask.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api
2 |
3 | import android.app.Application
4 | import android.content.res.Configuration
5 | import me.hacket.appinit.annotation.ITask
6 |
7 | interface IAppInitTask : ITask {
8 |
9 | /**
10 | * Called when the application is starting, before any activity, service,
11 | * or receiver objects (excluding content providers) have been created.
12 | */
13 | fun onCreate(application: Application)
14 |
15 | /**
16 | * This is called when the overall system is running low on memory, and
17 | * actively running processes should trim their memory usage.
18 | */
19 | fun onLowMemory(application: Application) {
20 | }
21 |
22 | /**
23 | * Called when the operating system has determined that it is a good
24 | * time for a process to trim unneeded memory from its process.
25 | *
26 | * @param application Application
27 | * @param level ComponentCallbacks2.TrimMemoryLevel
28 | */
29 | fun onTrimMemory(application: Application, level: Int) {
30 | }
31 |
32 | /**
33 | * Called by the system when the device configuration changes while your
34 | * component is running.
35 | *
36 | * @param newConfig The new device configuration.
37 | */
38 | fun onConfigurationChanged(application: Application, newConfig: Configuration) {
39 | }
40 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/Warehouse.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api
2 |
3 | import me.hacket.appinit.annotation.AppInitTaskInfo
4 |
5 | /**
6 | * Storage of app init task info
7 | */
8 | object Warehouse {
9 |
10 | val taskInfoSet: MutableSet = mutableSetOf()
11 |
12 | fun clear() {
13 | taskInfoSet.clear()
14 | }
15 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/facade/ILogger.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.facade
2 |
3 | import me.hacket.appinit.api.utils.Consts
4 |
5 | interface ILogger {
6 | fun showLog(isShowLog: Boolean)
7 | fun showStackTrace(isShowStackTrace: Boolean)
8 | fun debug(tag: String?, message: String?)
9 | fun info(tag: String?, message: String?)
10 | fun warning(tag: String?, message: String?)
11 | fun error(tag: String?, message: String?)
12 | fun error(tag: String?, message: String?, e: Throwable?)
13 | fun monitor(message: String?)
14 | val isMonitorMode: Boolean
15 | val defaultTag: String?
16 |
17 | companion object {
18 | const val isShowLog = false
19 | const val isShowStackTrace = false
20 | const val defaultTag = Consts.TAG
21 | }
22 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/thread/CancelableCountDownLatch.java:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.thread;
2 |
3 | import java.util.concurrent.CountDownLatch;
4 |
5 | /**
6 | * As its name.
7 | */
8 | public class CancelableCountDownLatch extends CountDownLatch {
9 | /**
10 | * Constructs a {@code CountDownLatch} initialized with the given count.
11 | *
12 | * @param count the number of times {@link #countDown} must be invoked
13 | * before threads can pass through {@link #await}
14 | * @throws IllegalArgumentException if {@code count} is negative
15 | */
16 | public CancelableCountDownLatch(int count) {
17 | super(count);
18 | }
19 |
20 | public void cancel() {
21 | while (getCount() > 0) {
22 | countDown();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/thread/DefaultPoolExecutor.java:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.thread;
2 |
3 | import java.util.concurrent.ArrayBlockingQueue;
4 | import java.util.concurrent.BlockingQueue;
5 | import java.util.concurrent.CancellationException;
6 | import java.util.concurrent.ExecutionException;
7 | import java.util.concurrent.Future;
8 | import java.util.concurrent.RejectedExecutionHandler;
9 | import java.util.concurrent.ThreadFactory;
10 | import java.util.concurrent.ThreadPoolExecutor;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | import me.hacket.appinit.api.AppInit;
14 | import me.hacket.appinit.api.utils.Consts;
15 | import me.hacket.appinit.api.utils.TextUtils;
16 |
17 | /**
18 | * Executors
19 | */
20 | public class DefaultPoolExecutor extends ThreadPoolExecutor {
21 | // Thread args
22 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
23 | private static final int INIT_THREAD_COUNT = CPU_COUNT + 1;
24 | private static final int MAX_THREAD_COUNT = INIT_THREAD_COUNT;
25 | private static final long SURPLUS_THREAD_LIFE = 30L;
26 |
27 | private static volatile DefaultPoolExecutor instance;
28 |
29 | public static DefaultPoolExecutor getInstance() {
30 | if (null == instance) {
31 | synchronized (DefaultPoolExecutor.class) {
32 | if (null == instance) {
33 | instance = new DefaultPoolExecutor(
34 | INIT_THREAD_COUNT,
35 | MAX_THREAD_COUNT,
36 | SURPLUS_THREAD_LIFE,
37 | TimeUnit.SECONDS,
38 | new ArrayBlockingQueue<>(64),
39 | new DefaultThreadFactory());
40 | }
41 | }
42 | }
43 | return instance;
44 | }
45 |
46 | private DefaultPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) {
47 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new RejectedExecutionHandler() {
48 | @Override
49 | public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
50 | AppInit.logger().error(Consts.TAG, "Task rejected, too many task!");
51 | }
52 | });
53 | }
54 |
55 | /*
56 | * 线程执行结束,顺便看一下有么有什么乱七八糟的异常
57 | *
58 | * @param r the runnable that has completed
59 | * @param t the exception that caused termination, or null if
60 | */
61 | @Override
62 | protected void afterExecute(Runnable r, Throwable t) {
63 | super.afterExecute(r, t);
64 | if (t == null && r instanceof Future>) {
65 | try {
66 | ((Future>) r).get();
67 | } catch (CancellationException ce) {
68 | t = ce;
69 | } catch (ExecutionException ee) {
70 | t = ee.getCause();
71 | } catch (InterruptedException ie) {
72 | Thread.currentThread().interrupt(); // ignore/reset
73 | }
74 | }
75 | if (t != null) {
76 | AppInit.logger().warning(Consts.TAG, "Running task appeared exception! Thread [" + Thread.currentThread().getName() + "], because [" + t.getMessage() + "]\n" + TextUtils.formatStackTrace(t.getStackTrace()));
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/thread/DefaultThreadFactory.java:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.thread;
2 |
3 | import java.util.concurrent.ThreadFactory;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 | import me.hacket.appinit.api.AppInit;
7 | import me.hacket.appinit.api.utils.Consts;
8 |
9 | /**
10 | * 线程池工厂类
11 | */
12 | public class DefaultThreadFactory implements ThreadFactory {
13 | private static final AtomicInteger poolNumber = new AtomicInteger(1);
14 |
15 | private final AtomicInteger threadNumber = new AtomicInteger(1);
16 | private final ThreadGroup group;
17 | private final String namePrefix;
18 |
19 | public DefaultThreadFactory() {
20 | SecurityManager s = System.getSecurityManager();
21 | group = (s != null) ? s.getThreadGroup() :
22 | Thread.currentThread().getThreadGroup();
23 | namePrefix = "AppInit task pool No." + poolNumber.getAndIncrement() + ", thread No.";
24 | }
25 |
26 | public Thread newThread(Runnable runnable) {
27 | String threadName = namePrefix + threadNumber.getAndIncrement();
28 | AppInit.logger().info(Consts.TAG, "Thread production, name is [" + threadName + "]");
29 | Thread thread = new Thread(group, runnable, threadName, 0);
30 | if (thread.isDaemon()) { //设为非后台线程
31 | thread.setDaemon(false);
32 | }
33 | if (thread.getPriority() != Thread.NORM_PRIORITY) { //优先级为normal
34 | thread.setPriority(Thread.NORM_PRIORITY);
35 | }
36 |
37 | // 捕获多线程处理中的异常
38 | thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
39 | @Override
40 | public void uncaughtException(Thread thread, Throwable ex) {
41 | AppInit.logger().info(Consts.TAG, "Running task appeared exception! Thread [" + thread.getName() + "], because [" + ex.getMessage() + "]");
42 | }
43 | });
44 | return thread;
45 | }
46 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/utils/ClassUtils.java:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.utils;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.content.pm.ApplicationInfo;
6 | import android.content.pm.PackageManager;
7 | import android.os.Build;
8 |
9 | import dalvik.system.DexFile;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.lang.reflect.Method;
14 | import java.util.ArrayList;
15 | import java.util.Arrays;
16 | import java.util.Enumeration;
17 | import java.util.HashSet;
18 | import java.util.List;
19 | import java.util.Set;
20 | import java.util.concurrent.CountDownLatch;
21 | import java.util.regex.Matcher;
22 | import java.util.regex.Pattern;
23 |
24 | import me.hacket.appinit.api.AppInit;
25 | import me.hacket.appinit.api.thread.DefaultPoolExecutor;
26 |
27 | public final class ClassUtils {
28 | private static final String EXTRACTED_NAME_EXT = ".classes";
29 | private static final String EXTRACTED_SUFFIX = ".zip";
30 |
31 | private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes";
32 |
33 | private static final String PREFS_FILE = "multidex.version";
34 | private static final String KEY_DEX_NUMBER = "dex.number";
35 |
36 | private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;
37 | private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;
38 |
39 | private static SharedPreferences getMultiDexPreferences(Context context) {
40 | return context.getSharedPreferences(PREFS_FILE, Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? Context.MODE_PRIVATE : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
41 | }
42 |
43 | /**
44 | * 通过指定包名,扫描包下面包含的所有的ClassName
45 | *
46 | * @param context U know
47 | * @param packageName 包名
48 | * @return 所有class的集合
49 | */
50 | public static Set getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
51 | final Set classNames = new HashSet<>();
52 |
53 | List paths = getSourcePaths(context);
54 | final CountDownLatch parserCtl = new CountDownLatch(paths.size());
55 |
56 | for (final String path : paths) {
57 | DefaultPoolExecutor.getInstance().execute(new Runnable() {
58 | @Override
59 | public void run() {
60 | DexFile dexfile = null;
61 |
62 | try {
63 | if (path.endsWith(EXTRACTED_SUFFIX)) {
64 | //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
65 | dexfile = DexFile.loadDex(path, path + ".tmp", 0);
66 | } else {
67 | dexfile = new DexFile(path);
68 | }
69 |
70 | Enumeration dexEntries = dexfile.entries();
71 | while (dexEntries.hasMoreElements()) {
72 | String className = dexEntries.nextElement();
73 | if (className.startsWith(packageName)) {
74 | classNames.add(className);
75 | }
76 | }
77 | } catch (Throwable ignore) {
78 | AppInit.logger().error(Consts.TAG, "Scan map file in dex files made error.", ignore);
79 | } finally {
80 | if (null != dexfile) {
81 | try {
82 | dexfile.close();
83 | } catch (Throwable ignore) {
84 | }
85 | }
86 |
87 | parserCtl.countDown();
88 | }
89 | }
90 | });
91 | }
92 |
93 | parserCtl.await();
94 |
95 | AppInit.logger().debug(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
96 | return classNames;
97 | }
98 |
99 | /**
100 | * get all the dex path
101 | *
102 | * @param context the application context
103 | * @return all the dex path
104 | * @throws PackageManager.NameNotFoundException NameNotFoundException
105 | * @throws IOException IOException
106 | */
107 | public static List getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
108 | ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
109 | File sourceApk = new File(applicationInfo.sourceDir);
110 |
111 | List sourcePaths = new ArrayList<>();
112 | sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
113 |
114 | //the prefix of extracted file, ie: test.classes
115 | String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
116 |
117 | // 如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
118 | // 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
119 | if (!isVMMultidexCapable()) {
120 | //the total dex numbers
121 | int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
122 | File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
123 |
124 | for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
125 | //for each dex file, ie: test.classes2.zip, test.classes3.zip...
126 | String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
127 | File extractedFile = new File(dexDir, fileName);
128 | if (extractedFile.isFile()) {
129 | sourcePaths.add(extractedFile.getAbsolutePath());
130 | //we ignore the verify zip part
131 | } else {
132 | throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
133 | }
134 | }
135 | }
136 |
137 | if (AppInit.debuggable()) { // Search instant run support only debuggable
138 | sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
139 | }
140 | return sourcePaths;
141 | }
142 |
143 | /**
144 | * Get instant run dex path, used to catch the branch usingApkSplits=false.
145 | */
146 | private static List tryLoadInstantRunDexFile(ApplicationInfo applicationInfo) {
147 | List instantRunSourcePaths = new ArrayList<>();
148 |
149 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && null != applicationInfo.splitSourceDirs) {
150 | // add the split apk, normally for InstantRun, and newest version.
151 | instantRunSourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs));
152 | AppInit.logger().debug(Consts.TAG, "Found InstantRun support");
153 | } else {
154 | try {
155 | // This man is reflection from Google instant run sdk, he will tell me where the dex files go.
156 | Class pathsByInstantRun = Class.forName("com.android.tools.fd.runtime.Paths");
157 | Method getDexFileDirectory = pathsByInstantRun.getMethod("getDexFileDirectory", String.class);
158 | String instantRunDexPath = (String) getDexFileDirectory.invoke(null, applicationInfo.packageName);
159 |
160 | File instantRunFilePath = new File(instantRunDexPath);
161 | if (instantRunFilePath.exists() && instantRunFilePath.isDirectory()) {
162 | File[] dexFile = instantRunFilePath.listFiles();
163 | for (File file : dexFile) {
164 | if (null != file && file.exists() && file.isFile() && file.getName().endsWith(".dex")) {
165 | instantRunSourcePaths.add(file.getAbsolutePath());
166 | }
167 | }
168 | AppInit.logger().debug(Consts.TAG, "Found InstantRun support");
169 | }
170 |
171 | } catch (Exception e) {
172 | AppInit.logger().error(Consts.TAG, "InstantRun support error, " + e.getMessage());
173 | }
174 | }
175 |
176 | return instantRunSourcePaths;
177 | }
178 |
179 | /**
180 | * Identifies if the current VM has a native support for multidex, meaning there is no need for
181 | * additional installation by this library.
182 | *
183 | * @return true if the VM handles multidex
184 | */
185 | private static boolean isVMMultidexCapable() {
186 | boolean isMultidexCapable = false;
187 | String vmName = null;
188 |
189 | try {
190 | if (isYunOS()) { // YunOS需要特殊判断
191 | vmName = "'YunOS'";
192 | isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21;
193 | } else { // 非YunOS原生Android
194 | vmName = "'Android'";
195 | String versionString = System.getProperty("java.vm.version");
196 | if (versionString != null) {
197 | Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
198 | if (matcher.matches()) {
199 | try {
200 | int major = Integer.parseInt(matcher.group(1));
201 | int minor = Integer.parseInt(matcher.group(2));
202 | isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
203 | || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
204 | && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
205 | } catch (NumberFormatException ignore) {
206 | // let isMultidexCapable be false
207 | }
208 | }
209 | }
210 | }
211 | } catch (Exception e) { // ignore
212 | e.printStackTrace();
213 | }
214 | AppInit.logger().info(Consts.TAG, "VM with name " + vmName + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
215 | return isMultidexCapable;
216 | }
217 |
218 | /**
219 | * 判断系统是否为YunOS系统
220 | */
221 | private static boolean isYunOS() {
222 | try {
223 | String version = System.getProperty("ro.yunos.version");
224 | String vmName = System.getProperty("java.vm.name");
225 | return (vmName != null && vmName.toLowerCase().contains("lemur"))
226 | || (version != null && version.trim().length() > 0);
227 | } catch (Exception ignore) {
228 | return false;
229 | }
230 | }
231 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/utils/Consts.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.utils
2 |
3 | object Consts {
4 | private const val SDK_NAME = "AppInit"
5 | const val TAG = "$SDK_NAME::"
6 | const val APP_INIT_SCAN_ROOT_PACKAGE = "me.hacket.appinit.apt.taskregister"
7 | const val APP_INIT_SP_FILE_NAME = "sp_appinit"
8 | const val APP_INIT_SP_KEY_MAP = "key_appinit_cache"
9 | const val APP_INIT_CACHE_LAST_VERSION_NAME = "key_appinit_last_version_name"
10 | const val APP_INIT_CACHE_LAST_VERSION_CODE = "key_appinit_last_version_code"
11 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/utils/Logger.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.utils
2 |
3 | import android.text.TextUtils
4 | import android.util.Log
5 | import me.hacket.appinit.api.facade.ILogger
6 | import java.lang.StringBuilder
7 |
8 | class Logger : ILogger {
9 | override var defaultTag = Consts.TAG
10 | private set
11 |
12 | override fun showLog(showLog: Boolean) {
13 | isShowLog = showLog
14 | }
15 |
16 | override fun showStackTrace(showStackTrace: Boolean) {
17 | isShowStackTrace = showStackTrace
18 | }
19 |
20 | fun showMonitor(showMonitor: Boolean) {
21 | Companion.isMonitorMode = showMonitor
22 | }
23 |
24 | constructor(defaultTag: String) {
25 | this.defaultTag = defaultTag
26 | }
27 |
28 | override fun debug(tag: String?, message: String?) {
29 | if (isShowLog) {
30 | val stackTraceElement = Thread.currentThread().stackTrace[3]
31 | Log.d(
32 | if (TextUtils.isEmpty(tag)) defaultTag else tag,
33 | message + getExtInfo(stackTraceElement)
34 | )
35 | }
36 | }
37 |
38 | override fun info(tag: String?, message: String?) {
39 | if (isShowLog) {
40 | val stackTraceElement = Thread.currentThread().stackTrace[3]
41 | Log.i(
42 | if (TextUtils.isEmpty(tag)) defaultTag else tag,
43 | message + getExtInfo(stackTraceElement)
44 | )
45 | }
46 | }
47 |
48 | override fun warning(tag: String?, message: String?) {
49 | if (isShowLog) {
50 | val stackTraceElement = Thread.currentThread().stackTrace[3]
51 | Log.w(
52 | if (TextUtils.isEmpty(tag)) defaultTag else tag,
53 | message + getExtInfo(stackTraceElement)
54 | )
55 | }
56 | }
57 |
58 | override fun error(tag: String?, message: String?) {
59 | if (isShowLog) {
60 | val stackTraceElement = Thread.currentThread().stackTrace[3]
61 | Log.e(
62 | if (TextUtils.isEmpty(tag)) defaultTag else tag,
63 | message + getExtInfo(stackTraceElement)
64 | )
65 | }
66 | }
67 |
68 | override fun error(tag: String?, message: String?, e: Throwable?) {
69 | if (isShowLog) {
70 | Log.e(if (TextUtils.isEmpty(tag)) defaultTag else tag, message, e)
71 | }
72 | }
73 |
74 | override fun monitor(message: String?) {
75 | if (isShowLog && isMonitorMode) {
76 | val stackTraceElement = Thread.currentThread().stackTrace[3]
77 | Log.d("$defaultTag::monitor", message + getExtInfo(stackTraceElement))
78 | }
79 | }
80 |
81 | override val isMonitorMode: Boolean
82 | get() = Companion.isMonitorMode
83 |
84 | companion object {
85 | private var isShowLog = true
86 | private var isShowStackTrace = true
87 | private var isMonitorMode = false
88 | fun getExtInfo(stackTraceElement: StackTraceElement): String {
89 | if (isShowStackTrace) {
90 | val separator = " & "
91 | val sb = StringBuilder("[")
92 | val threadName = Thread.currentThread().name
93 | val fileName = stackTraceElement.fileName
94 | val className = stackTraceElement.className
95 | val methodName = stackTraceElement.methodName
96 | val threadID = Thread.currentThread().id
97 | val lineNumber = stackTraceElement.lineNumber
98 | sb.append("ThreadId=").append(threadID).append(separator)
99 | sb.append("ThreadName=").append(threadName).append(separator)
100 | sb.append("FileName=").append(fileName).append(separator)
101 | sb.append("ClassName=").append(className).append(separator)
102 | sb.append("MethodName=").append(methodName).append(separator)
103 | sb.append("LineNumber=").append(lineNumber)
104 | sb.append(" ] ")
105 | return sb.toString()
106 | }
107 | return ""
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/utils/PackageUtils.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.utils
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageInfo
5 | import android.content.pm.PackageManager
6 | import android.text.TextUtils
7 | import android.util.Log
8 | import java.lang.Exception
9 |
10 | object PackageUtils {
11 | private var NEW_VERSION_NAME: String? = null
12 | private var NEW_VERSION_CODE = 0
13 | fun isNewVersion(context: Context): Boolean {
14 | val packageInfo = getPackageInfo(context)
15 | return if (null != packageInfo) {
16 | val versionName = packageInfo.versionName
17 | val versionCode = packageInfo.versionCode
18 | val sp = context.getSharedPreferences(
19 | Consts.APP_INIT_SP_FILE_NAME,
20 | Context.MODE_PRIVATE
21 | )
22 | if (versionName != sp.getString(
23 | Consts.APP_INIT_CACHE_LAST_VERSION_NAME,
24 | null
25 | ) || versionCode != sp.getInt(Consts.APP_INIT_CACHE_LAST_VERSION_CODE, -1)
26 | ) {
27 | // new version
28 | NEW_VERSION_NAME = versionName
29 | NEW_VERSION_CODE = versionCode
30 | true
31 | } else {
32 | false
33 | }
34 | } else {
35 | true
36 | }
37 | }
38 |
39 | fun updateVersion(context: Context) {
40 | if (!TextUtils.isEmpty(NEW_VERSION_NAME) && NEW_VERSION_CODE != 0) {
41 | val sp =
42 | context.getSharedPreferences(Consts.APP_INIT_SP_FILE_NAME, Context.MODE_PRIVATE)
43 | sp.edit().putString(Consts.APP_INIT_CACHE_LAST_VERSION_NAME, NEW_VERSION_NAME)
44 | .putInt(Consts.APP_INIT_CACHE_LAST_VERSION_CODE, NEW_VERSION_CODE).apply()
45 | }
46 | }
47 |
48 | private fun getPackageInfo(context: Context): PackageInfo? {
49 | var packageInfo: PackageInfo? = null
50 | try {
51 | packageInfo = context.packageManager.getPackageInfo(
52 | context.packageName,
53 | PackageManager.GET_CONFIGURATIONS
54 | )
55 | } catch (ex: Exception) {
56 | Log.e(Consts.TAG, "Get package info error.")
57 | }
58 | return packageInfo
59 | }
60 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/utils/ProcessUtils.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.utils
2 |
3 | import android.app.ActivityManager
4 | import android.content.Context
5 | import android.os.Process
6 | import java.io.BufferedReader
7 | import java.io.FileInputStream
8 | import java.io.IOException
9 | import java.io.InputStreamReader
10 |
11 | internal object ProcessUtils {
12 |
13 | fun isMainProcess(context: Context?): Boolean {
14 | if (context == null) {
15 | return false
16 | }
17 | val packageName = context.applicationContext.packageName
18 | val processName = getProcessName(context)
19 | return packageName == processName
20 | }
21 |
22 | fun getProcessName(context: Context): String {
23 | var processName = getProcessFromFile()
24 | if (processName == null || processName.isEmpty()) {
25 | // 如果装了xposed一类的框架,上面可能会拿不到,回到遍历迭代的方式
26 | processName = getProcessNameByAM(context)
27 | }
28 | return processName
29 | }
30 |
31 | private fun getProcessFromFile(): String? {
32 | var reader: BufferedReader? = null
33 | return try {
34 | val pid = Process.myPid()
35 | val file = "/proc/$pid/cmdline"
36 | reader = BufferedReader(InputStreamReader(FileInputStream(file), "iso-8859-1"))
37 | var c: Int
38 | val processName = StringBuilder()
39 | while (reader.read().also { c = it } > 0) {
40 | processName.append(c.toChar())
41 | }
42 | processName.toString()
43 | } catch (e: Exception) {
44 | null
45 | } finally {
46 | try {
47 | reader?.close()
48 | } catch (e: IOException) {
49 | e.printStackTrace()
50 | }
51 | }
52 | }
53 |
54 | private fun getProcessNameByAM(context: Context): String {
55 | var processName: String? = null
56 | val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
57 | while (true) {
58 | val plist = am.runningAppProcesses
59 | if (plist != null) {
60 | for (info in plist) {
61 | if (info.pid == Process.myPid()) {
62 | processName = info.processName
63 | break
64 | }
65 | }
66 | }
67 | if (processName != null && processName.isNotEmpty()) {
68 | return processName
69 | }
70 | try {
71 | Thread.sleep(100L) // take a rest and again
72 | } catch (ex: InterruptedException) {
73 | ex.printStackTrace()
74 | }
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/appinit-api/src/main/java/me/hacket/appinit/api/utils/TextUtils.java:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.api.utils;
2 |
3 | import android.net.Uri;
4 |
5 | import java.util.Collections;
6 | import java.util.LinkedHashMap;
7 | import java.util.Map;
8 |
9 | /**
10 | * Text utils
11 | */
12 | public class TextUtils {
13 |
14 | public static boolean isEmpty(final CharSequence cs) {
15 | return cs == null || cs.length() == 0;
16 | }
17 |
18 | /**
19 | * Print thread stack
20 | */
21 | public static String formatStackTrace(StackTraceElement[] stackTrace) {
22 | StringBuilder sb = new StringBuilder();
23 | for (StackTraceElement element : stackTrace) {
24 | sb.append(" at ").append(element.toString());
25 | sb.append("\n");
26 | }
27 | return sb.toString();
28 | }
29 |
30 | /**
31 | * Split query parameters
32 | * @param rawUri raw uri
33 | * @return map with params
34 | */
35 | public static Map splitQueryParameters(Uri rawUri) {
36 | String query = rawUri.getEncodedQuery();
37 |
38 | if (query == null) {
39 | return Collections.emptyMap();
40 | }
41 |
42 | Map paramMap = new LinkedHashMap<>();
43 | int start = 0;
44 | do {
45 | int next = query.indexOf('&', start);
46 | int end = (next == -1) ? query.length() : next;
47 |
48 | int separator = query.indexOf('=', start);
49 | if (separator > end || separator == -1) {
50 | separator = end;
51 | }
52 |
53 | String name = query.substring(start, separator);
54 |
55 | if (!android.text.TextUtils.isEmpty(name)) {
56 | String value = (separator == end ? "" : query.substring(separator + 1, end));
57 | paramMap.put(Uri.decode(name), Uri.decode(value));
58 | }
59 |
60 | // Move start to end of name.
61 | start = end + 1;
62 | } while (start < query.length());
63 |
64 | return Collections.unmodifiableMap(paramMap);
65 | }
66 |
67 | /**
68 | * Split key with |
69 | *
70 | * @param key raw key
71 | * @return left key
72 | */
73 | public static String getLeft(String key) {
74 | if (key.contains("|") && !key.endsWith("|")) {
75 | return key.substring(0, key.indexOf("|"));
76 | } else {
77 | return key;
78 | }
79 | }
80 |
81 | /**
82 | * Split key with |
83 | *
84 | * @param key raw key
85 | * @return right key
86 | */
87 | public static String getRight(String key) {
88 | if (key.contains("|") && !key.startsWith("|")) {
89 | return key.substring(key.indexOf("|") + 1);
90 | } else {
91 | return key;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/appinit-auto-register/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/appinit-auto-register/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'groovy'
2 | apply plugin: 'java'
3 | //apply plugin: 'maven-publish'
4 |
5 | java {
6 | sourceCompatibility = JavaVersion.VERSION_1_7
7 | targetCompatibility = JavaVersion.VERSION_1_7
8 | }
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 |
14 | dependencies {
15 | //gradle sdk
16 | implementation gradleApi()
17 | //groovy sdk
18 | implementation localGroovy()
19 |
20 | implementation 'com.android.tools.build:gradle:4.1.3'
21 | }
22 |
23 | //afterEvaluate {
24 | // publishing {
25 | // publications {
26 | // release(MavenPublication) {
27 | // from components.java
28 | // groupId = "io.github.hacket"
29 | // artifactId = "appinit-auto-register-plugin"
30 | // version = "1.0.0"
31 | // }
32 | // }
33 | // repositories {
34 | // maven {
35 | // url = "${rootDir}/repo"
36 | // }
37 | // }
38 | // }
39 | //}
--------------------------------------------------------------------------------
/appinit-auto-register/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/appinit-auto-register/consumer-rules.pro
--------------------------------------------------------------------------------
/appinit-auto-register/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/appinit-auto-register/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/appinit-auto-register/src/main/groovy/me/hacket/appinit/autoregister/AppInitAutoRegisterPlugin.groovy:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.autoregister
2 |
3 | import com.android.build.gradle.AppExtension
4 | import org.gradle.api.Plugin
5 | import org.gradle.api.Project
6 |
7 | class AppInitAutoRegisterPlugin implements Plugin {
8 |
9 | @Override
10 | void apply(Project project) {
11 | println "------AppInitAutoRegisterPlugin plugin entrance-------"
12 | def android = project.extensions.getByType(AppExtension)
13 | android.registerTransform(new AppInitAutoRegisterTransform(project))
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/appinit-auto-register/src/main/groovy/me/hacket/appinit/autoregister/AppInitAutoRegisterTransform.groovy:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.autoregister
2 |
3 | import com.android.build.api.transform.*
4 | import com.android.build.gradle.internal.pipeline.TransformManager
5 | import org.apache.commons.codec.digest.DigestUtils
6 | import org.apache.commons.io.FileUtils
7 | import org.gradle.api.Project
8 |
9 | class AppInitAutoRegisterTransform extends Transform {
10 |
11 | private def project
12 |
13 | AppInitAutoRegisterTransform(Project project) {
14 | this.project = project
15 | }
16 |
17 | @Override
18 | String getName() {
19 | return "AppInitAutoRegisterTransform"
20 | }
21 |
22 | @Override
23 | Set getInputTypes() {
24 | return TransformManager.CONTENT_CLASS
25 | }
26 |
27 | @Override
28 | Set super QualifiedContent.Scope> getScopes() {
29 | return TransformManager.SCOPE_FULL_PROJECT
30 | }
31 |
32 | @Override
33 | boolean isIncremental() {
34 | return true
35 | }
36 |
37 | @Override
38 | void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
39 | super.transform(transformInvocation)
40 | }
41 |
42 | @Override
43 | void transform(Context context, Collection inputs, Collection referencedInputs,
44 | TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
45 | println "\n-------------->>>>>>> start to transform ----------------<<<<<<<"
46 |
47 | def registerClassList = []
48 | inputs.each { TransformInput input ->
49 | input.directoryInputs.each { DirectoryInput directoryInput ->
50 |
51 | if (directoryInput.file.isDirectory()) {
52 | directoryInput.file.eachFileRecurse { File file ->
53 | //形如 AppInitTaskRegister$******.class 的类,是我们要找的目标class
54 | if (ScanUtil.isTargetClass(file)) {
55 | registerClassList.add(file.name)
56 | }
57 | }
58 | }
59 |
60 | def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
61 | FileUtils.copyDirectory(directoryInput.file, dest)
62 | }
63 |
64 | input.jarInputs.each { JarInput jarInput ->
65 | println "\n-------------->>>>>>> jarInput = ${jarInput} ----------------<<<<<<<"
66 |
67 | def jarName = jarInput.name
68 | def md5 = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
69 | if (jarName.endsWith(".jar")) {
70 | jarName = jarName.substring(0, jarName.length() - 4)
71 | }
72 | def dest = outputProvider.getContentLocation(jarName + md5, jarInput.contentTypes, jarInput.scopes, Format.JAR)
73 | if (jarInput.file.getAbsolutePath().endsWith(".jar")) {
74 | // 处理jar包里的代码
75 | File src = jarInput.file
76 | if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) {
77 | List list = ScanUtil.scanJar(src, dest)
78 | if (list != null) {
79 | registerClassList.addAll(list)
80 | }
81 | }
82 | }
83 | FileUtils.copyFile(jarInput.file, dest)
84 | }
85 | }
86 |
87 | println ""
88 | registerClassList.forEach({ fileName ->
89 | println "-------------->>>>>>> file name = " + fileName + " ----------------<<<<<<<"
90 | })
91 | println "\n-------------->>>>>>> 包含AppInitAutoRegister类的jar文件"
92 | println ScanUtil.FILE_CONTAINS_INIT_CLASS_JAR.getAbsolutePath()
93 | println "-------------->>>>>>> 开始自动注册 ----------------<<<<<<<"
94 |
95 | new RegisterCodeGenerator(registerClassList).execute()
96 |
97 | println "-------------->>>>>>> transform finish----------------<<<<<<<\n"
98 | }
99 | }
--------------------------------------------------------------------------------
/appinit-auto-register/src/main/groovy/me/hacket/appinit/autoregister/RegisterCodeGenerator.groovy:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.autoregister
2 |
3 | import org.apache.commons.io.IOUtils
4 | import org.objectweb.asm.*
5 | import org.objectweb.asm.commons.AdviceAdapter
6 |
7 | import java.util.jar.JarEntry
8 | import java.util.jar.JarFile
9 | import java.util.jar.JarOutputStream
10 | import java.util.zip.ZipEntry
11 |
12 | class RegisterCodeGenerator {
13 |
14 | List registerTargetClassList
15 |
16 | RegisterCodeGenerator(List list) {
17 | registerTargetClassList = list
18 | }
19 |
20 | void execute() {
21 | println("开始执行ASM方法======>>>>>>>>")
22 |
23 | File srcFile = ScanUtil.FILE_CONTAINS_INIT_CLASS_JAR
24 | // 创建一个临时jar文件,要修改注入的字节码会先写入该文件里
25 | def tempJar = new File(srcFile.getParent(), srcFile.name + ".opt")
26 | if (tempJar.exists()) {
27 | tempJar.delete()
28 | }
29 | JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tempJar))
30 | // 遍历目标jar
31 | def srcJarFile = new JarFile(srcFile)
32 | Enumeration enumeration = srcJarFile.entries()
33 | while (enumeration.hasMoreElements()) {
34 | JarEntry jarEntry = enumeration.nextElement()
35 | String entryName = jarEntry.getName()
36 | println "entryName=" + entryName
37 | ZipEntry zipEntry = new ZipEntry(entryName)
38 | InputStream inputStream = srcJarFile.getInputStream(jarEntry)
39 | jarOutputStream.putNextEntry(zipEntry)
40 | // 找到需要插入代码的class,通过ASM动态注入字节码
41 | if (ScanUtil.GENERATE_TO_CLASS_NAME + ".class" == entryName) {
42 | println "insert register code to class >> " + entryName
43 |
44 | ClassReader classReader = new ClassReader(inputStream)
45 | // 构建一个ClassWriter对象,并设置让系统自动计算栈和本地变量大小
46 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS)
47 | ClassVisitor classVisitor = new AppInitAutoRegisterClassVisitor(classWriter)
48 | // 开始扫描class文件
49 | classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
50 |
51 | byte[] bytes = classWriter.toByteArray()
52 | // 将注入过字节码的class,写入临时jar文件里
53 | jarOutputStream.write(bytes)
54 | } else {
55 | // 不需要修改的class,原样写入临时jar文件里
56 | jarOutputStream.write(IOUtils.toByteArray(inputStream))
57 | }
58 | inputStream.close()
59 | jarOutputStream.closeEntry()
60 | }
61 |
62 | jarOutputStream.close()
63 | srcJarFile.close()
64 |
65 | // 删除原来的jar文件
66 | if (srcFile.exists()) {
67 | println "delete srcFile=$srcFile"
68 | srcFile.delete()
69 | }
70 | println "renameTo $tempJar to $srcFile"
71 | // 重新命名临时jar文件,新的jar包里已经包含了我们注入的字节码了
72 | tempJar.renameTo(srcFile)
73 | }
74 |
75 | class AppInitAutoRegisterClassVisitor extends ClassVisitor {
76 | AppInitAutoRegisterClassVisitor(ClassVisitor classVisitor) {
77 | super(Opcodes.ASM5, classVisitor)
78 | }
79 |
80 | @Override
81 | MethodVisitor visitMethod(int access, String name,
82 | String desc, String signature,
83 | String[] exception) {
84 | println "visit method: " + name
85 | MethodVisitor mv = super.visitMethod(access, name, desc, signature, exception)
86 | // 找到AppInitAutoRegister里的load()方法
87 | if ("load" == name) {
88 | mv = new AutoLoadMethodAdapter(mv, access, name, desc)
89 | }
90 | return mv
91 | }
92 | }
93 |
94 | class AutoLoadMethodAdapter extends AdviceAdapter {
95 | protected AutoLoadMethodAdapter(MethodVisitor methodVisitor, int access, String name, String descriptor) {
96 | super(ASM7, methodVisitor, access, name, descriptor)
97 | }
98 |
99 | @Override
100 | protected void onMethodEnter() {
101 | super.onMethodEnter()
102 | println "-------onMethodEnter ${this.name}."
103 | }
104 |
105 | @Override
106 | protected void onMethodExit(int opcode) {
107 | println "-------onMethodEnter ${this.name}."
108 | super.onMethodExit(opcode)
109 | }
110 |
111 | @Override
112 | void visitInsn(int opcode) {
113 | // generate code before return
114 | if ((opcode >= IRETURN && opcode <= RETURN)) {
115 |
116 | println "-------visitInsn ${this.name},opcode=$opcode, registerTargetClassList(${registerTargetClassList.size()})=$registerTargetClassList------"
117 |
118 | // 在AppInitAutoRegister.load方法调用后添加如下代码功能:
119 | // AppInitAutoRegister.register("me.hacket.appinit.apt.taskregister.AppInitTaskRegister$app")
120 |
121 | registerTargetClassList.forEach({ proxyClassName ->
122 | println "----------visitInsn start inject:${proxyClassName}"
123 | def targetClassName = ScanUtil.TARGET_CLASS_PACKAGE_NAME.replace("/", ".") + "." + proxyClassName.substring(0, proxyClassName.length() - 6)
124 | println "----------visitInsn targetClassName full classname = ${targetClassName}"
125 |
126 | mv.visitLdcInsn(targetClassName) // 类名
127 | // generate invoke register method into AppInitAutoRegister.load()
128 | mv.visitMethodInsn(INVOKESTATIC
129 | , ScanUtil.GENERATE_TO_CLASS_NAME
130 | , ScanUtil.REGISTER_METHOD_NAME
131 | , "(Ljava/lang/String;)V"
132 | , false)
133 |
134 | println "-----------visitInsn end inject:${proxyClassName}."
135 | })
136 | }
137 |
138 | super.visitInsn(opcode)
139 | }
140 | }
141 |
142 | }
--------------------------------------------------------------------------------
/appinit-auto-register/src/main/groovy/me/hacket/appinit/autoregister/ScanUtil.groovy:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.autoregister
2 |
3 | import java.util.jar.JarEntry
4 | import java.util.jar.JarFile
5 |
6 | class ScanUtil {
7 |
8 | static final String TARGET_CLASS_PREFIX = "AppInitTaskRegister\$"
9 | static final String TARGET_CLASS_PACKAGE_NAME = "me/hacket/appinit/apt/taskregister"
10 |
11 | static final String REGISTER_CLASS_PACKAGE = "me/hacket/appinit/api/"
12 | /**
13 | * The register code is generated into this class
14 | */
15 | static final String GENERATE_TO_CLASS_NAME = REGISTER_CLASS_PACKAGE + "AppInitAutoRegister"
16 | /**
17 | * register method name in class: {@link #GENERATE_TO_CLASS_NAME}
18 | */
19 | static final String REGISTER_METHOD_NAME = "register"
20 |
21 | // 包含生命周期管理初始化类的文件,及包含 me.hacket.appinit.api.AppInitAutoRegister 类的class文件或者jar文件
22 | static File FILE_CONTAINS_INIT_CLASS_JAR
23 |
24 | /**
25 | * 判断该class是否是我们的目标类
26 | *
27 | * @param file
28 | * @return
29 | */
30 | static boolean isTargetClass(File file) {
31 | if (file.name.startsWith(TARGET_CLASS_PREFIX)) {
32 | return true
33 | }
34 | return false
35 | }
36 |
37 | /**
38 | * 扫描jar包里的所有class文件:
39 | * 1.通过包名识别所有需要注入的类名
40 | * 2.找到注入管理类所在的jar包,后面我们会在该jar包里进行代码注入
41 | *
42 | * @param jarFile
43 | * @param destFile
44 | * @return
45 | */
46 | static List scanJar(File jarFile, File destFile) {
47 | def file = new JarFile(jarFile)
48 | Enumeration enumeration = file.entries()
49 | List list = null
50 | while (enumeration.hasMoreElements()) {
51 | JarEntry jarEntry = enumeration.nextElement()
52 | String entryName = jarEntry.getName()
53 | if (entryName == GENERATE_TO_CLASS_NAME + ".class") {
54 | //标记这个jar包包含 AppInitAutoRegister.class
55 | //扫描结束后,我们会生成注册代码到这个文件里
56 | FILE_CONTAINS_INIT_CLASS_JAR = destFile
57 | } else {
58 | if (entryName.startsWith(TARGET_CLASS_PACKAGE_NAME)) {
59 | if (list == null) {
60 | list = new ArrayList<>()
61 | }
62 | list.addAll(entryName.substring(entryName.lastIndexOf("/") + 1))
63 | }
64 | }
65 | }
66 | return list
67 | }
68 |
69 | static boolean shouldProcessPreDexJar(String path) {
70 | return !path.contains("com.android.support") && !path.contains("/android/m2repository")
71 | }
72 |
73 | }
--------------------------------------------------------------------------------
/appinit-auto-register/src/main/resources/META-INF/gradle-plugins/appinit-auto-register.properties:
--------------------------------------------------------------------------------
1 | implementation-class=me.hacket.appinit.autoregister.AppInitAutoRegisterPlugin
--------------------------------------------------------------------------------
/appinit-compiler-ksp/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/appinit-compiler-ksp/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.kotlin.jvm'
3 | }
4 |
5 | dependencies {
6 | implementation project(':appinit-annotation')
7 |
8 | implementation 'com.google.devtools.ksp:symbol-processing-api:1.7.20-1.0.7'
9 | implementation 'com.squareup:kotlinpoet:1.10.2'
10 | }
--------------------------------------------------------------------------------
/appinit-compiler-ksp/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/appinit-compiler-ksp/consumer-rules.pro
--------------------------------------------------------------------------------
/appinit-compiler-ksp/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/appinit-compiler-ksp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/appinit-compiler-ksp/src/main/java/me/hacket/appinit/compiler/AppInitSymbolProcessor.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.compiler
2 |
3 | import com.google.devtools.ksp.KspExperimental
4 | import com.google.devtools.ksp.getAllSuperTypes
5 | import com.google.devtools.ksp.getAnnotationsByType
6 | import com.google.devtools.ksp.processing.*
7 | import com.google.devtools.ksp.symbol.KSAnnotated
8 | import com.google.devtools.ksp.symbol.KSClassDeclaration
9 | import com.google.devtools.ksp.symbol.KSDeclaration
10 | import com.squareup.kotlinpoet.*
11 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
12 | import me.hacket.appinit.annotation.AppInitTask
13 | import me.hacket.appinit.annotation.AppInitTaskInfo
14 | import me.hacket.appinit.annotation.AppInitTaskRegister
15 |
16 | class AppInitSymbolProcessor(
17 | private val codeGenerator: CodeGenerator,
18 | private val logger: KSPLogger,
19 | private val moduleName: String
20 | ) : SymbolProcessor {
21 |
22 | @OptIn(KspExperimental::class, DelicateKotlinPoetApi::class)
23 | override fun process(resolver: Resolver): List {
24 | val taskList = resolver.getSymbolsWithAnnotation(AppInitTask::class.java.name).toList()
25 | if (taskList.isEmpty()) {
26 | return emptyList()
27 | }
28 |
29 | L.warn("$moduleName Found AppInitTasks, size is ${taskList.size}")
30 |
31 | /**
32 | * Param type: MutableSet
33 | *
34 | * There's no such type as MutableSet at runtime so the library only sees the runtime type.
35 | * If you need MutableSet then you'll need to use a ClassName to create it.
36 | * [https://github.com/square/kotlinpoet/issues/482]
37 | */
38 | // MutableList
39 | val inputMapTypeName =
40 | ClassName(
41 | "kotlin.collections",
42 | "MutableSet"
43 | ).parameterizedBy(AppInitTaskInfo::class.asTypeName())
44 |
45 | // taskInfoList: MutableSet
46 | val paramSpec =
47 | ParameterSpec.builder(Utils.PARAM_NAME, inputMapTypeName).build()
48 |
49 | // override fun register(taskInfoList: MutableSet)
50 | val functionBuilder = FunSpec.builder(Utils.METHOD_NAME)
51 | .addModifiers(KModifier.OVERRIDE)
52 | .addParameter(paramSpec)
53 |
54 | taskList.forEach { ksAnnotated ->
55 | check(ksAnnotated is KSClassDeclaration) {
56 | "Type [$ksAnnotated] with annotation [${AppInitTask::class.java.name}] should be a class, " +
57 | "interface and object."
58 | }
59 | checkNotNull(
60 | ksAnnotated.getAllSuperTypes().find { ksType ->
61 | ksType.declaration.toClassName().canonicalName == Utils.TASK_INTERFACE_NAME
62 | }
63 | ) {
64 | "Type [${ksAnnotated.declarations}] with annotation [${AppInitTask::class.java.name}] " +
65 | "should implement $Utils.TASK_INTERFACE_NAME."
66 | }
67 |
68 | val declaration = ksAnnotated as KSDeclaration
69 | val taskCn = declaration.toClassName()
70 |
71 | L.warn("[AppInitTask] Found appTask: ${taskCn.canonicalName}")
72 |
73 | val task =
74 | declaration.getAnnotationsByType(AppInitTask::class).firstOrNull() ?: return@forEach
75 |
76 | // taskInfoList.add(AppInitTaskInfo("main1", false, 0, arrayOf("PROCESS_MAIN"),
77 | // arrayOf("lib1"), MainAppInit()))
78 | functionBuilder.addStatement(
79 | "%N.add(%T(%S, %L, %L, %L, %L, %T()))",
80 | Utils.PARAM_NAME,
81 | AppInitTaskInfo::class.java,
82 | task.id,
83 | task.background,
84 | task.priority,
85 | Utils.formatArray(task.process),
86 | Utils.formatArray(task.dependencies),
87 | taskCn
88 | )
89 | }
90 |
91 | val fileSpec = FileSpec.builder(Utils.PACKAGE_NAME, "AppInitTaskRegister\$$moduleName")
92 | .addType(
93 | TypeSpec.classBuilder("AppInitTaskRegister\$$moduleName")
94 | .addKdoc(Utils.JAVADOC)
95 | .addSuperinterface(AppInitTaskRegister::class.java)
96 | .addFunction(functionBuilder.build())
97 | .build()
98 | )
99 | .build()
100 |
101 | val file =
102 | codeGenerator.createNewFile(Dependencies.ALL_FILES, fileSpec.packageName, fileSpec.name)
103 | file.write(fileSpec.toString().toByteArray())
104 |
105 | // /**
106 | // * DO NOT EDIT THIS FILE! IT WAS GENERATED BY INIT.
107 | // */
108 | // public class `AppInitTaskRegister$app` : AppInitTaskRegister {
109 | // public override fun register(taskInfoList: MutableSet): Unit {
110 | // taskInfoList.add(AppInitTaskInfo("main1", false, 0, arrayOf("PROCESS_MAIN"), arrayOf("lib1"),
111 | // MainAppInit()))
112 | // }
113 | // }
114 | return emptyList()
115 | }
116 | }
--------------------------------------------------------------------------------
/appinit-compiler-ksp/src/main/java/me/hacket/appinit/compiler/AppInitSymbolProcessorProvider.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.compiler
2 |
3 | import com.google.devtools.ksp.processing.SymbolProcessor
4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
5 | import com.google.devtools.ksp.processing.SymbolProcessorProvider
6 |
7 | class AppInitSymbolProcessorProvider : SymbolProcessorProvider {
8 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
9 | val moduleNameParam = environment.options[Utils.MODULE_PARAM_NAME]
10 | if (moduleNameParam == null || moduleNameParam.isEmpty()) {
11 | throw IllegalArgumentException(Utils.NO_MODULE_NAME_TIPS_KOTLIN)
12 | }
13 | val moduleName = Utils.formatModuleName(moduleNameParam)
14 | L.setLogger(environment.logger)
15 |
16 | L.info("[${this.javaClass.simpleName} create] now start to process module $moduleName.")
17 |
18 | return AppInitSymbolProcessor(environment.codeGenerator, environment.logger, moduleName)
19 | }
20 | }
--------------------------------------------------------------------------------
/appinit-compiler-ksp/src/main/java/me/hacket/appinit/compiler/L.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.compiler
2 |
3 | import com.google.devtools.ksp.processing.KSPLogger
4 | import java.io.PrintWriter
5 | import java.io.StringWriter
6 | import java.net.UnknownHostException
7 |
8 | object L {
9 | private var sLogger: KSPLogger? = null
10 |
11 | fun setLogger(logger: KSPLogger?) {
12 | sLogger = logger
13 | }
14 |
15 | fun info(msg: String) {
16 | sLogger?.info(Utils.TAG + msg + "\n")
17 | }
18 |
19 | fun warn(msg: String) {
20 | sLogger?.warn(Utils.TAG + msg + "\n")
21 | }
22 |
23 | fun error(msg: String) {
24 | sLogger?.error(Utils.TAG + msg + "\n")
25 | }
26 |
27 | fun error(msg: String, tr: Throwable) {
28 | sLogger?.error(Utils.TAG + msg + "\n" + getStackTraceString(tr) + "\n")
29 | sLogger?.exception(tr)
30 | }
31 |
32 | private fun getStackTraceString(tr: Throwable?): String {
33 | if (tr == null) {
34 | return ""
35 | }
36 |
37 | // This is to reduce the amount of log spew that apps do in the non-error
38 | // condition of the network being unavailable.
39 | var t = tr
40 | while (t != null) {
41 | if (t is UnknownHostException) {
42 | return ""
43 | }
44 | t = t.cause
45 | }
46 |
47 | val sw = StringWriter()
48 | val pw = PrintWriter(sw)
49 | tr.printStackTrace(pw)
50 | pw.flush()
51 | return sw.toString()
52 | }
53 | }
--------------------------------------------------------------------------------
/appinit-compiler-ksp/src/main/java/me/hacket/appinit/compiler/Utils.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.compiler
2 |
3 | import com.google.devtools.ksp.symbol.KSDeclaration
4 | import com.squareup.kotlinpoet.ClassName
5 |
6 | object Utils {
7 | private const val SDK_NAME = "AppInit"
8 | const val TAG = "-------------------->>> $SDK_NAME:: "
9 |
10 | const val MODULE_PARAM_NAME = "APPINIT_MODULE_NAME"
11 |
12 | const val PARAM_NAME = "taskInfoList"
13 | const val METHOD_NAME = "register"
14 | const val PACKAGE_NAME = "me.hacket.appinit.apt.taskregister"
15 | const val TASK_INTERFACE_NAME = "me.hacket.appinit.api.IAppInitTask"
16 |
17 | const val JAVADOC = "DO NOT EDIT THIS FILE! IT WAS GENERATED BY appinit-compiler-ksp.\n"
18 |
19 | const val NO_MODULE_NAME_TIPS_KOTLIN =
20 | "These no module name, at your module 'build.gradle', like :\n" +
21 | " ksp {\n" +
22 | " arg(\"${MODULE_PARAM_NAME}\", project.name)\n" +
23 | " }\n"
24 |
25 | const val NO_MODULE_NAME_TIPS_JAVA = "These no module name, at 'build.gradle', like :\n" +
26 | "android {\n" +
27 | " defaultConfig {\n" +
28 | " ...\n" +
29 | " javaCompileOptions {\n" +
30 | " annotationProcessorOptions {\n" +
31 | " arguments = [\"${MODULE_PARAM_NAME}\": project.getName()]\n" +
32 | " }\n" +
33 | " }\n" +
34 | " }\n" +
35 | "}\n"
36 |
37 | fun formatModuleName(moduleName: String): String {
38 | return moduleName.replace('-', '_')
39 | }
40 |
41 | fun formatArray(array: Array): String {
42 | val sb = StringBuilder()
43 | array.forEach {
44 | sb.append("\"$it\"").append(",")
45 | }
46 | if (sb.isNotEmpty()) {
47 | sb.deleteAt(sb.length - 1)
48 | }
49 | return "arrayOf($sb)"
50 | }
51 | }
52 |
53 | fun KSDeclaration.toClassName(): ClassName {
54 | return ClassName(packageName.asString(), simpleName.asString())
55 | }
--------------------------------------------------------------------------------
/appinit-compiler-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider:
--------------------------------------------------------------------------------
1 | me.hacket.appinit.compiler.AppInitSymbolProcessorProvider
--------------------------------------------------------------------------------
/appinit-compiler/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/appinit-compiler/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.kotlin.jvm'
3 | }
4 |
5 | dependencies {
6 | implementation 'com.squareup:kotlinpoet:1.10.2'
7 | implementation project(':appinit-annotation')
8 | }
--------------------------------------------------------------------------------
/appinit-compiler/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/appinit-compiler/consumer-rules.pro
--------------------------------------------------------------------------------
/appinit-compiler/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/appinit-compiler/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/appinit-compiler/src/main/java/me/hacket/appinit/compiler/AppInitTaskProcessor.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.compiler
2 |
3 | import com.squareup.kotlinpoet.*
4 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
5 | import me.hacket.appinit.annotation.AppInitTask
6 | import me.hacket.appinit.annotation.AppInitTaskInfo
7 | import me.hacket.appinit.annotation.AppInitTaskRegister
8 | import javax.annotation.processing.AbstractProcessor
9 | import javax.annotation.processing.Filer
10 | import javax.annotation.processing.ProcessingEnvironment
11 | import javax.annotation.processing.RoundEnvironment
12 | import javax.lang.model.SourceVersion
13 | import javax.lang.model.element.Element
14 | import javax.lang.model.element.TypeElement
15 | import javax.lang.model.util.Elements
16 | import javax.lang.model.util.Types
17 |
18 | class AppInitTaskProcessor : AbstractProcessor() {
19 |
20 | private lateinit var mFiler: Filer
21 | private lateinit var mElementUtils: Elements
22 | private lateinit var mTypeUtils: Types
23 | private lateinit var moduleName: String
24 |
25 | override fun init(processingEnv: ProcessingEnvironment) {
26 | super.init(processingEnv)
27 |
28 | mFiler = processingEnv.filer
29 | mElementUtils = processingEnv.elementUtils
30 | mTypeUtils = processingEnv.typeUtils
31 | L.setMessager(processingEnv.messager)
32 |
33 | val moduleName = processingEnv.options[Utils.MODULE_PARAM_NAME]
34 | if (moduleName == null || moduleName.isEmpty()) {
35 | throw IllegalArgumentException(Utils.NO_MODULE_NAME_TIPS_KOTLIN)
36 | }
37 | this.moduleName = Utils.formatModuleName(moduleName)
38 |
39 | L.info("[${this.javaClass.simpleName} init] Start to deal module ${this.moduleName}")
40 | }
41 |
42 | override fun getSupportedAnnotationTypes(): MutableSet {
43 | val supportAnnotationTypes = mutableSetOf()
44 | supportAnnotationTypes.add(AppInitTask::class.java.canonicalName)
45 | return supportAnnotationTypes
46 | }
47 |
48 | override fun getSupportedSourceVersion(): SourceVersion {
49 | return SourceVersion.latestSupported()
50 | }
51 |
52 | override fun process(
53 | annotations: MutableSet,
54 | roundEnv: RoundEnvironment
55 | ): Boolean {
56 | L.info("[${this.javaClass.simpleName}] process annotations=$annotations, roundEnv=$roundEnv")
57 |
58 | val taskElements: Set? = roundEnv.getElementsAnnotatedWith(AppInitTask::class.java)
59 | if (taskElements == null || taskElements.isEmpty()) {
60 | L.info("[${this.javaClass.simpleName} process] taskElements is empty.")
61 | return false
62 | }
63 | L.info("[${this.javaClass.simpleName}] Found tasks, size is ${taskElements.size}")
64 |
65 | val taskType = mElementUtils.getTypeElement(Utils.TASK_INTERFACE_NAME)
66 |
67 | /**
68 | * Param type: MutableSet
69 | *
70 | * There's no such type as MutableSet at runtime so the library only sees the runtime type.
71 | * If you need MutableSet then you'll need to use a ClassName to create it.
72 | * [https://github.com/square/kotlinpoet/issues/482]
73 | */
74 | // MutableList
75 | val inputMapTypeName =
76 | ClassName(
77 | "kotlin.collections",
78 | "MutableSet"
79 | ).parameterizedBy(AppInitTaskInfo::class.asTypeName())
80 |
81 | // taskInfoList: MutableSet
82 | val paramSpec =
83 | ParameterSpec.builder(Utils.PARAM_NAME, inputMapTypeName).build()
84 |
85 | // override fun register(taskInfoList: MutableSet)
86 | val functionBuilder = FunSpec.builder(Utils.METHOD_NAME)
87 | .addModifiers(KModifier.OVERRIDE)
88 | .addParameter(paramSpec)
89 |
90 | for (ele in taskElements) {
91 | val typeMirror = ele.asType()
92 | val task = ele.getAnnotation(AppInitTask::class.java)
93 | if (mTypeUtils.isSubtype(typeMirror, taskType.asType())) {
94 | L.info("[${this.javaClass.simpleName}] Found task: $typeMirror")
95 |
96 | val taskCn = (ele as TypeElement).asClassName()
97 |
98 | // taskInfoList.add(AppInitTaskInfo("main1", false, 0, arrayOf("PROCESS_MAIN"), arrayOf("lib1"), MainAppInit()))
99 | functionBuilder.addStatement(
100 | "%N.add(%T(%S, %L, %L, %L, %L, %T()))",
101 | Utils.PARAM_NAME,
102 | AppInitTaskInfo::class.java,
103 | task.id,
104 | task.background,
105 | task.priority,
106 | Utils.formatArray(task.process),
107 | Utils.formatArray(task.dependencies),
108 | taskCn
109 | )
110 | }
111 | }
112 |
113 | /**
114 | * Write to file
115 | */
116 | FileSpec.builder(Utils.PACKAGE_NAME, "AppInitTaskRegister\$$moduleName")
117 | .addType(
118 | TypeSpec.classBuilder("AppInitTaskRegister\$$moduleName")
119 | .addKdoc(Utils.JAVADOC)
120 | .addSuperinterface(AppInitTaskRegister::class.java)
121 | .addFunction(functionBuilder.build())
122 | .build()
123 | )
124 | .build()
125 | .writeTo(mFiler)
126 |
127 | // /**
128 | // * DO NOT EDIT THIS FILE! IT WAS GENERATED BY INIT.
129 | // */
130 | // public class `AppInitTaskRegister$app` : AppInitTaskRegister {
131 | // public override fun register(taskInfoList: MutableSet): Unit {
132 | // taskInfoList.add(AppInitTaskInfo("main1", false, 0, arrayOf("PROCESS_MAIN"), arrayOf("lib1"),
133 | // MainAppInit()))
134 | // }
135 | // }
136 |
137 | return true
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/appinit-compiler/src/main/java/me/hacket/appinit/compiler/L.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.compiler
2 |
3 | import java.io.PrintWriter
4 | import java.io.StringWriter
5 | import java.net.UnknownHostException
6 | import javax.annotation.processing.Messager
7 | import javax.tools.Diagnostic
8 |
9 | object L {
10 | var sMessenger: Messager? = null
11 |
12 | fun setMessager(messager: Messager?) {
13 | sMessenger = messager
14 | }
15 |
16 | fun info(msg: String) {
17 | sMessenger?.printMessage(Diagnostic.Kind.NOTE, Utils.TAG + msg + "\n")
18 | }
19 |
20 | fun warn(msg: String) {
21 | sMessenger?.printMessage(Diagnostic.Kind.WARNING, Utils.TAG + msg + "\n")
22 | }
23 |
24 | fun error(msg: String) {
25 | sMessenger?.printMessage(Diagnostic.Kind.ERROR, Utils.TAG + msg + "\n")
26 | }
27 |
28 | fun error(msg: String, tr: Throwable) {
29 | sMessenger?.printMessage(
30 | Diagnostic.Kind.ERROR,
31 | Utils.TAG + msg + "\n" + getStackTraceString(tr) + "\n"
32 | )
33 | }
34 |
35 | private fun getStackTraceString(tr: Throwable?): String {
36 | if (tr == null) {
37 | return ""
38 | }
39 |
40 | // This is to reduce the amount of log spew that apps do in the non-error
41 | // condition of the network being unavailable.
42 | var t = tr
43 | while (t != null) {
44 | if (t is UnknownHostException) {
45 | return ""
46 | }
47 | t = t.cause
48 | }
49 |
50 | val sw = StringWriter()
51 | val pw = PrintWriter(sw)
52 | tr.printStackTrace(pw)
53 | pw.flush()
54 | return sw.toString()
55 | }
56 | }
--------------------------------------------------------------------------------
/appinit-compiler/src/main/java/me/hacket/appinit/compiler/Utils.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.compiler
2 |
3 | object Utils {
4 | private const val SDK_NAME = "AppInit"
5 | const val TAG = "-------------------->>> $SDK_NAME:: "
6 |
7 | const val MODULE_PARAM_NAME = "APPINIT_MODULE_NAME"
8 |
9 | const val PARAM_NAME = "taskInfoList"
10 | const val METHOD_NAME = "register"
11 | const val PACKAGE_NAME = "me.hacket.appinit.apt.taskregister"
12 | const val TASK_INTERFACE_NAME = "me.hacket.appinit.api.IAppInitTask"
13 |
14 | const val JAVADOC = "DO NOT EDIT THIS FILE! IT WAS GENERATED BY appinit-compiler.\n"
15 |
16 | const val NO_MODULE_NAME_TIPS_KOTLIN =
17 | "These no module name, at your module 'build.gradle', like :\n" +
18 | " kapt {\n" +
19 | " arguments {\n" +
20 | " arg(\"${MODULE_PARAM_NAME}\", project.name)\n" +
21 | " }\n" +
22 | " }\n"
23 |
24 | const val NO_MODULE_NAME_TIPS_JAVA = "These no module name, at 'build.gradle', like :\n" +
25 | "android {\n" +
26 | " defaultConfig {\n" +
27 | " ...\n" +
28 | " javaCompileOptions {\n" +
29 | " annotationProcessorOptions {\n" +
30 | " arguments = [\"${MODULE_PARAM_NAME}\": project.getName()]\n" +
31 | " }\n" +
32 | " }\n" +
33 | " }\n" +
34 | "}\n"
35 |
36 | fun formatModuleName(moduleName: String): String {
37 | return moduleName.replace('-', '_')
38 | }
39 |
40 | fun formatArray(array: Array): String {
41 | val sb = StringBuilder()
42 | array.forEach {
43 | sb.append("\"$it\"").append(",")
44 | }
45 | if (sb.isNotEmpty()) {
46 | sb.deleteAt(sb.length - 1)
47 | }
48 | return "arrayOf($sb)"
49 | }
50 | }
--------------------------------------------------------------------------------
/appinit-compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors:
--------------------------------------------------------------------------------
1 | me.hacket.appinit.compiler.AppInitTaskProcessor,aggregating
--------------------------------------------------------------------------------
/appinit-compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor:
--------------------------------------------------------------------------------
1 | me.hacket.appinit.compiler.AppInitTaskProcessor
--------------------------------------------------------------------------------
/appinit-startup/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/appinit-startup/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk 31
8 |
9 | defaultConfig {
10 | minSdk 21
11 | targetSdk 31
12 |
13 | consumerProguardFiles "consumer-rules.pro"
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_8
24 | targetCompatibility JavaVersion.VERSION_1_8
25 | }
26 | kotlinOptions {
27 | jvmTarget = '1.8'
28 | }
29 | }
30 |
31 | dependencies {
32 | compileOnly 'androidx.startup:startup-runtime:1.1.1'
33 | api project(':appinit-api')
34 | }
--------------------------------------------------------------------------------
/appinit-startup/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/appinit-startup/consumer-rules.pro
--------------------------------------------------------------------------------
/appinit-startup/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/appinit-startup/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
13 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/appinit-startup/src/main/java/me/hacket/appinit/startup/AppInitInitializer.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.appinit.startup
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.util.Log
6 | import androidx.startup.Initializer
7 | import me.hacket.appinit.api.AppInit
8 | import me.hacket.appinit.api.utils.Consts
9 |
10 | class AppInitInitializer : Initializer {
11 | override fun create(context: Context): Boolean {
12 | Log.d(Consts.TAG, "AppInitInitializer onCreate")
13 | AppInit.init(context as Application, onTaskComplete = {
14 | Log.i(Consts.TAG, "AppInitInitializer task complete: $it")
15 | }) {
16 | Log.i(Consts.TAG, "AppInitInitializer all task complete")
17 | }
18 | return true
19 | }
20 |
21 | override fun dependencies(): List>> {
22 | return emptyList()
23 | }
24 | }
--------------------------------------------------------------------------------
/archives_publish.gradle:
--------------------------------------------------------------------------------
1 | def publishLibraries = ['appinit-api', 'appinit-annotation', 'appinit-compiler', 'appinit-compiler-ksp','appinit-auto-register', 'appinit-startup']
2 |
3 | subprojects { Project subProject ->
4 | if (publishLibraries.contains(subProject.name)) {
5 | apply plugin: 'com.vanniktech.maven.publish'
6 | }
7 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | dependencies {
7 | classpath "com.android.tools.build:gradle:7.1.2"
8 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20"
9 |
10 | classpath 'com.vanniktech:gradle-maven-publish-plugin:0.22.0'
11 | classpath "io.github.hacket:appinit-auto-register:1.0.0"
12 | }
13 | }
14 | plugins {
15 | // id 'org.jetbrains.kotlin.jvm' version '1.7.20' apply false
16 | id 'com.google.devtools.ksp' version '1.7.20-1.0.7' apply(false)
17 | }
18 |
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | mavenCentral()
24 | }
25 | }
26 |
27 | apply from: "./archives_publish.gradle"
--------------------------------------------------------------------------------
/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=-Xmx2048m -Dfile.encoding=UTF-8
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 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
25 | ### Gradle HTTP/HTTPS
26 | #systemProp.http.proxyHost=127.0.0.1
27 | #systemProp.http.proxyPort=2080
28 | #systemProp.https.proxyHost=127.0.0.1
29 | #systemProp.https.proxyPort=2080
30 | #
31 | ## Gradle Socket
32 | #systemProp.socks.proxyHost=127.0.0.1
33 | #systemProp.socks.proxyPort=2080
34 |
35 |
36 | #SONATYPE_HOST=DEFAULT
37 | ## or when publishing to https://s01.oss.sonatype.org
38 | SONATYPE_HOST=S01
39 |
40 | RELEASE_SIGNING_ENABLED=true
41 |
42 | GROUP=io.github.hacket
43 | #POM_ARTIFACT_ID=appInit
44 | VERSION_NAME=1.0.1
45 |
46 | POM_NAME=AppInit
47 | POM_DESCRIPTION=A small library for Android startup init task.
48 | POM_INCEPTION_YEAR=2021
49 |
50 | POM_URL=https://github.com/hacket/AppInit
51 | POM_SCM_URL=https://github.com/hacket/AppInit
52 | POM_SCM_CONNECTION=scm:git:git://github.com/hacket/AppInit
53 | POM_SCM_DEV_CONNECTION=scm:git:git://github.com/hacket/AppInit
54 |
55 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
56 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
57 | POM_LICENCE_DIST=repo
58 |
59 | POM_DEVELOPER_ID=hacket
60 | POM_DEVELOPER_NAME=hacket
61 | POM_DEVELOPER_URL=https://github.com/hacket/
62 |
63 |
64 | android.disableAutomaticComponentCreation=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 15 08:52:08 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/gradlew
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/mylibrary1/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/mylibrary1/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | // id 'kotlin-kapt'
5 | id 'com.google.devtools.ksp'
6 | }
7 |
8 | android {
9 | namespace 'me.hacket.appinit.demo1'
10 | compileSdk 31
11 |
12 | defaultConfig {
13 | minSdk 21
14 | targetSdk 31
15 |
16 | consumerProguardFiles "consumer-rules.pro"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = '1.8'
31 | }
32 | }
33 |
34 | //kapt {
35 | // arguments {
36 | // arg("APPINIT_MODULE_NAME", project.name)
37 | // }
38 | //}
39 | ksp {
40 | arg("APPINIT_MODULE_NAME", project.name)
41 | }
42 | dependencies {
43 | implementation project(':appinit-api')
44 | ksp project(':appinit-compiler-ksp')
45 |
46 | // implementation 'io.github.hacket:appinit-api:1.0.0'
47 | // ksp 'io.github.hacket:appinit-compiler:1.0.0'
48 | }
--------------------------------------------------------------------------------
/mylibrary1/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/mylibrary1/consumer-rules.pro
--------------------------------------------------------------------------------
/mylibrary1/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/mylibrary1/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/mylibrary1/src/main/java/me/hacket/mylibrary1/MyLib1InitTask.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.mylibrary1
2 |
3 | import android.app.Application
4 | import android.content.res.Configuration
5 | import android.os.SystemClock
6 | import me.hacket.appinit.annotation.AppInitTask
7 | import me.hacket.appinit.api.AppInit
8 | import me.hacket.appinit.api.IAppInitTask
9 | import me.hacket.appinit.api.utils.Consts
10 |
11 | @AppInitTask(
12 | id = "myLib1",
13 | background = true,
14 | // process = [AppInitTask.PROCESS_NOT_MAIN],
15 | priority = AppInitTask.PRIORITY_HIGH,
16 | dependencies = []
17 | )
18 | class MyLib1InitTask : IAppInitTask {
19 | override fun onCreate(application: Application) {
20 | AppInit.logger().info(Consts.TAG, "myLib1 execute start, sleep 4000L.")
21 | SystemClock.sleep(4000L)
22 | AppInit.logger().info(Consts.TAG, "myLib1 execute end.")
23 | }
24 |
25 | override fun onConfigurationChanged(application: Application, newConfig: Configuration) {
26 | super.onConfigurationChanged(application, newConfig)
27 | AppInit.logger().debug(Consts.TAG, "myLib1 onConfigurationChanged:$newConfig")
28 | }
29 | }
--------------------------------------------------------------------------------
/mylibrary2/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/mylibrary2/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'com.google.devtools.ksp'
5 | }
6 | ksp {
7 | arg("APPINIT_MODULE_NAME", project.name)
8 | }
9 | android {
10 | namespace 'me.hacket.appinit.demo2'
11 | compileSdk 31
12 |
13 | defaultConfig {
14 | minSdk 21
15 | targetSdk 31
16 |
17 | consumerProguardFiles "consumer-rules.pro"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation project(':appinit-api')
37 | ksp project(':appinit-compiler-ksp')
38 | }
--------------------------------------------------------------------------------
/mylibrary2/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/mylibrary2/consumer-rules.pro
--------------------------------------------------------------------------------
/mylibrary2/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/mylibrary2/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/mylibrary2/src/main/java/me/hacket/mylibrary2/MyLib2InitTask.kt:
--------------------------------------------------------------------------------
1 | package me.hacket.mylibrary2
2 |
3 | import android.app.Application
4 | import android.content.res.Configuration
5 | import android.os.SystemClock
6 | import me.hacket.appinit.annotation.AppInitTask
7 | import me.hacket.appinit.api.AppInit
8 | import me.hacket.appinit.api.IAppInitTask
9 | import me.hacket.appinit.api.utils.Consts
10 |
11 | @AppInitTask(
12 | id = "myLib2",
13 | background = false,
14 | // process = [AppInitTask.PROCESS_NOT_MAIN],
15 | priority = AppInitTask.PRIORITY_MAX,
16 | dependencies = []
17 | )
18 | class MyLib2InitTask : IAppInitTask {
19 | override fun onCreate(application: Application) {
20 | AppInit.logger().info(Consts.TAG, "myLib2 execute start, sleep 2000L.")
21 | SystemClock.sleep(2000L)
22 | AppInit.logger().info(Consts.TAG, "myLib2 execute end.")
23 | }
24 |
25 | override fun onConfigurationChanged(application: Application, newConfig: Configuration) {
26 | super.onConfigurationChanged(application, newConfig)
27 | AppInit.logger().debug(Consts.TAG, "myLib2 onConfigurationChanged:$newConfig")
28 | }
29 | }
--------------------------------------------------------------------------------
/secret.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacket/AppInit/5ef8f147b4d1598e1486a5f19a7104e1b2a36b5d/secret.gpg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | //rootProject.name = "AppInit"
2 | include ':app'
3 | include ':appinit-annotation'
4 | include ':appinit-compiler'
5 | include ':appinit-api'
6 | include ':mylibrary1'
7 | include ':appinit-auto-register'
8 | include ':appinit-startup'
9 | include ':appinit-compiler-ksp'
10 | include ':mylibrary2'
11 |
--------------------------------------------------------------------------------