├── .gitignore
├── LICENSE
├── README.md
└── android-modular
├── .gitignore
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── jeremyliao
│ │ └── android
│ │ └── modular
│ │ └── app
│ │ └── MainActivity.java
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── base_config.gradle
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── modular-demo
├── common
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── jeremyliao
│ │ │ └── lib_common
│ │ │ └── BaseApplication.java
│ │ └── res
│ │ ├── drawable
│ │ └── common_launcher.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
├── module-a-interface
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── jeremyliao
│ │ │ └── modulea
│ │ │ └── export
│ │ │ └── ModuleAInterface.java
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── module-a
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── jeremyliao
│ │ │ └── modulea
│ │ │ ├── ModuleAActivity.java
│ │ │ └── ModuleAInterfaceImpl.java
│ │ └── res
│ │ ├── layout
│ │ ├── activity_module_a.xml
│ │ └── activity_module_shell.xml
│ │ └── values
│ │ └── strings.xml
├── module-b-interface
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── jeremyliao
│ │ │ └── moduleb
│ │ │ └── export
│ │ │ ├── ModuleBInterface.java
│ │ │ └── event
│ │ │ └── ModuleBEvent.java
│ │ └── res
│ │ └── values
│ │ └── strings.xml
└── module-b
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── jeremyliao
│ │ └── moduleb
│ │ ├── ModuleBActivity.java
│ │ └── ModuleBInterfaceImpl.java
│ └── res
│ ├── layout
│ ├── activity_module_b.xml
│ └── activity_module_shell.xml
│ └── values
│ └── strings.xml
├── modular-tool
├── modular-base
│ ├── .gitignore
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── jeremyliao
│ │ └── base
│ │ ├── anotation
│ │ └── ModuleService.java
│ │ ├── base
│ │ └── IInterface.java
│ │ └── inner
│ │ ├── bean
│ │ └── ModuleServiceInfo.java
│ │ └── utils
│ │ └── GsonUtil.java
├── modular-compiler
│ ├── .gitignore
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── jeremyliao
│ │ └── compiler
│ │ └── ModularProcessor.java
├── modular-manager
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── jeremyliao
│ │ │ └── modular
│ │ │ ├── ModuleRpcManager.java
│ │ │ ├── exception
│ │ │ ├── InterfaceNotFoundException.java
│ │ │ └── ModuleNotFoundException.java
│ │ │ └── utils
│ │ │ ├── AppUtils.java
│ │ │ ├── Reflect.java
│ │ │ └── ReflectException.java
│ │ └── res
│ │ └── values
│ │ └── strings.xml
└── modular-plugin
│ ├── .gitignore
│ ├── build.gradle
│ └── src
│ └── main
│ ├── groovy
│ └── com
│ │ └── jeremyliao
│ │ └── plugin
│ │ ├── ModularPlugin.groovy
│ │ └── ModularTransform.groovy
│ ├── java
│ └── com
│ │ └── jeremyliao
│ │ └── asm
│ │ └── ModularManagerVisitor.java
│ └── resources
│ └── META-INF
│ └── gradle-plugins
│ └── modular-plugin.properties
├── settings.gradle
└── upload.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # IntelliJ
36 | *.iml
37 | .idea/workspace.xml
38 | .idea/tasks.xml
39 | .idea/gradle.xml
40 | .idea/assetWizardSettings.xml
41 | .idea/dictionaries
42 | .idea/libraries
43 | .idea/caches
44 |
45 | # Keystore files
46 | # Uncomment the following line if you do not want to check your keystore files in.
47 | #*.jks
48 |
49 | # External native build folder generated in Android Studio 2.2 and later
50 | .externalNativeBuild
51 |
52 | # Google Services (e.g. APIs or Firebase)
53 | google-services.json
54 |
55 | # Freeline
56 | freeline.py
57 | freeline/
58 | freeline_project_description.json
59 |
60 | # fastlane
61 | fastlane/report.xml
62 | fastlane/Preview.html
63 | fastlane/screenshots
64 | fastlane/test_output
65 | fastlane/readme.md
66 |
67 | release
68 | target
69 | build
70 | .settings
71 | .project
72 | .classpath
73 | .idea
74 | .DS_Store
75 | bin
76 | gen
77 | proguard
78 | .pmd
79 | *~
80 | *.iml
81 | tmp
82 | gen-external-apklibs
83 | out
84 | tmp
85 | coverage
86 | build/
87 | .gradle/
88 | local.properties
89 | .gradletasknamecache
90 | src/main/provided_libs
91 | src/main/assets/*.dex
92 | src/main/assets/mtdata*.json
93 | reports/
94 | aartojar/
95 | keystore
96 | captures
97 |
--------------------------------------------------------------------------------
/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 | # android-modular
2 | 一套Android组件化的实施方案和支撑框架
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 | public class ModuleBEvent implements IModularEvent {
29 | final public String content;
30 |
31 | public ModuleBEvent(String content) {
32 | this.content = content;
33 | }
34 | }
35 | ```
36 | #### 接口定义
37 |
38 | ```
39 | public interface ModuleBInterface extends IInterface {
40 | void launchModuleBMainPage(Context context);
41 | }
42 | ```
43 | ### 组件实现
44 | 用注解@ModuleService制定实现的接口
45 |
46 | ```
47 | @ModuleService(interfaceDefine = ModuleBInterface.class)
48 | public class ModuleBInterfaceImpl implements ModuleBInterface {
49 | @Override
50 | public void launchModuleBMainPage(Context context) {
51 | if (context == null) {
52 | return;
53 | }
54 | context.startActivity(new Intent(context, ModuleBActivity.class));
55 | }
56 | }
57 | ```
58 | ## 组件间通信
59 | #### 组件间接口调用
60 |
61 | ```
62 | userName = ModuleRpcManager.get()
63 | .call(ModuleAInterface.class)
64 | .getUserName();
65 | ```
66 | #### 组件间消息
67 | ##### 监听消息
68 |
69 | ```
70 | ModularBus.toObservable(ModuleBEvent.class)
71 | .observe(this, moduleBEvent ->
72 | Toast.makeText(ModuleAActivity.this,
73 | moduleBEvent != null ? moduleBEvent.content : "",
74 | Toast.LENGTH_SHORT).show());
75 | ```
76 |
77 | ##### 发送消息
78 |
79 | ```
80 | ModularBus.toObservable(ModuleBEvent.class).post(new ModuleBEvent("hello world"));
81 | ```
82 | ## 使用组件通信框架
83 | ### 使用组件接口调用框架
84 | ##### 配置classpath
85 | ```
86 | classpath "com.jeremyliao.modular-tools:plugin:0.0.1"
87 | ```
88 | ##### 在组件中处理注解
89 | ```
90 | annotationProcessor "com.jeremyliao.modular-tools:processor:0.0.1"
91 | ```
92 | ##### 在壳工程中使用插件
93 | ```
94 | apply plugin: 'modular-plugin'
95 | ```
96 | ##### 运行时使用
97 |
98 | ```
99 | implementation "com.jeremyliao.modular-tools:manager:0.0.1"
100 | ```
101 |
102 | ### 使用组件消息总线框架
103 | ```
104 | implementation 'com.jeremyliao:live-event-bus:1.7.2'
105 | ```
106 |
--------------------------------------------------------------------------------
/android-modular/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 | repo/
12 |
--------------------------------------------------------------------------------
/android-modular/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android-modular/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'modular-plugin'
3 | apply from: "${project.rootDir}/base_config.gradle"
4 | android {
5 | compileSdkVersion 28
6 |
7 | defaultConfig {
8 | applicationId "com.jeremyliao.android.modular.app"
9 | minSdkVersion 15
10 | targetSdkVersion 28
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | }
25 |
26 | dependencies {
27 | implementation fileTree(dir: 'libs', include: ['*.jar'])
28 |
29 | implementation project(':modular-demo:module-a-interface')
30 | implementation project(':modular-demo:module-b-interface')
31 |
32 | implementation project(':modular-demo:module-a')
33 | implementation project(':modular-demo:module-b')
34 | }
35 |
--------------------------------------------------------------------------------
/android-modular/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/android-modular/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
Reflect
is
20 | * // Static import all reflection methods to decrease verbosity
21 | * import static org.joor.Reflect.*;
22 | *
23 | * // Wrap an Object / Class / class name with the on() method:
24 | * on("java.lang.String")
25 | * // Invoke constructors using the create() method:
26 | * .create("Hello World")
27 | * // Invoke methods using the call() method:
28 | * .call("toString")
29 | * // Retrieve the wrapped object
30 | */
31 | public final class Reflect {
32 |
33 | /**
34 | * 获取外部类的引用
35 | */
36 | public T getOuterObject() throws ReflectException {
37 | return get("this$0");
38 | }
39 |
40 | // ---------------------------------------------------------------------
41 | // Static API used as entrance points to the fluent API
42 | // ---------------------------------------------------------------------
43 |
44 | /**
45 | * Wrap a class name.
46 | *
47 | * This is the same as calling on(Class.forName(name))
48 | *
49 | * @param name A fully qualified class name
50 | * @return A wrapped class object, to be used for further reflection.
51 | * @throws ReflectException If any reflection exception occurred.
52 | * @see #on(Class)
53 | */
54 | public static Reflect on(String name) throws ReflectException {
55 | return on(forName(name));
56 | }
57 |
58 | /**
59 | * Wrap a class name, loading it via a given class loader.
60 | *
61 | * This is the same as calling
62 | * on(Class.forName(name, classLoader))
63 | *
64 | * @param name A fully qualified class name.
65 | * @param classLoader The class loader in whose context the class should be
66 | * loaded.
67 | * @return A wrapped class object, to be used for further reflection.
68 | * @throws ReflectException If any reflection exception occurred.
69 | * @see #on(Class)
70 | */
71 | public static Reflect on(String name, ClassLoader classLoader) throws ReflectException {
72 | return on(forName(name, classLoader));
73 | }
74 |
75 | /**
76 | * Wrap a class.
77 | *
78 | * Use this when you want to access static fields and methods on a
79 | * {@link Class} object, or as a basis for constructing objects of that
80 | * class using {@link #create(Object...)}
81 | *
82 | * @param clazz The class to be wrapped
83 | * @return A wrapped class object, to be used for further reflection.
84 | */
85 | public static Reflect on(Class> clazz) {
86 | return new Reflect(clazz);
87 | }
88 |
89 | /**
90 | * Wrap an object.
91 | *
92 | * Use this when you want to access instance fields and methods on any
93 | * {@link Object}
94 | *
95 | * @param object The object to be wrapped
96 | * @return A wrapped object, to be used for further reflection.
97 | */
98 | public static Reflect on(Object object) {
99 | return new Reflect(object);
100 | }
101 |
102 | /**
103 | * Conveniently render an {@link AccessibleObject} accessible.
104 | *
105 | * To prevent {@link SecurityException}, this is only done if the argument
106 | * object and its declaring class are non-public.
107 | *
108 | * @param accessible The object to render accessible
109 | * @return The argument object rendered accessible
110 | */
111 | public static T accessible(T accessible) {
112 | if (accessible == null) {
113 | return null;
114 | }
115 |
116 | if (accessible instanceof Member) {
117 | Member member = (Member) accessible;
118 |
119 | if (Modifier.isPublic(member.getModifiers()) &&
120 | Modifier.isPublic(member.getDeclaringClass().getModifiers())) {
121 |
122 | return accessible;
123 | }
124 | }
125 |
126 | // [jOOQ #3392] The accessible flag is set to false by default, also for public members.
127 | if (!accessible.isAccessible()) {
128 | accessible.setAccessible(true);
129 | }
130 |
131 | return accessible;
132 | }
133 |
134 | // ---------------------------------------------------------------------
135 | // Members
136 | // ---------------------------------------------------------------------
137 |
138 | /**
139 | * The wrapped object
140 | */
141 | private final Object object;
142 |
143 | /**
144 | * A flag indicating whether the wrapped object is a {@link Class} (for
145 | * accessing static fields and methods), or any other type of {@link Object}
146 | * (for accessing instance fields and methods).
147 | */
148 | private final boolean isClass;
149 |
150 | // ---------------------------------------------------------------------
151 | // Constructors
152 | // ---------------------------------------------------------------------
153 |
154 | private Reflect(Class> type) {
155 | this.object = type;
156 | this.isClass = true;
157 | }
158 |
159 | private Reflect(Object object) {
160 | this.object = object;
161 | this.isClass = false;
162 | }
163 |
164 | // ---------------------------------------------------------------------
165 | // Fluent Reflection API
166 | // ---------------------------------------------------------------------
167 |
168 | /**
169 | * Get the wrapped object
170 | *
171 | * @param A convenience generic parameter for automatic unsafe casting
172 | */
173 | @SuppressWarnings("unchecked")
174 | public T get() {
175 | return (T) object;
176 | }
177 |
178 | /**
179 | * Set a field value.
180 | *
181 | * This is roughly equivalent to {@link Field#set(Object, Object)}. If the
182 | * wrapped object is a {@link Class}, then this will set a value to a static
183 | * member field. If the wrapped object is any other {@link Object}, then
184 | * this will set a value to an instance member field.
185 | *
186 | * @param name The field name
187 | * @param value The new field value
188 | * @return The same wrapped object, to be used for further reflection.
189 | * @throws ReflectException If any reflection exception occurred.
190 | */
191 | public Reflect set(String name, Object value) throws ReflectException {
192 | try {
193 | Field field = field0(name);
194 | field.set(object, unwrap(value));
195 | return this;
196 | } catch (Exception e) {
197 | throw new ReflectException(e);
198 | }
199 | }
200 |
201 | /**
202 | * Get a field value.
203 | *
204 | * This is roughly equivalent to {@link Field#get(Object)}. If the wrapped
205 | * object is a {@link Class}, then this will get a value from a static
206 | * member field. If the wrapped object is any other {@link Object}, then
207 | * this will get a value from an instance member field.
208 | *
209 | * If you want to "navigate" to a wrapped version of the field, use
210 | * {@link #field(String)} instead.
211 | *
212 | * @param name The field name
213 | * @return The field value
214 | * @throws ReflectException If any reflection exception occurred.
215 | * @see #field(String)
216 | */
217 | public T get(String name) throws ReflectException {
218 | return field(name).get();
219 | }
220 |
221 | /**
222 | * Get a wrapped field.
223 | *
224 | * This is roughly equivalent to {@link Field#get(Object)}. If the wrapped
225 | * object is a {@link Class}, then this will wrap a static member field. If
226 | * the wrapped object is any other {@link Object}, then this wrap an
227 | * instance member field.
228 | *
229 | * @param name The field name
230 | * @return The wrapped field
231 | * @throws ReflectException If any reflection exception occurred.
232 | */
233 | public Reflect field(String name) throws ReflectException {
234 | try {
235 | Field field = field0(name);
236 | return on(field.get(object));
237 | } catch (Exception e) {
238 | throw new ReflectException(e);
239 | }
240 | }
241 |
242 | private Field field0(String name) throws ReflectException {
243 | Class> type = type();
244 |
245 | // Try getting a public field
246 | try {
247 | return type.getField(name);
248 | } catch (NoSuchFieldException e) {
249 | do {
250 | try {
251 | return accessible(type.getDeclaredField(name));
252 | } catch (NoSuchFieldException ignore) {
253 | }
254 |
255 | type = type.getSuperclass();
256 | }
257 | while (type != null);
258 |
259 | throw new ReflectException(e);
260 | }
261 | }
262 |
263 | /**
264 | * Get a Map containing field names and wrapped values for the fields'
265 | * values.
266 | *
267 | * If the wrapped object is a {@link Class}, then this will return static
268 | * fields. If the wrapped object is any other {@link Object}, then this will
269 | * return instance fields.
270 | *
271 | * These two calls are equivalent
272 | * on(object).field("myField");
273 | * on(object).fields().get("myField");
274 | *
275 | *
276 | * @return A map containing field names and wrapped values.
277 | */
278 | public Map fields() throws ReflectException {
279 | Map result = new LinkedHashMap();
280 | Class> type = type();
281 |
282 | do {
283 | for (Field field : type.getDeclaredFields()) {
284 | if (!isClass ^ Modifier.isStatic(field.getModifiers())) {
285 | String name = field.getName();
286 |
287 | if (!result.containsKey(name)) {
288 | result.put(name, field(name));
289 | }
290 | }
291 | }
292 |
293 | type = type.getSuperclass();
294 | } while (type != null);
295 |
296 | return result;
297 | }
298 |
299 | /**
300 | * Call a method by its name.
301 | *
302 | * This is a convenience method for calling
303 | * call(name, new Object[0])
304 | *
305 | * @param name The method name
306 | * @return The wrapped method result or the same wrapped object if the
307 | * method returns void
, to be used for further
308 | * reflection.
309 | * @throws ReflectException If any reflection exception occurred.
310 | * @see #call(String, Object...)
311 | */
312 | public Reflect call(String name) throws ReflectException {
313 | return call(name, new Object[0]);
314 | }
315 |
316 | /**
317 | * Call a method by its name.
318 | *
319 | * This is roughly equivalent to {@link Method#invoke(Object, Object...)}.
320 | * If the wrapped object is a {@link Class}, then this will invoke a static
321 | * method. If the wrapped object is any other {@link Object}, then this will
322 | * invoke an instance method.
323 | *
324 | * Just like {@link Method#invoke(Object, Object...)}, this will try to wrap
325 | * primitive types or unwrap primitive type wrappers if applicable. If
326 | * several methods are applicable, by that rule, the first one encountered
327 | * is called. i.e. when calling
328 | * on(...).call("method", 1, 1);
329 | *
The first of the following methods will be called:
330 | *
331 | * public void method(int param1, Integer param2);
332 | * public void method(Integer param1, int param2);
333 | * public void method(Number param1, Number param2);
334 | * public void method(Number param1, Object param2);
335 | * public void method(int param1, Object param2);
336 | *
337 | *
338 | * The best matching method is searched for with the following strategy:
339 | *
340 | * - public method with exact signature match in class hierarchy
341 | * - non-public method with exact signature match on declaring class
342 | * - public method with similar signature in class hierarchy
343 | * - non-public method with similar signature on declaring class
344 | *
345 | *
346 | * @param name The method name
347 | * @param args The method arguments
348 | * @return The wrapped method result or the same wrapped object if the
349 | * method returns void
, to be used for further
350 | * reflection.
351 | * @throws ReflectException If any reflection exception occurred.
352 | */
353 | public Reflect call(String name, Object... args) throws ReflectException {
354 | Class>[] types = types(args);
355 |
356 | // Try invoking the "canonical" method, i.e. the one with exact
357 | // matching argument types
358 | try {
359 | Method method = exactMethod(name, types);
360 | return on(method, object, args);
361 | } catch (NoSuchMethodException e) {
362 | try {
363 | Method method = similarMethod(name, types);
364 | return on(method, object, args);
365 | } catch (NoSuchMethodException e1) {
366 | throw new ReflectException(e1);
367 | }
368 | }
369 | }
370 |
371 | /**
372 | * Searches a method with the exact same signature as desired.
373 | *
374 | * If a public method is found in the class hierarchy, this method is returned.
375 | * Otherwise a private method with the exact same signature is returned.
376 | * If no exact match could be found, we let the {@code NoSuchMethodException} pass through.
377 | */
378 | private Method exactMethod(String name, Class>[] types) throws NoSuchMethodException {
379 | Class> type = type();
380 |
381 | // first priority: find a public method with exact signature match in class hierarchy
382 | try {
383 | return type.getMethod(name, types);
384 | } catch (NoSuchMethodException e) {
385 | do {
386 | try {
387 | return type.getDeclaredMethod(name, types);
388 | } catch (NoSuchMethodException ignore) {
389 | }
390 |
391 | type = type.getSuperclass();
392 | } while (type != null);
393 |
394 | throw new NoSuchMethodException();
395 | }
396 | }
397 |
398 | /**
399 | * Searches a method with a similar signature as desired using
400 | * {@link #isSimilarSignature(Method, String, Class[])}.
401 | *
402 | * First public methods are searched in the class hierarchy, then private
403 | * methods on the declaring class. If a method could be found, it is
404 | * returned, otherwise a {@code NoSuchMethodException} is thrown.
405 | */
406 | private Method similarMethod(String name, Class>[] types) throws NoSuchMethodException {
407 | Class> type = type();
408 |
409 | // first priority: find a public method with a "similar" signature in class hierarchy
410 | // similar interpreted in when primitive argument types are converted to their wrappers
411 | for (Method method : type.getMethods()) {
412 | if (isSimilarSignature(method, name, types)) {
413 | return method;
414 | }
415 | }
416 |
417 | // second priority: find a non-public method with a "similar" signature on declaring class
418 | do {
419 | for (Method method : type.getDeclaredMethods()) {
420 | if (isSimilarSignature(method, name, types)) {
421 | return method;
422 | }
423 | }
424 |
425 | type = type.getSuperclass();
426 | }
427 | while (type != null);
428 |
429 | throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays
430 | .toString(types) + " could be found on type " + type() + ".");
431 | }
432 |
433 | /**
434 | * Determines if a method has a "similar" signature, especially if wrapping
435 | * primitive argument types would result in an exactly matching signature.
436 | */
437 | private boolean isSimilarSignature(Method possiblyMatchingMethod, String desiredMethodName,
438 | Class>[] desiredParamTypes) {
439 | return possiblyMatchingMethod.getName().equals(desiredMethodName) && match
440 | (possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);
441 | }
442 |
443 | /**
444 | * Call a constructor.
445 | *
446 | * This is a convenience method for calling
447 | * create(new Object[0])
448 | *
449 | * @return The wrapped new object, to be used for further reflection.
450 | * @throws ReflectException If any reflection exception occurred.
451 | * @see #create(Object...)
452 | */
453 | public Reflect create() throws ReflectException {
454 | return create(new Object[0]);
455 | }
456 |
457 | /**
458 | * Call a constructor.
459 | *
460 | * This is roughly equivalent to {@link Constructor#newInstance(Object...)}.
461 | * If the wrapped object is a {@link Class}, then this will create a new
462 | * object of that class. If the wrapped object is any other {@link Object},
463 | * then this will create a new object of the same type.
464 | *
465 | * Just like {@link Constructor#newInstance(Object...)}, this will try to
466 | * wrap primitive types or unwrap primitive type wrappers if applicable. If
467 | * several constructors are applicable, by that rule, the first one
468 | * encountered is called. i.e. when calling
469 | * on(C.class).create(1, 1);
470 | *
The first of the following constructors will be applied:
471 | *
472 | * public C(int param1, Integer param2);
473 | * public C(Integer param1, int param2);
474 | * public C(Number param1, Number param2);
475 | * public C(Number param1, Object param2);
476 | * public C(int param1, Object param2);
477 | *
478 | *
479 | * @param args The constructor arguments
480 | * @return The wrapped new object, to be used for further reflection.
481 | * @throws ReflectException If any reflection exception occurred.
482 | */
483 | public Reflect create(Object... args) throws ReflectException {
484 | Class>[] types = types(args);
485 |
486 | // Try invoking the "canonical" constructor, i.e. the one with exact
487 | // matching argument types
488 | try {
489 | Constructor> constructor = type().getDeclaredConstructor(types);
490 | return on(constructor, args);
491 | } catch (NoSuchMethodException e) {
492 | for (Constructor> constructor : type().getDeclaredConstructors()) {
493 | if (match(constructor.getParameterTypes(), types)) {
494 | return on(constructor, args);
495 | }
496 | }
497 |
498 | throw new ReflectException(e);
499 | }
500 | }
501 |
502 | /**
503 | * Create a proxy for the wrapped object allowing to typesafely invoke
504 | * methods on it using a custom interface
505 | *
506 | * @param proxyType The interface type that is implemented by the proxy
507 | * @return A proxy for the wrapped object
508 | */
509 | @SuppressWarnings("unchecked")
510 | public P as(Class
proxyType) {
511 | final boolean isMap = (object instanceof Map);
512 | final InvocationHandler handler = new InvocationHandler() {
513 | @SuppressWarnings("null")
514 | @Override
515 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
516 | String name = method.getName();
517 |
518 | // Actual method name matches always come first
519 | try {
520 | return on(object).call(name, args).get();
521 | } catch (ReflectException e) {
522 | if (isMap) {
523 | Map map = (Map) object;
524 | int length = (args == null ? 0 : args.length);
525 |
526 | if (length == 0 && name.startsWith("get")) {
527 | return map.get(property(name.substring(3)));
528 | } else if (length == 0 && name.startsWith("is")) {
529 | return map.get(property(name.substring(2)));
530 | } else if (length == 1 && name.startsWith("set")) {
531 | map.put(property(name.substring(3)), args[0]);
532 | return null;
533 | }
534 | }
535 |
536 | throw e;
537 | }
538 | }
539 | };
540 |
541 | return (P) Proxy.newProxyInstance(proxyType.getClassLoader(), new Class[]{proxyType},
542 | handler);
543 | }
544 |
545 | /**
546 | * Get the POJO property name of an getter/setter
547 | */
548 | private static String property(String string) {
549 | int length = string.length();
550 |
551 | if (length == 0) {
552 | return "";
553 | } else if (length == 1) {
554 | return string.toLowerCase();
555 | } else {
556 | return string.substring(0, 1).toLowerCase() + string.substring(1);
557 | }
558 | }
559 |
560 | // ---------------------------------------------------------------------
561 | // Object API
562 | // ---------------------------------------------------------------------
563 |
564 | /**
565 | * Check whether two arrays of types match, converting primitive types to
566 | * their corresponding wrappers.
567 | */
568 | private boolean match(Class>[] declaredTypes, Class>[] actualTypes) {
569 | if (declaredTypes.length == actualTypes.length) {
570 | for (int i = 0; i < actualTypes.length; i++) {
571 | if (actualTypes[i] == NULL.class) {
572 | continue;
573 | }
574 |
575 | if (wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) {
576 | continue;
577 | }
578 |
579 | return false;
580 | }
581 |
582 | return true;
583 | } else {
584 | return false;
585 | }
586 | }
587 |
588 | /**
589 | * {@inheritDoc}
590 | */
591 | @Override
592 | public int hashCode() {
593 | return object.hashCode();
594 | }
595 |
596 | /**
597 | * {@inheritDoc}
598 | */
599 | @Override
600 | public boolean equals(Object obj) {
601 | return obj instanceof Reflect && object.equals(((Reflect) obj).get());
602 |
603 | }
604 |
605 | /**
606 | * {@inheritDoc}
607 | */
608 | @Override
609 | public String toString() {
610 | return object.toString();
611 | }
612 |
613 | // ---------------------------------------------------------------------
614 | // Utility methods
615 | // ---------------------------------------------------------------------
616 |
617 | /**
618 | * Wrap an object created from a constructor
619 | */
620 | private static Reflect on(Constructor> constructor, Object... args) throws ReflectException {
621 | try {
622 | return on(accessible(constructor).newInstance(args));
623 | } catch (Exception e) {
624 | throw new ReflectException(e);
625 | }
626 | }
627 |
628 | /**
629 | * Wrap an object returned from a method
630 | */
631 | private static Reflect on(Method method, Object object, Object... args) throws
632 | ReflectException {
633 | try {
634 | accessible(method);
635 |
636 | if (method.getReturnType() == void.class) {
637 | method.invoke(object, args);
638 | return on(object);
639 | } else {
640 | return on(method.invoke(object, args));
641 | }
642 | } catch (Exception e) {
643 | throw new ReflectException(e);
644 | }
645 | }
646 |
647 | /**
648 | * Unwrap an object
649 | */
650 | private static Object unwrap(Object object) {
651 | if (object instanceof Reflect) {
652 | return ((Reflect) object).get();
653 | }
654 |
655 | return object;
656 | }
657 |
658 | /**
659 | * Get an array of types for an array of objects
660 | *
661 | * @see Object#getClass()
662 | */
663 | private static Class>[] types(Object... values) {
664 | if (values == null) {
665 | return new Class[0];
666 | }
667 |
668 | Class>[] result = new Class[values.length];
669 |
670 | for (int i = 0; i < values.length; i++) {
671 | Object value = values[i];
672 | result[i] = value == null ? NULL.class : value.getClass();
673 | }
674 |
675 | return result;
676 | }
677 |
678 | /**
679 | * Load a class
680 | *
681 | * @see Class#forName(String)
682 | */
683 | private static Class> forName(String name) throws ReflectException {
684 | try {
685 | return Class.forName(name);
686 | } catch (Exception e) {
687 | throw new ReflectException(e);
688 | }
689 | }
690 |
691 | private static Class> forName(String name, ClassLoader classLoader) throws ReflectException {
692 | try {
693 | return Class.forName(name, true, classLoader);
694 | } catch (Exception e) {
695 | throw new ReflectException(e);
696 | }
697 | }
698 |
699 | /**
700 | * Get the type of the wrapped object.
701 | *
702 | * @see Object#getClass()
703 | */
704 | public Class> type() {
705 | if (isClass) {
706 | return (Class>) object;
707 | } else {
708 | return object.getClass();
709 | }
710 | }
711 |
712 | /**
713 | * Get a wrapper type for a primitive type, or the argument type itself, if
714 | * it is not a primitive type.
715 | */
716 | public static Class> wrapper(Class> type) {
717 | if (type == null) {
718 | return null;
719 | } else if (type.isPrimitive()) {
720 | if (boolean.class == type) {
721 | return Boolean.class;
722 | } else if (int.class == type) {
723 | return Integer.class;
724 | } else if (long.class == type) {
725 | return Long.class;
726 | } else if (short.class == type) {
727 | return Short.class;
728 | } else if (byte.class == type) {
729 | return Byte.class;
730 | } else if (double.class == type) {
731 | return Double.class;
732 | } else if (float.class == type) {
733 | return Float.class;
734 | } else if (char.class == type) {
735 | return Character.class;
736 | } else if (void.class == type) {
737 | return Void.class;
738 | }
739 | }
740 |
741 | return type;
742 | }
743 |
744 | private static class NULL {
745 | }
746 | }
747 |
--------------------------------------------------------------------------------
/android-modular/modular-tool/modular-manager/src/main/java/com/jeremyliao/modular/utils/ReflectException.java:
--------------------------------------------------------------------------------
1 | package com.jeremyliao.modular.utils;
2 |
3 |
4 | /**
5 | * A unchecked wrapper for any of Java's checked reflection exceptions:
6 | *
7 | * These exceptions are
8 | *
9 | * - {@link ClassNotFoundException}
10 | * - {@link IllegalAccessException}
11 | * - {@link IllegalArgumentException}
12 | * - {@link InstantiationException}
13 | * - {@link java.lang.reflect.InvocationTargetException}
14 | * - {@link NoSuchMethodException}
15 | * - {@link NoSuchFieldException}
16 | * - {@link SecurityException}
17 | *
18 | */
19 | public class ReflectException extends Exception {
20 |
21 | /**
22 | * Generated UID
23 | */
24 | private static final long serialVersionUID = -6213149635297151442L;
25 |
26 | public ReflectException(String message) {
27 | super(message);
28 | }
29 |
30 | public ReflectException(String message, Throwable cause) {
31 | super(message, cause);
32 | }
33 |
34 | public ReflectException() {
35 | super();
36 | }
37 |
38 | public ReflectException(Throwable cause) {
39 | super(cause);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/android-modular/modular-tool/modular-manager/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | modular
3 |
4 |
--------------------------------------------------------------------------------
/android-modular/modular-tool/modular-plugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android-modular/modular-tool/modular-plugin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'groovy'
2 | apply plugin: 'java'
3 | apply plugin: 'maven'
4 |
5 | repositories {
6 | maven {
7 | url uri('../../repo')
8 | }
9 | mavenLocal()
10 | jcenter()
11 | }
12 |
13 | sourceSets {
14 | main {
15 | groovy {
16 | srcDir 'src/main/groovy'
17 | }
18 |
19 | java {
20 | srcDir 'src/main/java'
21 | }
22 | }
23 | }
24 |
25 |
26 | dependencies {
27 | implementation gradleApi()
28 | implementation localGroovy()
29 | implementation 'com.squareup:javapoet:1.11.1'
30 | implementation 'com.android.tools.build:gradle:3.2.1'
31 | implementation 'com.android.tools.build:transform-api:1.5.0'
32 | if (USE_LOCAL_LIB.toBoolean()) {
33 | implementation 'com.jeremyliao:modular-base:0.0.1'
34 | // implementation project(':modular-tool:modular-base')
35 | } else {
36 | implementation 'com.jeremyliao.modular-tools:base:0.0.1'
37 | }
38 | }
39 |
40 | apply from: '../../upload.gradle'
--------------------------------------------------------------------------------
/android-modular/modular-tool/modular-plugin/src/main/groovy/com/jeremyliao/plugin/ModularPlugin.groovy:
--------------------------------------------------------------------------------
1 | package com.jeremyliao.plugin
2 |
3 | import com.android.build.gradle.AppExtension
4 | import org.gradle.api.Plugin
5 | import org.gradle.api.Project
6 |
7 | /**
8 | * Created by liaohailiang on 2018/12/26.
9 | */
10 | class ModularPlugin implements Plugin {
11 |
12 | final String TAG = "[ModularPlugin]"
13 |
14 | @Override
15 | void apply(Project project) {
16 | System.out.println(TAG + project)
17 | addTransform(project)
18 | }
19 |
20 | private void addTransform(Project project) {
21 | def extension = project.extensions.findByType(AppExtension.class)
22 | System.out.println(TAG + extension)
23 | extension.registerTransform(new ModularTransform(project))
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android-modular/modular-tool/modular-plugin/src/main/groovy/com/jeremyliao/plugin/ModularTransform.groovy:
--------------------------------------------------------------------------------
1 | package com.jeremyliao.plugin
2 |
3 | import com.android.build.api.transform.*
4 | import com.android.build.gradle.internal.pipeline.TransformManager
5 | import com.google.gson.reflect.TypeToken
6 | import com.jeremyliao.asm.ModularManagerVisitor
7 | import com.jeremyliao.base.inner.bean.ModuleServiceInfo
8 | import com.jeremyliao.base.inner.utils.GsonUtil
9 | import org.apache.commons.io.FileUtils
10 | import org.apache.commons.io.IOUtils
11 | import org.gradle.api.Project
12 | import org.objectweb.asm.ClassReader
13 | import org.objectweb.asm.ClassVisitor
14 | import org.objectweb.asm.ClassWriter
15 |
16 | import java.lang.reflect.Type
17 | import java.util.jar.JarEntry
18 | import java.util.jar.JarFile
19 | import java.util.jar.JarOutputStream
20 | import java.util.stream.Collectors
21 | import java.util.zip.ZipEntry
22 |
23 | import static org.objectweb.asm.ClassReader.EXPAND_FRAMES
24 |
25 | /**
26 | * Created by liaohailiang on 2018/12/26.
27 | */
28 | class ModularTransform extends Transform {
29 |
30 | static final String TAG = "[[ModularTransform]]"
31 | static final String MODULAR_PATH = "META-INF/modules/module_info/"
32 | static final String MODULE_MANAGER_CLASS_NAME = "ModuleRpcManager.class"
33 |
34 | private final Project project
35 |
36 | private List moduleServiceInfos = new ArrayList<>();
37 | //输出的目标jar
38 | private JarInput outPutJarInput
39 |
40 | ModularTransform(Project project) {
41 | this.project = project
42 | }
43 |
44 | @Override
45 | void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
46 | super.transform(transformInvocation)
47 | traversal(transformInvocation)
48 | handleOutputJarInputs(transformInvocation.outputProvider)
49 | }
50 |
51 | /**
52 | * 处理输入
53 | * @param transformInvocation
54 | */
55 | private void traversal(TransformInvocation transformInvocation) {
56 | printLog("处理输入")
57 | Collection inputs = transformInvocation.inputs
58 | TransformOutputProvider outputProvider = transformInvocation.outputProvider
59 | //遍历inputs
60 | inputs.each { TransformInput input ->
61 | input.directoryInputs.each { DirectoryInput directoryInput ->
62 | handleDirectoryInput(directoryInput, outputProvider)
63 | }
64 |
65 | input.jarInputs.each { JarInput jarInput ->
66 | handleJarInputs(jarInput, outputProvider)
67 | }
68 | }
69 | }
70 |
71 | @Override
72 | String getName() {
73 | return ModularTransform.simpleName
74 | }
75 |
76 | @Override
77 | Set getInputTypes() {
78 | return TransformManager.CONTENT_CLASS
79 | }
80 |
81 | @Override
82 | Set super QualifiedContent.Scope> getScopes() {
83 | return TransformManager.SCOPE_FULL_PROJECT
84 | }
85 |
86 | @Override
87 | boolean isIncremental() {
88 | return false
89 | }
90 |
91 | private void handleJarInputs(JarInput jarInput, TransformOutputProvider outputProvider) {
92 | boolean copyAfterProcess = true
93 | File file = jarInput.getFile()
94 | JarFile jarFile = new JarFile(file)
95 | Enumeration entries = jarFile.entries()
96 | while (entries.hasMoreElements()) {
97 | JarEntry entry = entries.nextElement()
98 | def name = entry.getName()
99 | if (name.contains(MODULE_MANAGER_CLASS_NAME)) {
100 | printLog("ModuleRpcManager JarEntry name: " + name)
101 | printLog("ModuleRpcManager path: " + file.getAbsolutePath())
102 | outPutJarInput = jarInput
103 | copyAfterProcess = false
104 | }
105 | if (name.contains(MODULAR_PATH)) {
106 | printLog("JarEntry: " + entry)
107 | try {
108 | InputStream inputStream = jarFile.getInputStream(entry)
109 | String json = new BufferedReader(new InputStreamReader(inputStream))
110 | .lines().parallel().collect(Collectors.joining(System.lineSeparator()))
111 | Type type = new TypeToken>() {
112 | }.getType()
113 | ArrayList serviceInfos = GsonUtil.fromJson(json, type)
114 | if (serviceInfos != null && serviceInfos.size() > 0) {
115 | moduleServiceInfos.addAll(serviceInfos)
116 | }
117 | inputStream.close()
118 | } catch (IOException e) {
119 | e.printStackTrace()
120 | }
121 | }
122 | }
123 | //处理完输入文件之后,要把输出给下一个任务
124 | if (copyAfterProcess) {
125 | File dest = outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes,
126 | jarInput.scopes, Format.JAR)
127 | FileUtils.copyFile(file, dest)
128 | }
129 | }
130 |
131 | private void handleDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
132 | //是否是目录
133 | if (directoryInput.file.isDirectory()) {
134 | //列出目录所有文件(包含子文件夹,子文件夹内文件)
135 | directoryInput.file.eachFileRecurse { File file ->
136 | def name = file.name
137 | if (name.contains(MODULAR_PATH)) {
138 | printLog('filename: ' + name)
139 | try {
140 | InputStream inputStream = new FileInputStream(file)
141 | String json = new BufferedReader(new InputStreamReader(inputStream))
142 | .lines().parallel().collect(Collectors.joining(System.lineSeparator()))
143 | Type type = new TypeToken>() {
144 | }.getType()
145 | ArrayList serviceInfos = GsonUtil.fromJson(json, type)
146 | if (serviceInfos != null && serviceInfos.size() > 0) {
147 | moduleServiceInfos.addAll(serviceInfos)
148 | }
149 | inputStream.close()
150 | } catch (IOException e) {
151 | e.printStackTrace()
152 | }
153 | }
154 | }
155 | }
156 | //处理完输入文件之后,要把输出给下一个任务
157 | def dest = outputProvider.getContentLocation(directoryInput.name,
158 | directoryInput.contentTypes, directoryInput.scopes,
159 | Format.DIRECTORY)
160 | FileUtils.copyDirectory(directoryInput.file, dest)
161 | }
162 |
163 | private void handleOutputJarInputs(TransformOutputProvider outputProvider) {
164 | if (outPutJarInput == null) {
165 | return
166 | }
167 | if (moduleServiceInfos == null || moduleServiceInfos.size() == 0) {
168 | return
169 | }
170 | JarFile jarFile = new JarFile(outPutJarInput.file)
171 | Enumeration enumeration = jarFile.entries()
172 | File tmpFile = new File(outPutJarInput.file.getParent() + File.separator + "classes_temp.jar")
173 | JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tmpFile))
174 | //用于保存
175 | while (enumeration.hasMoreElements()) {
176 | JarEntry jarEntry = (JarEntry) enumeration.nextElement()
177 | String entryName = jarEntry.getName()
178 | ZipEntry zipEntry = new ZipEntry(entryName)
179 | InputStream inputStream = jarFile.getInputStream(jarEntry)
180 | if (entryName.contains(MODULE_MANAGER_CLASS_NAME)) {
181 | //class文件处理
182 | jarOutputStream.putNextEntry(zipEntry)
183 | ClassReader classReader = new ClassReader(IOUtils.toByteArray(inputStream))
184 | ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
185 | ClassVisitor cv = new ModularManagerVisitor(classWriter, GsonUtil.toJson(moduleServiceInfos))
186 | classReader.accept(cv, EXPAND_FRAMES)
187 | byte[] code = classWriter.toByteArray()
188 | jarOutputStream.write(code)
189 | } else {
190 | jarOutputStream.putNextEntry(zipEntry)
191 | jarOutputStream.write(IOUtils.toByteArray(inputStream))
192 | }
193 | jarOutputStream.closeEntry()
194 | }
195 | //结束
196 | jarOutputStream.close()
197 | jarFile.close()
198 | File dest = outputProvider.getContentLocation(outPutJarInput.name,
199 | outPutJarInput.contentTypes, outPutJarInput.scopes, Format.JAR)
200 | printLog("src path: " + outPutJarInput.file.absolutePath)
201 | printLog("copy dest: " + dest.absolutePath)
202 | FileUtils.copyFile(tmpFile, dest)
203 | tmpFile.delete()
204 | }
205 |
206 | private static void printLog(String log) {
207 | println TAG + log
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/android-modular/modular-tool/modular-plugin/src/main/java/com/jeremyliao/asm/ModularManagerVisitor.java:
--------------------------------------------------------------------------------
1 | package com.jeremyliao.asm;
2 |
3 | import org.objectweb.asm.ClassVisitor;
4 | import org.objectweb.asm.Label;
5 | import org.objectweb.asm.MethodVisitor;
6 | import org.objectweb.asm.Opcodes;
7 |
8 | /**
9 | * Created by liaohailiang on 2019-09-30.
10 | */
11 | public class ModularManagerVisitor extends ClassVisitor implements Opcodes {
12 |
13 | private static final String INJECT_METHOD_NAME = "getModuleJson";
14 | private final String outputJson;
15 |
16 | public ModularManagerVisitor(ClassVisitor cv, String json) {
17 | super(Opcodes.ASM5, cv);
18 | outputJson = json;
19 | }
20 |
21 | @Override
22 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
23 | MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
24 | if (!INJECT_METHOD_NAME.equals(name)) {
25 | return mv;
26 | }
27 | return new MethodVisitor(Opcodes.ASM5, mv) {
28 | @Override
29 | public void visitCode() {
30 | super.visitCode();
31 | mv.visitCode();
32 | Label l0 = new Label();
33 | mv.visitLabel(l0);
34 | mv.visitLineNumber(66, l0);
35 | mv.visitLdcInsn(outputJson);
36 | mv.visitInsn(ARETURN);
37 | mv.visitMaxs(1, 0);
38 | mv.visitEnd();
39 | }
40 | };
41 | }
42 |
43 | @Override
44 | public void visitEnd() {
45 | super.visitEnd();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/android-modular/modular-tool/modular-plugin/src/main/resources/META-INF/gradle-plugins/modular-plugin.properties:
--------------------------------------------------------------------------------
1 | implementation-class=com.jeremyliao.plugin.ModularPlugin
--------------------------------------------------------------------------------
/android-modular/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include 'modular-demo:module-a'
3 | include 'modular-demo:common'
4 | include 'modular-demo:module-a-interface'
5 | include 'modular-demo:module-b'
6 | include 'modular-demo:module-b-interface'
7 | include 'modular-tool:modular-manager'
8 | include 'modular-tool:modular-base'
9 | include 'modular-tool:modular-plugin'
10 | include 'modular-tool:modular-compiler'
--------------------------------------------------------------------------------
/android-modular/upload.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven'
2 |
3 | //group和version
4 | group = 'com.jeremyliao'
5 | version = '0.0.1'
6 |
7 | //打包到本地或者远程Maven库
8 | uploadArchives {
9 | repositories {
10 | mavenDeployer {
11 | repository(url: uri('../../repo'))
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------