├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── CHANGELOG.md
├── README.md
├── api
├── build.gradle
├── consumer-proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── sankuai
│ └── erp
│ └── component
│ └── appinit
│ └── api
│ ├── AppInitApiUtils.java
│ ├── AppInitDispatcher.java
│ ├── AppInitManager.java
│ ├── Logger.java
│ ├── SimpleAppInit.java
│ └── SimpleAppInitCallback.java
├── build.gradle
├── buildSrc
├── build.gradle
└── src
│ └── main
│ ├── groovy
│ └── com
│ │ └── sankuai
│ │ └── erp
│ │ └── component
│ │ └── plugin
│ │ └── appinit
│ │ ├── AppInitAsmTransform.groovy
│ │ ├── AppInitExtension.groovy
│ │ ├── AppInitManagerAsmKnife.groovy
│ │ ├── AppInitPlugin.groovy
│ │ ├── ApplicationAsmKnife.groovy
│ │ ├── BaseAptPlugin.groovy
│ │ ├── BaseAsmTransform.groovy
│ │ ├── BaseTransform.groovy
│ │ ├── ChildInitTableSortUtils.groovy
│ │ └── Logger.groovy
│ └── resources
│ └── META-INF
│ └── gradle-plugins
│ └── bga-appinit-plugin.properties
├── common
├── build.gradle
└── src
│ └── main
│ └── java
│ └── com
│ └── sankuai
│ └── erp
│ └── component
│ └── appinit
│ └── common
│ ├── AppInit.java
│ ├── AppInitCallback.java
│ ├── AppInitCommonUtils.java
│ ├── AppInitItem.java
│ ├── AppInitLogger.java
│ ├── ChildInitTable.java
│ ├── IAppInit.java
│ ├── ILogger.java
│ ├── ModuleConsts.java
│ ├── OrderConstraintRule.java
│ └── Process.java
├── compiler
├── build.gradle
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── sankuai
│ │ └── erp
│ │ └── component
│ │ └── appinit
│ │ └── compiler
│ │ ├── BaseGenerateChildTableProcessor.java
│ │ └── GenerateAppInitChildTableProcessor.java
│ └── resources
│ └── META-INF
│ └── services
│ └── javax.annotation.processing.Processor
├── demo
├── app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── src
│ │ ├── androidTest
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── sankuai
│ │ │ │ └── erp
│ │ │ │ └── component
│ │ │ │ └── appinitdemo
│ │ │ │ └── TransformTest.java
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── sankuai
│ │ │ │ └── erp
│ │ │ │ └── component
│ │ │ │ └── appinitdemo
│ │ │ │ ├── App.java
│ │ │ │ ├── AppService.java
│ │ │ │ ├── activity
│ │ │ │ └── MainActivity.java
│ │ │ │ └── init
│ │ │ │ ├── RouterInit.java
│ │ │ │ └── ShellOneInit.kt
│ │ │ └── res
│ │ │ ├── layout
│ │ │ └── activity_main.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ └── test_config.gradle
├── module1
│ ├── build.gradle
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── sankuai
│ │ │ └── erp
│ │ │ └── component
│ │ │ └── appinitmodule1
│ │ │ ├── Module1Activity.java
│ │ │ ├── Module1Fragment.java
│ │ │ ├── Module1Service.java
│ │ │ └── init
│ │ │ ├── Module1FiveInit.java
│ │ │ ├── Module1FourInit.java
│ │ │ ├── Module1OneInit.kt
│ │ │ ├── Module1ThreeInit.kt
│ │ │ └── Module1TwoInit.java
│ │ └── res
│ │ └── layout
│ │ ├── activity_module1.xml
│ │ └── fragment_module1.xml
└── module3
│ ├── build.gradle
│ ├── gradle.properties
│ └── src
│ ├── baidu
│ └── java
│ │ └── com
│ │ └── sankuai
│ │ └── erp
│ │ └── component
│ │ └── appinitmodule3
│ │ └── BaiduInit.java
│ ├── free
│ └── java
│ │ └── com
│ │ └── sankuai
│ │ └── erp
│ │ └── component
│ │ └── appinitmodule3
│ │ └── FreeInit.java
│ ├── freeBaidu
│ └── java
│ │ └── com
│ │ └── sankuai
│ │ └── erp
│ │ └── component
│ │ └── appinitmodule3
│ │ └── FreeBaiduInit.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── sankuai
│ │ │ └── erp
│ │ │ └── component
│ │ │ └── appinitmodule3
│ │ │ ├── Module3Activity.java
│ │ │ ├── Module3Fragment.kt
│ │ │ ├── Module3Service.java
│ │ │ └── init
│ │ │ ├── Module3FiveInit.java
│ │ │ ├── Module3FourInit.java
│ │ │ ├── Module3OneInit.java
│ │ │ ├── Module3ThreeInit.kt
│ │ │ └── Module3TwoInit.kt
│ └── res
│ │ └── layout
│ │ ├── activity_module3.xml
│ │ └── fragment_module3.xml
│ ├── vip
│ └── java
│ │ └── com
│ │ └── sankuai
│ │ └── erp
│ │ └── component
│ │ └── appinitmodule3
│ │ └── VipInit.java
│ └── xiaomi
│ └── java
│ └── com
│ └── sankuai
│ └── erp
│ └── component
│ └── appinitmodule3
│ └── XiaomiInit.java
├── docs
├── imgs
│ ├── app_init_dispath.png
│ ├── app_init_manager_class.png
│ ├── app_init_manager_knife.png
│ ├── application_class.png
│ ├── application_knife.png
│ ├── child_init_table.png
│ ├── comparison.png
│ ├── disclaimer.png
│ ├── execution_flow.png
│ ├── init_coordinate_desc1.png
│ ├── init_coordinate_desc2.png
│ ├── module_coordinate_desc1.png
│ ├── module_coordinate_desc2.png
│ ├── module_coordinate_desc3.png
│ ├── module_introduce.png
│ ├── processor.png
│ ├── run_log.png
│ └── source.png
├── principle.md
└── user-manual.md
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── module2
├── build.gradle
├── gradle.properties
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── sankuai
│ │ └── erp
│ │ └── component
│ │ └── appinitmodule2
│ │ ├── Module2Activity.java
│ │ ├── Module2Fragment.java
│ │ ├── Module2Service.java
│ │ └── init
│ │ ├── Module2FiveInit.java
│ │ ├── Module2FourInit.java
│ │ ├── Module2OneInit.java
│ │ ├── Module2ThreeInit.java
│ │ └── Module2TwoInit.kt
│ └── res
│ └── layout
│ ├── activity_module2.xml
│ └── fragment_module2.xml
├── publishLib.sh
├── settings.gradle
└── updateVersion.sh
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Issue/提问须知
2 |
3 | 在提交 issue 之前,我们应该先查询是否已经有相关的 issue 和[使用文档](https://github.com/bingoogolapple/AppInit/blob/master/docs/user-manual.md)。提交 issue 时,我们需要写明 issue 的原因,最好可以携带编译或运行过程的日志或者截图。issue 需要以下面的格式提出:
4 |
5 | ----
6 |
7 | * 异常类型:app 运行时异常/编译异常
8 |
9 | * 手机型号(如是编译异常,则可以不填):如:Nexus 5
10 |
11 | * 手机系统版本(如是编译异常,则可以不填):如:Android 5.0
12 |
13 | * AppInit 版本:如:1.0.0
14 |
15 | * Gradle 版本:如:3.1.3
16 |
17 | * 系统:如:Mac
18 |
19 | * 堆栈/日志或者截图:
20 |
21 | ----
22 |
23 | 如是编译异常,请在执行 gradle 命令时,加上 --stacktrace,并把结果重定向,如:
24 |
25 | ```shell
26 | ./gradlew clean assembleRelease --stacktrace --no-daemon > log.txt
27 | ```
28 |
29 | 结果重定向到当前的目录下的 log.txt 文件;日志中我们需要过滤`AppInit`关键字,可以初步查找问题的大概原因。
30 |
31 | AppInit 提供了 sample 样例与我们的源码,大家在使用前可以先将样例跑通,如遇任何疑问也欢迎大家提出,更鼓励大家给我们提 PR,谢谢大家的支持。
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 | /*/build/
19 | /*/*/build/
20 | /*/*/*/build/
21 | reports/
22 | htmlreports/
23 | /*/reports/
24 | /*/*/reports/
25 | /*/*/*/reports/
26 |
27 | # Local configuration file (sdk path, etc)
28 | local.properties
29 |
30 | # Proguard folder generated by Eclipse
31 | proguard/
32 |
33 | # Log Files
34 | *.log
35 |
36 | # Eclipse project files
37 | .classpath
38 | .project
39 | .settings/
40 |
41 | # Intellij project files
42 | *.iml
43 | *.ipr
44 | *.iws
45 | .idea/
46 |
47 | # Mac system files
48 | .DS_Store
49 |
50 | *.keystore
51 |
52 | captures
53 |
54 | repo/
55 |
56 | *.properties-e
57 |
58 | .vscode/
59 |
60 | *.hprof
61 | node_modules
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 更新日志
2 |
3 | ## Version 1.0.8 (2020-04-05)
4 |
5 | - 从 JCenter 迁移到 JitPack,Gradle 依赖由「cn.bingoogolapple:bga-appinit-plugin」变为「com.github.bingoogolapple.AppInit:buildSrc」
6 |
7 | ## Version 1.0.7 (2020-03-26)
8 |
9 | - 改由个人账号维护,Gradle 依赖由「com.sankuai.erp.component:appinit-plugin」变为「cn.bingoogolapple:bga-appinit-plugin」
10 | - v1.0.7 扫描 jar 包时捕获一下异常
11 |
12 | ## Version 1.0.6 (2019-10-29)
13 |
14 | - v1.0.6 fix #15 兼容 gradle 4.1
15 |
16 | ## Version 1.0.5 (2019-04-14)
17 |
18 | - v1.0.5 修复 Windows 下 rebuild 时提示无法删除 app\build\intermediates\transforms\xxx\debug\xxx.jar
19 |
20 | ## Version 1.0.4 (2019-04-13)
21 |
22 | - v1.0.4 去掉对 commons-io:2.6 的依赖
23 |
24 | ## Version 1.0.3 (2019-02-19)
25 |
26 | - Fix #10 兼容 rootProject 目录以数字开头
27 |
28 | ## Version 1.0.2 (2019-01-30)
29 |
30 | - Fix #6 minSdkVersion 改为 16
31 |
32 | ## Version 1.0.1 (2019-01-25)
33 |
34 | - Fix #1 不支持增量编译的 Transform 在消费上游文件前需要先清除输出目录的文件
35 | - Fix #3 Transform 里获取 Variant 的方式可以更优雅一些
36 |
37 | ## Version 1.0.0 (2019-01-23)
38 |
39 | - 第一个开源版本
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AppInit:Android 应用初始化框架
2 |
3 | [](https://bintray.com/bingoogolapple/maven/bga-appinit-plugin/_latestVersion)
4 | [](https://github.com/bingoogolapple/AppInit/pulls)
5 |
6 | AppInit 是一款 Android 应用初始化框架,基于组件化的设计思路,功能灵活,使用简单。
7 |
8 | AppInit 用于解决美团收银 B 端 App 在业务演进过程中的实际问题,取得了不错的效果,因此我们决定将其开源,希望更多技术同行一起开发,应用到更广泛的场景里去。
9 |
10 | ## 背景
11 |
12 | 随着业务的快速发展,新项目新业务不断出现,以及项目组件化的实施,项目里需要初始化的业务模块和 SDK 也逐渐增多,而且有些业务模块间可能有着错综复杂的依赖关系,在项目开发和测试人员不足、新加入开发同学不熟悉项目的情况下,难免会出现少测漏测的情况,如何使各模块初始化代码解耦、按正确的顺序初始化是我们需要思考的问题。
13 |
14 | ## 功能简介
15 |
16 | * 可以在指定进程的指定线程,按指定顺序分发 Application 生命周期方法给初始化类(继承自 SimpleAppInit 并添加 AppInit 注解,低耦合)
17 | * 可以配置各模块间的初始化顺序,模块内部自己管理各初始化类的顺序,也可配置在其他模块的某个初始化类之前初始化(编译期间排序,运行期高性能)
18 | * 可以在应用启动时拉取配置信息动态修改初始化顺序,及时修复线上包初始化顺序错乱问题(高稳定)
19 | * 可以统计各模块和具体初始化类的初始化时间,便于优化冷启动时间
20 |
21 | ## TODO
22 |
23 | * 非主 dex 懒加载
24 | * 初始化线程池优化
25 |
26 | ## 业界初始化方案对比
27 |
28 | 
29 |
30 | ## 设计与使用文档
31 |
32 | * [使用文档](docs/user-manual.md)
33 | * [设计文档](docs/principle.md)
34 |
35 | ## 更新日志
36 |
37 | [更新日志](CHANGELOG.md)
38 |
39 | ## 免责声明
40 |
41 | AppInit 于 2019 年 1 月 21 日以公司名义在 Meituan-Dianping 账号下开源 ,并于 2020 年 1 月 13 日停止从 Meituan-Dianping 账号开源。
42 |
43 | 由于之前开源的 1 年时间内已经有开发者将 AppInit 应用到了实际项目中,后续业务项目升级 Gradle 版本后可能也需要 AppInit 升级进行兼容,且在之前开源的 1 年时间内已经大量 Fork,因此再次 Fork 一份到个人账号下进行维护。代码包名不变,为了便于后续上传 JitPack,Gradle 依赖由「com.sankuai.erp.component:appinit-plugin」变为「com.github.bingoogolapple.AppInit:buildSrc」
44 |
45 | 
46 |
47 | ## 作者联系方式
48 |
49 | | 个人主页 | 邮箱 |
50 | | ------------- | ------------ |
51 | | bingoogolapple.cn | bingoogolapple@gmail.com |
52 |
53 | | 个人微信号 | 微信群 | 公众号 |
54 | | ------------ | ------------ | ------------ |
55 | |
|
|
|
56 |
57 | | 个人 QQ 号 | QQ 群 |
58 | | ------------ | ------------ |
59 | |
|
|
60 |
61 | ## 打赏支持作者
62 |
63 | 如果您觉得 BGA 系列开源库或工具软件帮您节省了大量的开发时间,可以扫描下方的二维码打赏支持。您的支持将鼓励我继续创作,打赏后还可以加我微信免费开通一年 [上帝小助手浏览器扩展/插件开发平台](https://github.com/bingoogolapple/bga-god-assistant-config) 的会员服务
64 |
65 | | 微信 | QQ | 支付宝 |
66 | | ------------- | ------------- | ------------- |
67 | |
|
|
|
68 |
69 | ## 作者项目推荐
70 |
71 | * 欢迎您使用我开发的第一个独立开发软件产品 [上帝小助手浏览器扩展/插件开发平台](https://github.com/bingoogolapple/bga-god-assistant-config)
72 |
--------------------------------------------------------------------------------
/api/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 |
4 | dependencies {
5 | compileOnly 'com.android.support:support-v4:28.0.0'
6 | api project(':common')
7 | }
8 |
--------------------------------------------------------------------------------
/api/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class * implements com.sankuai.erp.component.appinit.common.IAppInit
2 | -keep class * extends com.sankuai.erp.component.appinit.common.ChildInitTable
--------------------------------------------------------------------------------
/api/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/api/src/main/java/com/sankuai/erp/component/appinit/api/AppInitApiUtils.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.api;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 | import android.text.TextUtils;
6 |
7 | import com.sankuai.erp.component.appinit.common.AppInitCommonUtils;
8 | import com.sankuai.erp.component.appinit.common.AppInitItem;
9 | import com.sankuai.erp.component.appinit.common.ChildInitTable;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | * 作者:王浩
15 | * 创建时间:2018/10/30
16 | * 描述:
17 | */
18 | public final class AppInitApiUtils {
19 |
20 | private AppInitApiUtils() {
21 | }
22 |
23 | /**
24 | * 包名判断是否为主进程
25 | */
26 | public static boolean isMainProcess() {
27 | return TextUtils.equals(AppInitManager.get().getApplication().getPackageName(), getProcessName());
28 | }
29 |
30 | /**
31 | * 获取进程全名
32 | */
33 | static String getProcessName() {
34 | ActivityManager am = (ActivityManager) AppInitManager.get().getApplication().getSystemService(Context.ACTIVITY_SERVICE);
35 | if (am == null) {
36 | return "";
37 | }
38 | List runningApps = am.getRunningAppProcesses();
39 | if (runningApps == null) {
40 | return "";
41 | }
42 | for (ActivityManager.RunningAppProcessInfo proInfo : runningApps) {
43 | if (proInfo.pid == android.os.Process.myPid()) {
44 | if (proInfo.processName != null) {
45 | return proInfo.processName;
46 | }
47 | }
48 | }
49 | return "";
50 | }
51 |
52 | public static String getInitOrderAndTimeLog(List childInitTableList, List appInitItemList) {
53 | StringBuilder logSb = new StringBuilder("初始化顺序为:\n");
54 | AppInitItem pre = null;
55 | for (AppInitItem appInitItem : appInitItemList) {
56 | if (pre == null || !AppInitCommonUtils.equals(pre.moduleCoordinate, appInitItem.moduleCoordinate)) {
57 | logSb.append(getModuleTimeInfo(childInitTableList, appInitItem.moduleCoordinate)).append("\n");
58 | }
59 | pre = appInitItem;
60 | logSb.append(appInitItem.timeInfo()).append("\n");
61 | }
62 | int time = 0;
63 | for (ChildInitTable childInitTable : childInitTableList) {
64 | time += childInitTable.time;
65 | }
66 | return logSb.append("\n").append(String.format("所有模块的初始化耗时:%sms", time)).toString();
67 | }
68 |
69 | private static String getModuleTimeInfo(List childInitTableList, String moduleCoordinate) {
70 | for (ChildInitTable childInitTable : childInitTableList) {
71 | if (AppInitCommonUtils.equals(childInitTable.coordinate, moduleCoordinate)) {
72 | return childInitTable.getTimeInfo();
73 | }
74 | }
75 | return "";
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/api/src/main/java/com/sankuai/erp/component/appinit/api/AppInitDispatcher.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.api;
2 |
3 | import android.app.Application;
4 | import android.content.ComponentCallbacks;
5 | import android.content.ComponentCallbacks2;
6 | import android.content.res.Configuration;
7 | import android.os.AsyncTask;
8 |
9 | import com.sankuai.erp.component.appinit.common.AppInitCommonUtils;
10 | import com.sankuai.erp.component.appinit.common.AppInitItem;
11 |
12 | import java.util.List;
13 | import java.util.concurrent.ArrayBlockingQueue;
14 | import java.util.concurrent.BlockingQueue;
15 | import java.util.concurrent.TimeUnit;
16 |
17 | /**
18 | * 作者:王浩
19 | * 创建时间:2018/10/30
20 | * 描述:
21 | */
22 | final class AppInitDispatcher {
23 | private static final int CONST_100 = 100;
24 | private List mAppInitItemList;
25 | private boolean mIsMainProcess;
26 |
27 | private BlockingQueue mAsyncOnCreateQueuedInit;
28 | private volatile boolean mAsyncOnCreateQueuedInitFinished;
29 |
30 | AppInitDispatcher(List appInitItemList) {
31 | mAppInitItemList = appInitItemList;
32 | mIsMainProcess = AppInitApiUtils.isMainProcess();
33 | }
34 |
35 | /**
36 | * 初始化列表是否为空
37 | */
38 | private boolean isAppInitEmpty() {
39 | return mAppInitItemList == null || mAppInitItemList.size() <= 0;
40 | }
41 |
42 | /**
43 | * 是否忽略该初始化
44 | */
45 | private boolean isIgnoreDispatch(AppInitItem appInitItem) {
46 | if (appInitItem.onlyForDebug && !AppInitManager.get().isDebug()) {
47 | return true;
48 | }
49 | boolean dispatch = (mIsMainProcess && appInitItem.isForMainProcess()) || (!mIsMainProcess && appInitItem.isNotForMainProcess());
50 | return !dispatch;
51 | }
52 |
53 | /**
54 | * 在 {@link Application#onCreate()} 中调用
55 | */
56 | void onCreate() {
57 | if (isAppInitEmpty()) {
58 | return;
59 | }
60 | mAsyncOnCreateQueuedInit = new ArrayBlockingQueue<>(mAppInitItemList.size());
61 | AsyncTask.THREAD_POOL_EXECUTOR.execute(this::asyncOnCreate);
62 | dispatch(appInitItem -> {
63 | appInitItem.time += AppInitCommonUtils.time(appInitItem.toString() + " onCreate ", () -> appInitItem.appInit.onCreate());
64 | if (appInitItem.appInit.needAsyncInit()) {
65 | mAsyncOnCreateQueuedInit.add(appInitItem);
66 | }
67 | });
68 | mAsyncOnCreateQueuedInitFinished = true;
69 | }
70 |
71 | private void asyncOnCreate() {
72 | AppInitItem appInitItem;
73 | try {
74 | while (true) {
75 | appInitItem = mAsyncOnCreateQueuedInit.poll(CONST_100, TimeUnit.MILLISECONDS);
76 | if (appInitItem == null) {
77 | if (mAsyncOnCreateQueuedInitFinished) {
78 | break;
79 | } else {
80 | continue;
81 | }
82 | }
83 |
84 | dispatchAsyncOnCreate(appInitItem);
85 |
86 | if (mAsyncOnCreateQueuedInitFinished && mAsyncOnCreateQueuedInit.isEmpty()) {
87 | break;
88 | }
89 | }
90 | } catch (InterruptedException e) {
91 | e.printStackTrace();
92 | }
93 | }
94 |
95 | private void dispatchAsyncOnCreate(AppInitItem appInitItem) {
96 | if (isIgnoreDispatch(appInitItem)) {
97 | return;
98 | }
99 | AppInitCommonUtils.time(appInitItem.toString() + " asyncOnCreate ", () -> appInitItem.appInit.asyncOnCreate());
100 | }
101 |
102 | /**
103 | * 在 {@link Application#onTerminate()} 中调用
104 | */
105 | void onTerminate() {
106 | dispatch(appInitItem -> AppInitCommonUtils.time(appInitItem.toString() + " onTerminate ", () -> appInitItem.appInit.onTerminate()));
107 | }
108 |
109 | /**
110 | * 在 {@link Application#onConfigurationChanged(Configuration)} 中调用
111 | *
112 | * @param newConfig The new device configuration.
113 | * @see ComponentCallbacks#onConfigurationChanged(Configuration)
114 | */
115 | void onConfigurationChanged(Configuration newConfig) {
116 | dispatch(appInitItem -> AppInitCommonUtils.time(appInitItem.toString() + " onConfigurationChanged ",
117 | () -> appInitItem.appInit.onConfigurationChanged(newConfig)));
118 | }
119 |
120 | /**
121 | * 在 {@link Application#onLowMemory()} 中调用
122 | *
123 | * @see ComponentCallbacks#onLowMemory()
124 | */
125 | void onLowMemory() {
126 | dispatch(appInitItem -> AppInitCommonUtils.time(appInitItem.toString() + " onLowMemory ", () -> appInitItem.appInit.onLowMemory()));
127 | }
128 |
129 | /**
130 | * 在 {@link Application#onTrimMemory(int)} 中调用
131 | *
132 | * @param level The context of the trim, giving a hint of the amount of trimming the application may like to perform.
133 | * @see ComponentCallbacks2#onTrimMemory(int)
134 | */
135 | void onTrimMemory(int level) {
136 | dispatch(appInitItem -> AppInitCommonUtils.time(appInitItem.toString() + " onTrimMemory ", () -> appInitItem.appInit.onTrimMemory(level)));
137 | }
138 |
139 | private void dispatch(DispatchCallback dispatchCallback) {
140 | if (isAppInitEmpty()) {
141 | return;
142 | }
143 | for (AppInitItem appInitItem : mAppInitItemList) {
144 | if (isIgnoreDispatch(appInitItem)) {
145 | continue;
146 | }
147 | dispatchCallback.dispatch(appInitItem);
148 | }
149 | }
150 |
151 | private interface DispatchCallback {
152 | void dispatch(AppInitItem appInitItem);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/api/src/main/java/com/sankuai/erp/component/appinit/api/AppInitManager.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.api;
2 |
3 | import android.app.Application;
4 | import android.content.ComponentCallbacks;
5 | import android.content.ComponentCallbacks2;
6 | import android.content.res.Configuration;
7 | import android.support.annotation.NonNull;
8 |
9 | import com.sankuai.erp.component.appinit.common.AppInitCallback;
10 | import com.sankuai.erp.component.appinit.common.AppInitCommonUtils;
11 | import com.sankuai.erp.component.appinit.common.AppInitItem;
12 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
13 | import com.sankuai.erp.component.appinit.common.ChildInitTable;
14 | import com.sankuai.erp.component.appinit.common.IAppInit;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 | import java.util.Map;
19 |
20 | /**
21 | * 作者:王浩
22 | * 创建时间:2018/10/29
23 | * 描述:
24 | */
25 | public final class AppInitManager {
26 | private AppInitDispatcher mAppInitDispatcher;
27 | private boolean mIsDebug;
28 | private Application mApplication;
29 | private List mChildInitTableList = new ArrayList<>();
30 | private List mAppInitItemList = new ArrayList<>();
31 | private AppInitCallback mAppInitCallback;
32 | private boolean mAbortOnNotExist = true;
33 |
34 | private AppInitManager() {
35 | }
36 |
37 | private static class SingletonHolder {
38 | private static final AppInitManager INSTANCE = new AppInitManager();
39 | }
40 |
41 | public static AppInitManager get() {
42 | return SingletonHolder.INSTANCE;
43 | }
44 |
45 | /**
46 | * 获取当前应用是否处于 debug 模式
47 | */
48 | public boolean isDebug() {
49 | return mIsDebug;
50 | }
51 |
52 | /**
53 | * 获取当前应用程序上下文
54 | */
55 | public Application getApplication() {
56 | return mApplication;
57 | }
58 |
59 | /**
60 | * 在插件中注入 ChildInitTableLis
61 | */
62 | private void injectChildInitTableList() {
63 | }
64 |
65 | /**
66 | * 在插件中注入 AppInitItemList
67 | */
68 | private void injectAppInitItemList() {
69 | }
70 |
71 | /**
72 | * 初始化 AppInitManager。在 {@link Application#onCreate()} 中调用
73 | *
74 | * @param application AndroidManifest 中注册的 Application
75 | * @param appInitCallback 初始化配置回调接口
76 | */
77 | public void init(@NonNull Application application, @NonNull AppInitCallback appInitCallback) {
78 | mApplication = application;
79 | mAppInitCallback = appInitCallback;
80 |
81 | mAppInitCallback.onInitStart(AppInitApiUtils.isMainProcess(), AppInitApiUtils.getProcessName());
82 | mIsDebug = mAppInitCallback.isDebug();
83 | AppInitLogger.sLogger = new Logger();
84 |
85 | injectChildInitTableList();
86 | injectAppInitItemList();
87 |
88 | AppInitCommonUtils.timeStr("initSort ", () -> {
89 | // 接入方运行时自定义了排序
90 | Map coordinateAheadOfMap = mAppInitCallback.getCoordinateAheadOfMap();
91 | if (coordinateAheadOfMap != null && !coordinateAheadOfMap.isEmpty()) {
92 |
93 | mAppInitItemList = AppInitCommonUtils.sortAppInitItem(mAbortOnNotExist, mChildInitTableList, coordinateAheadOfMap, new StringBuilder());
94 | for (AppInitItem appInitItem : mAppInitItemList) {
95 | try {
96 | appInitItem.appInit = (IAppInit) Class.forName(appInitItem.appInitClassName).newInstance();
97 | } catch (Exception e) {
98 | e.printStackTrace();
99 | }
100 | }
101 | }
102 | // 实例化分发器
103 | mAppInitDispatcher = new AppInitDispatcher(mAppInitItemList);
104 | });
105 | onCreate();
106 | }
107 |
108 | /**
109 | * 在 {@link Application#onCreate()} 中调用
110 | */
111 | private void onCreate() {
112 | AppInitCommonUtils.timeStr("总的 onCreate ", () -> mAppInitDispatcher.onCreate());
113 |
114 | mAppInitCallback.onInitFinished(AppInitApiUtils.isMainProcess(), AppInitApiUtils.getProcessName(),
115 | new ArrayList<>(mChildInitTableList), new ArrayList<>(mAppInitItemList));
116 | }
117 |
118 | /**
119 | * 在 {@link Application#onTerminate()} 中调用
120 | */
121 | public void onTerminate() {
122 | AppInitCommonUtils.timeStr("总的 onTerminate ", () -> mAppInitDispatcher.onTerminate());
123 | }
124 |
125 | /**
126 | * 在 {@link Application#onConfigurationChanged(Configuration)} 中调用
127 | *
128 | * @param newConfig The new device configuration.
129 | * @see ComponentCallbacks#onConfigurationChanged(Configuration)
130 | */
131 | public void onConfigurationChanged(Configuration newConfig) {
132 | AppInitCommonUtils.timeStr("总的 onConfigurationChanged ", () -> mAppInitDispatcher.onConfigurationChanged(newConfig));
133 | }
134 |
135 | /**
136 | * 在 {@link Application#onLowMemory()} 中调用
137 | *
138 | * @see ComponentCallbacks#onLowMemory()
139 | */
140 | public void onLowMemory() {
141 | AppInitCommonUtils.timeStr("总的 onLowMemory ", () -> mAppInitDispatcher.onLowMemory());
142 | }
143 |
144 | /**
145 | * 在 {@link Application#onTrimMemory(int)} 中调用
146 | *
147 | * @param level The context of the trim, giving a hint of the amount of trimming the application may like to perform.
148 | * @see ComponentCallbacks2#onTrimMemory(int)
149 | */
150 | public void onTrimMemory(int level) {
151 | AppInitCommonUtils.timeStr("总的 onTrimMemory ", () -> mAppInitDispatcher.onTrimMemory(level));
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/api/src/main/java/com/sankuai/erp/component/appinit/api/Logger.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.api;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import com.sankuai.erp.component.appinit.common.ILogger;
7 |
8 | /**
9 | * 作者:王浩
10 | * 创建时间:2018/11/2
11 | * 描述:AppInit 打印 Android 日志
12 | */
13 | public class Logger implements ILogger {
14 | private static final String TAG = "AppInit";
15 |
16 | @Override
17 | public boolean isDebug() {
18 | return AppInitManager.get().isDebug();
19 | }
20 |
21 | @Override
22 | public boolean isIsMainProcess() {
23 | return AppInitApiUtils.isMainProcess();
24 | }
25 |
26 | @Override
27 | public void demo(String msg) {
28 | d("AppInitDemo", msg);
29 | }
30 |
31 | @Override
32 | public void d(String msg) {
33 | d(TAG, msg);
34 | }
35 |
36 | private void d(String tag, String msg) {
37 | if (AppInitManager.get().isDebug()) {
38 | Log.d(tag, String.format("[%s][%s]%s", getProcessName(), Thread.currentThread().getName(), msg));
39 | }
40 | }
41 |
42 | @Override
43 | public void e(String msg) {
44 | Log.e(TAG, String.format("[%s][%s]=>%s", getProcessName(), Thread.currentThread().getName(), msg));
45 | }
46 |
47 | private String getProcessName() {
48 | String processName;
49 | if (isIsMainProcess()) {
50 | processName = "主进程";
51 | } else {
52 | processName = AppInitApiUtils.getProcessName();
53 | if (!TextUtils.isEmpty(processName) && processName.contains(":")) {
54 | processName = processName.substring(processName.indexOf(":"));
55 | }
56 | }
57 | return processName;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/api/src/main/java/com/sankuai/erp/component/appinit/api/SimpleAppInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.api;
2 |
3 | import android.app.Application;
4 | import android.content.ComponentCallbacks;
5 | import android.content.ComponentCallbacks2;
6 | import android.content.res.Configuration;
7 |
8 | import com.sankuai.erp.component.appinit.common.IAppInit;
9 |
10 | /**
11 | * 作者:王浩
12 | * 创建时间:2018/10/29
13 | * 描述:所有方法都只会运行在你注册的进程
14 | */
15 | public abstract class SimpleAppInit implements IAppInit {
16 | @SuppressWarnings("checkstyle:MemberName")
17 | protected final String TAG;
18 | protected Application mApplication;
19 | protected boolean mIsDebug;
20 |
21 | public SimpleAppInit() {
22 | TAG = this.getClass().getSimpleName();
23 | mApplication = AppInitManager.get().getApplication();
24 | mIsDebug = AppInitManager.get().isDebug();
25 | }
26 |
27 | /**
28 | * 是否需要异步初始化,默认为 false
29 | */
30 | @Override
31 | public boolean needAsyncInit() {
32 | return false;
33 | }
34 |
35 | /**
36 | * {@link Application#onCreate()} 时调用
37 | */
38 | @Override
39 | public void onCreate() {
40 | }
41 |
42 | @Override
43 | public void asyncOnCreate() {
44 | }
45 |
46 | /**
47 | * {@link Application#onTerminate()} 时调用
48 | */
49 | @Override
50 | public void onTerminate() {
51 | }
52 |
53 | /**
54 | * {@link Application#onConfigurationChanged(Configuration)} 时调用
55 | *
56 | * @param newConfig The new device configuration.
57 | * @see ComponentCallbacks#onConfigurationChanged(Configuration)
58 | */
59 | @Override
60 | public void onConfigurationChanged(Configuration newConfig) {
61 | }
62 |
63 | /**
64 | * {@link Application#onLowMemory()} 时调用
65 | *
66 | * @see ComponentCallbacks#onLowMemory()
67 | */
68 | @Override
69 | public void onLowMemory() {
70 | }
71 |
72 | /**
73 | * {@link Application#onTrimMemory(int)} 时调用
74 | *
75 | * @param level The context of the trim, giving a hint of the amount of trimming the application may like to perform.
76 | * @see ComponentCallbacks2#onTrimMemory(int)
77 | */
78 | @Override
79 | public void onTrimMemory(int level) {
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/api/src/main/java/com/sankuai/erp/component/appinit/api/SimpleAppInitCallback.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.api;
2 |
3 | import com.sankuai.erp.component.appinit.common.AppInitCallback;
4 | import com.sankuai.erp.component.appinit.common.AppInitItem;
5 | import com.sankuai.erp.component.appinit.common.ChildInitTable;
6 |
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | /**
11 | * 作者:王浩
12 | * 创建时间:2018/11/15
13 | * 描述:
14 | */
15 | public abstract class SimpleAppInitCallback implements AppInitCallback {
16 | @Override
17 | public void onInitStart(boolean isMainProcess, String processName) {
18 | }
19 |
20 | @Override
21 | public boolean isDebug() {
22 | return false;
23 | }
24 |
25 | @Override
26 | public Map getCoordinateAheadOfMap() {
27 | return null;
28 | }
29 |
30 | @Override
31 | public void onInitFinished(boolean isMainProcess, String processName, List childInitTableList, List appInitItemList) {
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.2.71'
3 | repositories {
4 | maven { url 'https://jitpack.io' }
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.0.1'
10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | classpath 'com.sankuai.waimai.router:plugin:1.1.0'
13 |
14 | if (project.findProject(':buildSrc') == null) {
15 | classpath 'com.github.bingoogolapple.AppInit:buildSrc:1.0.8'
16 | }
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | maven {
23 | url uri("${System.properties['user.home']}/.gradle/local_repo")
24 | }
25 | maven { url 'https://jitpack.io' }
26 | jcenter()
27 | google()
28 | }
29 |
30 | tasks.withType(JavaCompile) { options.encoding = "UTF-8" }
31 | }
32 |
33 | // 配置 demo
34 | subprojects {
35 | afterEvaluate { project ->
36 | ext.pluginContainer = project.getPlugins()
37 | // 统一配置 Android App 和 Android Library 公共参数
38 | if (ext.pluginContainer.hasPlugin("com.android.application") || ext.pluginContainer.hasPlugin("com.android.library")) {
39 | android {
40 | compileSdkVersion 28
41 |
42 | defaultConfig {
43 | minSdkVersion 16
44 | //noinspection ExpiredTargetSdkVersion
45 | targetSdkVersion 23
46 | versionCode 100
47 | versionName '1.0.0'
48 | if (project.file('consumer-proguard-rules.pro').exists()) {
49 | consumerProguardFiles 'consumer-proguard-rules.pro'
50 | }
51 | }
52 |
53 | compileOptions {
54 | sourceCompatibility '1.8'
55 | targetCompatibility '1.8'
56 | }
57 | }
58 | }
59 |
60 | // 统一配置 Java、groovy、kotlin 公共参数
61 | if (ext.pluginContainer.hasPlugin("java") || ext.pluginContainer.hasPlugin("groovy") || ext.pluginContainer.hasPlugin("kotlin")) {
62 | sourceCompatibility = '1.8'
63 | targetCompatibility = '1.8'
64 | }
65 | }
66 |
67 | def libProjectList = ['common', 'api', 'compiler', 'buildSrc']
68 | if (libProjectList.contains(it.name)) {
69 | return
70 | }
71 |
72 | if (it.name == 'app') {
73 | apply plugin: 'com.android.application'
74 | } else {
75 | apply plugin: 'com.android.library'
76 | }
77 | // 应用 Kotlin 插件,用于测试对 Kotlin 的支持
78 | apply plugin: 'kotlin-android'
79 | apply plugin: 'kotlin-android-extensions'
80 | apply plugin: 'kotlin-kapt'
81 | // 应用 AppInit 插件。如果有用到 Kotlin,需要放到 Kotlin 插件之后
82 | apply plugin: 'bga-appinit-plugin'
83 |
84 | // 测试多 flavor 的情况
85 | // android {
86 | // /**
87 | // * money 场景,免费版和收费版
88 | // * channel 场景,不同的市场渠道
89 | // */
90 | // flavorDimensions 'money', 'channel'
91 | //
92 | // productFlavors {
93 | // free {
94 | // dimension 'money'
95 | // }
96 | // vip {
97 | // dimension 'money'
98 | // }
99 | // baidu {
100 | // dimension 'channel'
101 | // }
102 | // xiaomi {
103 | // dimension 'channel'
104 | // }
105 | // }
106 | // }
107 |
108 | dependencies {
109 | // android support
110 | implementation 'com.android.support:appcompat-v7:28.0.0'
111 | implementation 'com.android.support:support-v4:28.0.0'
112 | // 用于测试对 Kotlin 的支持
113 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
114 |
115 | // 美团外卖 Router 相关,用于测试多模块
116 | implementation 'com.sankuai.waimai.router:router:1.1.2'
117 | annotationProcessor 'com.sankuai.waimai.router:compiler:1.1.2'
118 | kapt 'com.sankuai.waimai.router:compiler:1.1.2'
119 | }
120 | }
121 |
122 | task clean(type: Delete) {
123 | delete rootProject.buildDir
124 | }
125 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'groovy'
2 |
3 | def isStandalone = project.parent == null
4 | if (isStandalone) {
5 | repositories {
6 | maven {
7 | url uri("${System.properties['user.home']}/.gradle/local_repo")
8 | }
9 | jcenter()
10 | google()
11 | mavenCentral()
12 | }
13 | } else {
14 | apply plugin: 'com.github.dcendents.android-maven'
15 | }
16 |
17 | dependencies {
18 | //gradle sdk
19 | implementation gradleApi()
20 | //groovy sdk
21 | implementation localGroovy()
22 |
23 | implementation 'com.android.tools.build:gradle:3.0.1'
24 |
25 | compileOnly 'com.google.android:android:4.1.1.4'
26 | }
27 |
28 | if (isStandalone) {
29 | println "独立,依赖 common 源码"
30 | sourceSets {
31 | main {
32 | java.srcDirs += "${project.rootDir.parent}/common/src/main/java"
33 | }
34 | }
35 | } else {
36 | println "非独立,依赖 common 模块"
37 | dependencies {
38 | implementation project.project(':common')
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/AppInitAsmTransform.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import com.sankuai.erp.component.appinit.common.*
4 | import org.gradle.api.Project
5 | import org.objectweb.asm.ClassWriter
6 |
7 | import java.util.concurrent.CopyOnWriteArrayList
8 |
9 | /**
10 | * 作者:王浩
11 | * 创建时间:2018/11/18
12 | * 描述:
13 | */
14 | class AppInitAsmTransform extends BaseAsmTransform {
15 | private static final String CHILD_INIT_TABLE_ENTRY_NAME = convertCanonicalNameToEntryName(ModuleConsts.CHILD_INIT_TABLE_CANONICAL_NAME, false)
16 | private static final String APP_INIT_MANAGER_ENTRY_NAME = convertCanonicalNameToEntryName(ModuleConsts.APP_INIT_MANAGER_CANONICAL_NAME, false)
17 | private static final String APP_INIT_MANAGER_ENTRY_NAME_WITH_CLASS = convertCanonicalNameToEntryName(ModuleConsts.APP_INIT_MANAGER_CANONICAL_NAME, true)
18 |
19 | private File mAppInitManagerCtClassDest
20 | private File mApplicationCtClassDest
21 |
22 | private String mApplicationEntryName
23 | private String mApplicationEntryNameWithClass
24 | private AppInitExtension mAppInitExtension
25 | private List mChildInitTableClassNameList
26 | private List mChildInitTableList
27 |
28 | AppInitAsmTransform(Project project) {
29 | super(project)
30 | }
31 |
32 | @Override
33 | String getName() {
34 | return "AppInit"
35 | }
36 |
37 | @Override
38 | protected void beforeTransform() {
39 | super.beforeTransform()
40 | mAppInitExtension = mProject.extensions.findByType(AppInitExtension)
41 | if (!AppInitCommonUtils.isEmpty(mAppInitExtension.applicationCanonicalName)) {
42 | mApplicationEntryNameWithClass = convertCanonicalNameToEntryName(mAppInitExtension.applicationCanonicalName, true)
43 | mApplicationEntryName = convertCanonicalNameToEntryName(mAppInitExtension.applicationCanonicalName, false)
44 | }
45 |
46 | mChildInitTableClassNameList = new CopyOnWriteArrayList<>()
47 | mChildInitTableList = new ArrayList<>()
48 |
49 | mAppInitManagerCtClassDest = null
50 | mApplicationCtClassDest = null
51 | }
52 |
53 | @Override
54 | protected boolean shouldScanPath(String path) {
55 | boolean result = path.contains(ModuleConsts.PACKAGE_NAME_GENERATED_SLASH) || path.contains(APP_INIT_MANAGER_ENTRY_NAME_WITH_CLASS)
56 | if (!AppInitCommonUtils.isEmpty(mAppInitExtension.applicationCanonicalName)) {
57 | result = result || path.contains(mApplicationEntryNameWithClass)
58 | }
59 | return result
60 | }
61 |
62 | @Override
63 | protected void scanClass(String name, String superName, String[] interfaces, File dest) {
64 | if (superName == CHILD_INIT_TABLE_ENTRY_NAME) {
65 | mChildInitTableClassNameList.add(name.replace('/', '.'))
66 | AppInitLogger.d "找到了子表 ${name}"
67 | } else if (name == APP_INIT_MANAGER_ENTRY_NAME) {
68 | mAppInitManagerCtClassDest = dest
69 | AppInitLogger.d "找到了 AppInitManager ${name},存放位置为 ${dest}"
70 | } else if (name == mApplicationEntryName) {
71 | mApplicationCtClassDest = dest
72 | mApplicationEntryNameWithClass = "${name}.class"
73 | AppInitLogger.d "找到了 Application ${name},存放位置为 ${dest}"
74 | }
75 | }
76 |
77 | @Override
78 | protected void handle() {
79 | if (mAppInitManagerCtClassDest == null || !mAppInitManagerCtClassDest.exists()) {
80 | throw new IllegalStateException("未找到 ${ModuleConsts.APP_INIT_MANAGER_CANONICAL_NAME}")
81 | }
82 | AppInitLogger.d "mAppInitManagerCtClassDest 为 ${mAppInitManagerCtClassDest.absolutePath}"
83 | // 修改 AppInitManager,一定是在 jar 包里
84 | modifyJarClass(mAppInitManagerCtClassDest)
85 |
86 | // 处理 Application 自动注入
87 | if (mApplicationCtClassDest == null || !mApplicationCtClassDest.exists()) {
88 | // 未找到 AndroidManifest.xml 中配置的 Application,不自动注入字节码
89 | return
90 | }
91 | AppInitLogger.d "mApplicationCtClassDest 为 ${mApplicationCtClassDest.absolutePath}"
92 | // 修改 Application,可能在 jar 包里,也可能在壳工程的目录里
93 | if (mApplicationCtClassDest.isDirectory()) {
94 | modifyDirectoryClass(mApplicationCtClassDest, mApplicationEntryNameWithClass)
95 | } else {
96 | modifyJarClass(mApplicationCtClassDest)
97 | }
98 | }
99 |
100 | @Override
101 | protected byte[] modifyDirectoryClass(InputStream is, String entryName) {
102 | if (entryName == mApplicationEntryNameWithClass) { // 修改 Application
103 | return modifyClass(is) { new ApplicationAsmKnife.ApplicationClassVisitor(it) }
104 | }
105 | return null
106 | }
107 |
108 | @Override
109 | protected byte[] modifyJarClass(File jarFile, InputStream jarEntryInputStream, String entryName) {
110 | if (entryName == APP_INIT_MANAGER_ENTRY_NAME_WITH_CLASS) { // 修改 AppInitManager
111 | for (String childInitTableClassName : mChildInitTableClassNameList) {
112 | mChildInitTableList.add(loadClass(childInitTableClassName).newInstance(0))
113 | }
114 | List appInitItemList = ChildInitTableSortUtils.sortMasterInitTable(mAppInitExtension, mChildInitTableList, mLogSb)
115 | return modifyClass(jarEntryInputStream) { ClassWriter classWriter ->
116 | new AppInitManagerAsmKnife.AppInitManagerClassVisitor(classWriter, mChildInitTableList, appInitItemList, mAppInitExtension.getAbortOnNotExist())
117 | }
118 | } else if (entryName == mApplicationEntryNameWithClass) { // 修改 Application
119 | return modifyClass(jarEntryInputStream) { new ApplicationAsmKnife.ApplicationClassVisitor(it) }
120 | }
121 | return null
122 | }
123 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/AppInitExtension.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import com.sankuai.erp.component.appinit.common.AppInitCommonUtils
4 | /**
5 | * 作者:王浩
6 | * 创建时间:2018/11/2
7 | * 描述:
8 | */
9 | class AppInitExtension {
10 | private boolean mAbortOnNotExist = true
11 | /**
12 | * AndroidManifest.xml 中配置的 Application 的类全名,配置后注入字节码自动回调 AppInitManager 对应的 Application 生命周期方法
13 | */
14 | private String mApplicationCanonicalName
15 | /**
16 | * 编译期自定义模块间初始化的依赖关系,键是 coordinate,值是 dependencyCoordinate
17 | */
18 | final Map> dependencyMap = new HashMap<>()
19 |
20 | /**
21 | * AndroidManifest.xml 中配置的 Application 的类全名,配置后注入字节码自动回调 AppInitManager 对应的 Application 生命周期方法
22 | */
23 | void applicationCanonicalName(String applicationCanonicalName) {
24 | mApplicationCanonicalName = applicationCanonicalName
25 | }
26 |
27 | String getApplicationCanonicalName() {
28 | return mApplicationCanonicalName
29 | }
30 |
31 | boolean getAbortOnNotExist() {
32 | return mAbortOnNotExist
33 | }
34 |
35 | /**
36 | * 依赖的模块或 aheadOf 指定的初始化不存在时是否中断编译
37 | */
38 | void abortOnNotExist(boolean abortOnNotExist) {
39 | mAbortOnNotExist = abortOnNotExist
40 | }
41 |
42 | /**
43 | * 自定义模块间初始化的依赖关系,键是 coordinate,值是被依赖的 coordinate
44 | */
45 | void dependency(String coordinate, String dependencyCoordinate) {
46 | if (AppInitCommonUtils.isEmpty(coordinate)) {
47 | throw new IllegalArgumentException("appInit {\n dependency 「第一个参数 coordinate 不能为空」\n}")
48 | }
49 | if (AppInitCommonUtils.isEmpty(dependencyCoordinate)) {
50 | throw new IllegalArgumentException("appInit {\n dependency 「第二个参数 dependencyCoordinate 不能为空」\n}")
51 | }
52 | getDependencyCoordinateSet(coordinate).add(dependencyCoordinate)
53 | }
54 |
55 | /**
56 | * 自定义模块间初始化的依赖关系,键是 coordinate,值是被依赖的 coordinate 集合
57 | */
58 | void dependency(String coordinate, Collection dependencyCoordinateCollection) {
59 | if (AppInitCommonUtils.isEmpty(coordinate)) {
60 | throw new IllegalArgumentException("appInit {\n dependency 「第一个参数 coordinate 不能为空」\n}")
61 | }
62 | if (dependencyCoordinateCollection == null || dependencyCoordinateCollection.isEmpty()) {
63 | throw new IllegalArgumentException("appInit {\n dependency 「第二个参数 dependencyCoordinateCollection 不能为空」\n}")
64 | }
65 | getDependencyCoordinateSet(coordinate).addAll(dependencyCoordinateCollection)
66 | }
67 |
68 | /**
69 | * 自定义模块间初始化的依赖关系
70 | */
71 | void dependency(Map> dependencyMap) {
72 | if (dependencyMap == null || dependencyMap.isEmpty()) {
73 | return
74 | }
75 |
76 | for (Map.Entry> entry : dependencyMap.entrySet()) {
77 | if (AppInitCommonUtils.isEmpty(entry.getKey()) || entry.getValue() == null || entry.getValue().isEmpty()) {
78 | continue
79 | }
80 | dependency(entry.getKey(), entry.getValue())
81 | }
82 | }
83 |
84 | private Set getDependencyCoordinateSet(String coordinate) {
85 | Set dependencyCoordinateSet = dependencyMap.get(coordinate)
86 | if (dependencyCoordinateSet == null) {
87 | dependencyCoordinateSet = new HashSet<>()
88 | dependencyMap.put(coordinate, dependencyCoordinateSet)
89 | }
90 | return dependencyCoordinateSet
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/AppInitManagerAsmKnife.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import com.sankuai.erp.component.appinit.common.AppInitItem
4 | import com.sankuai.erp.component.appinit.common.ChildInitTable
5 | import com.sankuai.erp.component.appinit.common.ModuleConsts
6 | import org.objectweb.asm.*
7 |
8 | /**
9 | * 作者:王浩
10 | * 创建时间:2018/11/18
11 | * 描述:修改 AppInitManager 字节码
12 | */
13 | class AppInitManagerAsmKnife {
14 |
15 | static class AppInitManagerClassVisitor extends ClassVisitor {
16 | private List mChildInitTableList
17 | private List mAppInitItemList
18 | private boolean mAbortOnNotExist
19 |
20 | AppInitManagerClassVisitor(ClassVisitor cv, List childInitTableList, List appInitItemList, boolean abortOnNotExist) {
21 | super(Opcodes.ASM5, cv)
22 | mChildInitTableList = childInitTableList
23 | mAppInitItemList = appInitItemList
24 | mAbortOnNotExist = abortOnNotExist
25 | }
26 |
27 | @Override
28 | MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
29 | MethodVisitor result = super.visitMethod(access, name, desc, signature, exceptions)
30 | if (ModuleConsts.METHOD_INJECT_CHILD_INIT_TABLE_LIST == name) {
31 | result = new InjectChildInitTableListMethodVisitor(result, mChildInitTableList, mAbortOnNotExist)
32 | } else if (ModuleConsts.METHOD_INJECT_APP_INIT_ITEM_LIST == name) {
33 | result = new InjectAppInitItemListMethodVisitor(result, mAppInitItemList)
34 | }
35 | return result
36 | }
37 | }
38 |
39 | private static class InjectChildInitTableListMethodVisitor extends MethodVisitor {
40 | private List mChildInitTableList
41 | private boolean mAbortOnNotExist
42 |
43 | InjectChildInitTableListMethodVisitor(MethodVisitor mv, List childInitTableList, boolean abortOnNotExist) {
44 | super(Opcodes.ASM5, mv)
45 | mChildInitTableList = childInitTableList
46 | mAbortOnNotExist = abortOnNotExist
47 | }
48 |
49 | @Override
50 | void visitInsn(int opcode) {
51 | if (opcode == Opcodes.RETURN) {
52 | // 修改 mAbortOnNotExist
53 | mv.visitVarInsn(Opcodes.ALOAD, 0)
54 | if (mAbortOnNotExist) {
55 | mv.visitInsn(Opcodes.ICONST_1)
56 | } else {
57 | mv.visitInsn(Opcodes.ICONST_0)
58 | }
59 | mv.visitFieldInsn(Opcodes.PUTFIELD, "com/sankuai/erp/component/appinit/api/AppInitManager", "mAbortOnNotExist", "Z")
60 |
61 | // 注入 mChildInitTableList
62 | String childInitTableEntryName
63 | mChildInitTableList.each { childInitTable ->
64 | childInitTableEntryName = BaseTransform.convertCanonicalNameToEntryName(childInitTable.class.name, false)
65 | mv.visitVarInsn(Opcodes.ALOAD, 0)
66 | mv.visitFieldInsn(Opcodes.GETFIELD, "com/sankuai/erp/component/appinit/api/AppInitManager", "mChildInitTableList", "Ljava/util/List;")
67 | mv.visitTypeInsn(Opcodes.NEW, childInitTableEntryName)
68 | mv.visitInsn(Opcodes.DUP)
69 | mv.visitIntInsn(Opcodes.SIPUSH, childInitTable.priority)
70 | mv.visitMethodInsn(Opcodes.INVOKESPECIAL, childInitTableEntryName, "", "(I)V", false)
71 | mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true)
72 | mv.visitInsn(Opcodes.POP)
73 | }
74 | }
75 | super.visitInsn(opcode)
76 | }
77 | }
78 |
79 | private static class InjectAppInitItemListMethodVisitor extends MethodVisitor {
80 | private List mAppInitItemList
81 |
82 | InjectAppInitItemListMethodVisitor(MethodVisitor mv, List appInitItemList) {
83 | super(Opcodes.ASM5, mv)
84 | mAppInitItemList = appInitItemList
85 | }
86 |
87 | @Override
88 | void visitInsn(int opcode) {
89 | if (opcode == Opcodes.RETURN) {
90 | String appInitEntryName
91 | mAppInitItemList.each { appInitItem ->
92 | appInitEntryName = BaseTransform.convertCanonicalNameToEntryName(appInitItem.appInitClassName, false)
93 |
94 | mv.visitVarInsn(Opcodes.ALOAD, 0)
95 | mv.visitFieldInsn(Opcodes.GETFIELD, "com/sankuai/erp/component/appinit/api/AppInitManager", "mAppInitItemList", "Ljava/util/List;")
96 | mv.visitTypeInsn(Opcodes.NEW, "com/sankuai/erp/component/appinit/common/AppInitItem")
97 | mv.visitInsn(Opcodes.DUP)
98 | mv.visitTypeInsn(Opcodes.NEW, appInitEntryName)
99 | mv.visitInsn(Opcodes.DUP)
100 | mv.visitMethodInsn(Opcodes.INVOKESPECIAL, appInitEntryName, "", "()V", false)
101 | mv.visitIntInsn(Opcodes.SIPUSH, appInitItem.process.ordinal())
102 | mv.visitIntInsn(Opcodes.SIPUSH, appInitItem.priority)
103 | mv.visitLdcInsn(appInitItem.coordinate)
104 | mv.visitLdcInsn(appInitItem.aheadOf)
105 | mv.visitLdcInsn(appInitItem.description)
106 | mv.visitLdcInsn(appInitItem.onlyForDebug.toString())
107 | mv.visitLdcInsn(appInitItem.moduleCoordinate)
108 | mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/sankuai/erp/component/appinit/common/AppInitItem", "",
109 | "(Lcom/sankuai/erp/component/appinit/common/IAppInit;IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
110 | false)
111 | mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true)
112 | mv.visitInsn(Opcodes.POP)
113 | }
114 | }
115 | super.visitInsn(opcode)
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/AppInitPlugin.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import com.sankuai.erp.component.appinit.common.AppInitLogger
4 | import com.sankuai.erp.component.appinit.common.ModuleConsts
5 |
6 | /**
7 | * 「app 初始化、多模块初始化」插件
8 | */
9 | class AppInitPlugin extends BaseAptPlugin {
10 | private static final String VERSION_NAME = "1.0.8"
11 |
12 | @Override
13 | protected void handleMasterModule() {
14 | AppInitLogger.sLogger = new Logger(mProject)
15 | mProject.extensions.create('appInit', AppInitExtension)
16 |
17 | // mProject.android.registerTransform(new AppInitJavassistTransform(mProject))
18 | mProject.android.registerTransform(new AppInitAsmTransform(mProject))
19 | }
20 |
21 | @Override
22 | protected String getAptDebugKey() {
23 | // gradle.properties 中添加该属性来配置是否处于调试 apt 模式
24 | return "DEBUG_APP_INIT_APT"
25 | }
26 |
27 | @Override
28 | protected String getGroupId() {
29 | return "com.github.bingoogolapple.AppInit"
30 | }
31 |
32 | @Override
33 | protected String getPomVersionName() {
34 | return VERSION_NAME
35 | }
36 |
37 | @Override
38 | protected String getAptModuleCoordinateKey() {
39 | return ModuleConsts.APT_MODULE_COORDINATE_KEY
40 | }
41 |
42 | @Override
43 | protected String getAptDependenciesKey() {
44 | return ModuleConsts.APT_DEPENDENCIES_KEY
45 | }
46 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/ApplicationAsmKnife.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import org.objectweb.asm.*
4 |
5 | /**
6 | * 作者:王浩
7 | * 创建时间:2018/11/15
8 | * 描述:修改 Application 字节码
9 | */
10 | class ApplicationAsmKnife {
11 |
12 | static class ApplicationClassVisitor extends ClassVisitor {
13 | private InjectNoParameterMethodVisitor mOnTerminateMethodVisitor
14 | private OnConfigurationChangedMethodVisitor mOnConfigurationChangedMethodVisitor
15 | private InjectNoParameterMethodVisitor mOnLowMemoryMethodVisitor
16 | private OnTrimMemoryMethodVisitor mOnTrimMemoryMethodVisitor
17 |
18 | ApplicationClassVisitor(ClassVisitor cv) {
19 | super(Opcodes.ASM5, cv)
20 | }
21 |
22 | @Override
23 | MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
24 | MethodVisitor result = super.visitMethod(access, name, desc, signature, exceptions)
25 | if ('onTerminate' == name) {
26 | result = mOnTerminateMethodVisitor = new InjectNoParameterMethodVisitor(result, name)
27 | } else if ('onConfigurationChanged' == name) {
28 | result = mOnConfigurationChangedMethodVisitor = new OnConfigurationChangedMethodVisitor(result)
29 | } else if ('onLowMemory' == name) {
30 | result = mOnLowMemoryMethodVisitor = new InjectNoParameterMethodVisitor(result, name)
31 | } else if ('onTrimMemory' == name) {
32 | result = mOnTrimMemoryMethodVisitor = new OnTrimMemoryMethodVisitor(result)
33 | }
34 | return result
35 | }
36 |
37 | @Override
38 | void visitEnd() {
39 | if (mOnTerminateMethodVisitor == null) {
40 | InjectNoParameterMethodVisitor.visitMethodOnNotExist(this, 'onTerminate')
41 | }
42 | if (mOnConfigurationChangedMethodVisitor == null) {
43 | OnConfigurationChangedMethodVisitor.visitMethodOnNotExist(this)
44 | }
45 | if (mOnLowMemoryMethodVisitor == null) {
46 | InjectNoParameterMethodVisitor.visitMethodOnNotExist(this, 'onLowMemory')
47 | }
48 | if (mOnTrimMemoryMethodVisitor == null) {
49 | OnTrimMemoryMethodVisitor.visitMethodOnNotExist(this)
50 | }
51 | super.visitEnd()
52 | }
53 | }
54 |
55 | private static class OnConfigurationChangedMethodVisitor extends MethodVisitor {
56 |
57 | OnConfigurationChangedMethodVisitor(MethodVisitor mv) {
58 | super(Opcodes.ASM5, mv)
59 | }
60 |
61 | static void visitMethodOnNotExist(ClassVisitor classVisitor) {
62 | OnConfigurationChangedMethodVisitor methodVisitor = classVisitor.visitMethod(Opcodes.ACC_PUBLIC, 'onConfigurationChanged', "(Landroid/content/res/Configuration;)V", null, null)
63 | methodVisitor.visitCode()
64 | methodVisitor.callSuper()
65 | methodVisitor.visitInsn(Opcodes.RETURN)
66 | methodVisitor.visitEnd()
67 | }
68 |
69 | void callSuper() {
70 | mv.visitVarInsn(Opcodes.ALOAD, 0)
71 | mv.visitVarInsn(Opcodes.ALOAD, 1)
72 | mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "android/app/Application", 'onConfigurationChanged', "(Landroid/content/res/Configuration;)V", false)
73 | }
74 |
75 | @Override
76 | void visitInsn(int opcode) {
77 | if (opcode == Opcodes.RETURN) {
78 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/sankuai/erp/component/appinit/api/AppInitManager", "get", "()Lcom/sankuai/erp/component/appinit/api/AppInitManager;", false)
79 | mv.visitVarInsn(Opcodes.ALOAD, 1)
80 | mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/sankuai/erp/component/appinit/api/AppInitManager", 'onConfigurationChanged', "(Landroid/content/res/Configuration;)V", false)
81 | }
82 | super.visitInsn(opcode)
83 | }
84 | }
85 |
86 | private static class OnTrimMemoryMethodVisitor extends MethodVisitor {
87 |
88 | OnTrimMemoryMethodVisitor(MethodVisitor mv) {
89 | super(Opcodes.ASM5, mv)
90 | }
91 |
92 | static void visitMethodOnNotExist(ClassVisitor classVisitor) {
93 | OnTrimMemoryMethodVisitor methodVisitor = classVisitor.visitMethod(Opcodes.ACC_PUBLIC, 'onTrimMemory', "(I)V", null, null)
94 | methodVisitor.visitCode()
95 | methodVisitor.callSuper()
96 | methodVisitor.visitInsn(Opcodes.RETURN)
97 | methodVisitor.visitEnd()
98 | }
99 |
100 | void callSuper() {
101 | mv.visitVarInsn(Opcodes.ALOAD, 0)
102 | mv.visitVarInsn(Opcodes.ILOAD, 1)
103 | mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "android/app/Application", 'onTrimMemory', "(I)V", false)
104 | }
105 |
106 | @Override
107 | void visitInsn(int opcode) {
108 | if (opcode == Opcodes.RETURN) {
109 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/sankuai/erp/component/appinit/api/AppInitManager", "get", "()Lcom/sankuai/erp/component/appinit/api/AppInitManager;", false)
110 | mv.visitVarInsn(Opcodes.ILOAD, 1)
111 | mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/sankuai/erp/component/appinit/api/AppInitManager", 'onTrimMemory', "(I)V", false)
112 | }
113 | super.visitInsn(opcode)
114 | }
115 | }
116 |
117 | private static class InjectNoParameterMethodVisitor extends MethodVisitor {
118 | private String mName
119 |
120 | InjectNoParameterMethodVisitor(MethodVisitor mv, String name) {
121 | super(Opcodes.ASM5, mv)
122 | mName = name
123 | }
124 |
125 | static void visitMethodOnNotExist(ClassVisitor classVisitor, String name) {
126 | InjectNoParameterMethodVisitor methodVisitor = classVisitor.visitMethod(Opcodes.ACC_PUBLIC, name, "()V", null, null)
127 | methodVisitor.visitCode()
128 | methodVisitor.callSuper()
129 | methodVisitor.visitInsn(Opcodes.RETURN)
130 | methodVisitor.visitEnd()
131 | }
132 |
133 | void callSuper() {
134 | mv.visitVarInsn(Opcodes.ALOAD, 0)
135 | mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "android/app/Application", mName, "()V", false)
136 | }
137 |
138 | @Override
139 | void visitInsn(int opcode) {
140 | if (opcode == Opcodes.RETURN) {
141 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/sankuai/erp/component/appinit/api/AppInitManager", "get", "()Lcom/sankuai/erp/component/appinit/api/AppInitManager;", false)
142 | mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/sankuai/erp/component/appinit/api/AppInitManager", mName, "()V", false)
143 | }
144 | super.visitInsn(opcode)
145 | }
146 | }
147 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/BaseAptPlugin.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import com.sankuai.erp.component.appinit.common.AppInitCommonUtils
4 | import org.gradle.api.Plugin
5 | import org.gradle.api.Project
6 |
7 | /**
8 | * 作者:王浩
9 | * 创建时间:2018/1/18
10 | * 描述:
11 | * 1.配置模块名称参数,用于在 apt 中读取
12 | * 2.添加依赖
13 | */
14 | abstract class BaseAptPlugin implements Plugin {
15 | private static final String GROUP = "GROUP"
16 | private static final String POM_GROUP_ID = "POM_GROUP_ID"
17 | private static final String POM_ARTIFACT_ID = "POM_ARTIFACT_ID"
18 | protected Project mProject
19 |
20 | @Override
21 | void apply(Project project) {
22 | if (!hasAndroidPlugin(project)) {
23 | return
24 | }
25 |
26 | mProject = project
27 | configCompileOptions()
28 | addDependencies()
29 |
30 | if (hasAppPlugin(project)) {
31 | handleMasterModule()
32 | }
33 | }
34 |
35 | protected void configCompileOptions() {
36 | addConfigCompileOption(getAptModuleCoordinateKey(), getAptModuleCoordinate())
37 |
38 | String aptDependenciesKey = getAptDependenciesKey()
39 | if (aptDependenciesKey != null && aptDependenciesKey.length() > 0 && mProject.hasProperty(aptDependenciesKey)) {
40 | addConfigCompileOption(aptDependenciesKey, mProject.property(aptDependenciesKey))
41 | }
42 | }
43 |
44 | /**
45 | * 处理壳工程
46 | */
47 | protected abstract void handleMasterModule()
48 |
49 | /**
50 | * 添加编译选项
51 | */
52 | protected void addConfigCompileOption(String key, String value) {
53 | mProject.android.defaultConfig {
54 | javaCompileOptions {
55 | annotationProcessorOptions {
56 | if (arguments == null) {
57 | arguments = [(key): value]
58 | } else {
59 | arguments.put((key), value)
60 | }
61 | }
62 | }
63 | }
64 | }
65 |
66 | protected abstract String getAptDebugKey()
67 |
68 | protected abstract String getGroupId()
69 |
70 | protected abstract String getPomVersionName()
71 |
72 | protected abstract String getAptModuleCoordinateKey()
73 |
74 | protected abstract String getAptDependenciesKey()
75 |
76 | /**
77 | * 添加依赖
78 | */
79 | private void addDependencies() {
80 | if (isDebugApt()) {
81 | info "调试 Apt"
82 | mProject.dependencies.add('implementation', mProject.project(':api'))
83 | mProject.dependencies.add('annotationProcessor', mProject.project(':compiler'))
84 | if (hasKotlinAndroidPlugin(mProject)) {
85 | mProject.dependencies.add('kapt', mProject.project(':compiler'))
86 | }
87 | } else {
88 | info "不调试 Apt"
89 | mProject.dependencies.add('implementation', "${getGroupId()}:api:${getPomVersionName()}")
90 | mProject.dependencies.add('annotationProcessor', "${getGroupId()}:compiler:${getPomVersionName()}")
91 | if (hasKotlinAndroidPlugin(mProject)) {
92 | mProject.dependencies.add('kapt', "${getGroupId()}:compiler:${getPomVersionName()}")
93 | }
94 | }
95 | }
96 |
97 | private String getAptModuleCoordinate() {
98 | String pomCoordinate = getPomCoordinate()
99 | if (!AppInitCommonUtils.isEmpty(pomCoordinate)) {
100 | return pomCoordinate
101 | }
102 | return getAptModuleName()
103 | }
104 |
105 | private String getPomCoordinate() {
106 | String groupId = getProjectProperty(POM_GROUP_ID)
107 | String artifactId = getProjectProperty(POM_ARTIFACT_ID)
108 | // 兼容集团 CI 发布 maven
109 | if (AppInitCommonUtils.isEmpty(groupId)) {
110 | groupId = getProjectProperty(GROUP)
111 | }
112 | if (AppInitCommonUtils.isEmpty(groupId) || AppInitCommonUtils.isEmpty(artifactId)) {
113 | return null
114 | }
115 | return "${groupId}:${artifactId}"
116 | }
117 |
118 | private String getProjectProperty(String propertyName) {
119 | if (mProject.hasProperty(propertyName)) {
120 | return mProject.property(propertyName)
121 | }
122 | return null
123 | }
124 |
125 | /**
126 | * 获取 apt 需要的模块名称
127 | */
128 | private String getAptModuleName() {
129 | String groupId = mProject.projectDir.parentFile.name
130 | // CI 上构建时会多出「@并发构建编号」
131 | if (groupId.contains('@')) {
132 | println("rootProject 名称 ${mProject.rootProject.name}")
133 | groupId = groupId.substring(0, groupId.lastIndexOf('@'))
134 | }
135 | if ('0123456789'.contains(groupId.substring(0, 1))) {
136 | groupId = "Dummy$groupId"
137 | }
138 | return "${groupId}:${mProject.name}"
139 | }
140 |
141 | /**
142 | * 是否调试 apt
143 | */
144 | private boolean isDebugApt() {
145 | return mProject.hasProperty(getAptDebugKey()) && Boolean.valueOf(mProject.property(getAptDebugKey()))
146 | }
147 |
148 | private void info(String msg) {
149 | println "「${this.class.simpleName}」「${mProject.path}」「${getAptModuleName()}」=> ${msg}"
150 | }
151 |
152 | static boolean hasAndroidPlugin(Project project) {
153 | return hasAppPlugin(project) || hasLibraryPlugin(project)
154 | }
155 |
156 | static boolean hasAppPlugin(Project project) {
157 | return project.plugins.hasPlugin('com.android.application')
158 | }
159 |
160 | static boolean hasLibraryPlugin(Project project) {
161 | return project.plugins.hasPlugin('com.android.library')
162 | }
163 |
164 | static boolean hasKotlinAndroidPlugin(Project project) {
165 | return project.plugins.hasPlugin('kotlin-android')
166 | }
167 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/BaseAsmTransform.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import org.gradle.api.Project
4 | import org.objectweb.asm.ClassReader
5 | import org.objectweb.asm.ClassVisitor
6 | import org.objectweb.asm.ClassWriter
7 | import org.objectweb.asm.Opcodes
8 |
9 | /**
10 | * 作者:王浩
11 | * 创建时间:2018/11/18
12 | * 描述:
13 | */
14 | abstract class BaseAsmTransform extends BaseTransform {
15 |
16 | BaseAsmTransform(Project project) {
17 | super(project)
18 | }
19 |
20 | @Override
21 | protected void scanClass(InputStream inputStream, File dest) {
22 | new ClassReader(inputStream).accept(new ClassVisitor(Opcodes.ASM5) {
23 | @Override
24 | void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
25 | super.visit(version, access, name, signature, superName, interfaces)
26 | scanClass(name, superName, interfaces, dest)
27 | }
28 | }, ClassReader.EXPAND_FRAMES)
29 | }
30 |
31 | protected abstract void scanClass(String name, String superName, String[] interfaces, File dest)
32 |
33 | protected void modifyDirectoryClass(File destDir, String entryName) {
34 | File classFile = new File(destDir, entryName)
35 | new FileInputStream(classFile).withCloseable { fis ->
36 | byte[] bytes = modifyDirectoryClass(fis, entryName)
37 | if (bytes == null) {
38 | return
39 | }
40 | new FileOutputStream(classFile).withCloseable { fos ->
41 | fos.write(bytes)
42 | }
43 | }
44 | }
45 |
46 | protected byte[] modifyDirectoryClass(InputStream is, String entryName) {
47 | }
48 |
49 | protected static byte[] modifyClass(InputStream is, Closure closure) {
50 | ClassReader classReader = new ClassReader(is)
51 | ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS)
52 | classReader.accept(closure.call(classWriter), ClassReader.EXPAND_FRAMES)
53 | return classWriter.toByteArray()
54 | }
55 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/BaseTransform.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import com.android.SdkConstants
4 | import com.android.build.api.transform.*
5 | import com.android.build.gradle.internal.pipeline.TransformManager
6 | import com.sankuai.erp.component.appinit.common.AppInitCommonUtils
7 | import com.sankuai.erp.component.appinit.common.AppInitLogger
8 | import groovy.io.FileType
9 | import org.apache.commons.codec.digest.DigestUtils
10 | import org.apache.commons.io.FileUtils
11 | import org.apache.commons.io.IOUtils
12 | import org.gradle.api.Project
13 | import org.gradle.internal.Pair
14 |
15 | import java.text.SimpleDateFormat
16 | import java.util.concurrent.CyclicBarrier
17 | import java.util.concurrent.Executor
18 | import java.util.concurrent.Executors
19 | import java.util.function.Consumer
20 | import java.util.jar.JarEntry
21 | import java.util.jar.JarFile
22 | import java.util.jar.JarOutputStream
23 | import java.util.zip.ZipEntry
24 |
25 | /**
26 | * 作者:王浩
27 | * 创建时间:2018/11/6
28 | * 描述:
29 | */
30 | abstract class BaseTransform extends Transform {
31 | protected final Set mExcludeJarSet = ["com.android.support", "android.arch.", "androidx."]
32 | protected static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
33 | protected final Project mProject
34 | protected GroovyClassLoader mUrlClassLoader
35 | protected String mVariant
36 | protected StringBuilder mLogSb
37 | protected File mLogDir
38 |
39 | BaseTransform(Project project) {
40 | mProject = project
41 |
42 | mLogDir = new File(mProject.buildDir, "${getName()}Log")
43 | if (mLogDir.exists()) {
44 | mLogDir.delete()
45 | }
46 | mLogDir.mkdirs()
47 | }
48 |
49 | /**
50 | * 用于指明本 Transform 的名字,也是代表该 Transform 的 Task 的名字。transformClassesWith「getName()」For「Free|Vip」「Baidu|Xiaomi」「Debug|Release」
51 | */
52 | @Override
53 | String getName() {
54 | return this.class.simpleName.replace('Transform', '')
55 | }
56 |
57 | /**
58 | * 用于指明 Transform 的输入类型,可以作为输入过滤的手段。
59 | */
60 | @Override
61 | Set getInputTypes() {
62 | return TransformManager.CONTENT_CLASS
63 | }
64 |
65 | /**
66 | * 用于指明 Transform 的作用域
67 | */
68 | @Override
69 | Set getScopes() {
70 | return TransformManager.SCOPE_FULL_PROJECT
71 | }
72 |
73 | /**
74 | * 用于指明是否是增量构建
75 | */
76 | @Override
77 | boolean isIncremental() {
78 | return false
79 | }
80 |
81 | protected void updateVariant(TransformInvocation transformInvocation) {
82 | // transformInvocation.context 为 TransformTask
83 | mVariant = transformInvocation.context.getVariantName()
84 | AppInitLogger.d "变体为 ${mVariant}"
85 | }
86 |
87 | protected static String getDestJarName(JarInput jarInput) {
88 | String destJarName = jarInput.name
89 | if (destJarName.endsWith(".jar")) {
90 | destJarName = "${destJarName.substring(0, destJarName.length() - 4)}_${DigestUtils.md5Hex(jarInput.file.absolutePath)}"
91 | }
92 | return destJarName
93 | }
94 |
95 | protected static void transformAndroidTest(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
96 | transformInput(transformInvocation, new Consumer() {
97 | @Override
98 | void accept(JarInput jarInput) {
99 | FileUtils.copyFile(jarInput.file, transformInvocation.outputProvider.getContentLocation(getDestJarName(jarInput), jarInput.contentTypes, jarInput.scopes, Format.JAR))
100 | }
101 | }, new Consumer() {
102 | @Override
103 | void accept(DirectoryInput directoryInput) {
104 | FileUtils.copyDirectory(directoryInput.file, transformInvocation.outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY))
105 | }
106 | })
107 | }
108 |
109 | @Override
110 | void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
111 | if (!transformInvocation.isIncremental()) {
112 | transformInvocation.getOutputProvider().deleteAll()
113 | }
114 |
115 | updateVariant(transformInvocation)
116 | if (mVariant.toLowerCase().endsWith('androidtest')) {
117 | transformAndroidTest(transformInvocation)
118 | return
119 | }
120 |
121 | mLogSb = new StringBuilder(SDF.format(new Date())).append('\n\n')
122 |
123 | String scanTime = ''
124 | String handleTime = ''
125 | String transformTime = AppInitCommonUtils.timeStr("transform ") {
126 | beforeTransform()
127 | try {
128 | scanTime = AppInitCommonUtils.timeStr("scan ") {
129 | scan(transformInvocation)
130 | }
131 | handleTime = AppInitCommonUtils.timeStr("handle ") {
132 | handle()
133 | }
134 | } finally {
135 | afterTransform()
136 | mUrlClassLoader.clearCache()
137 | mUrlClassLoader.close()
138 | }
139 | }
140 | mLogSb.append(scanTime).append(handleTime).append(transformTime)
141 | FileUtils.writeStringToFile(new File(mLogDir, "${mVariant}.log"), mLogSb.toString())
142 | }
143 |
144 | protected void scan(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
145 | transformInput(transformInvocation, new Consumer() {
146 | @Override
147 | void accept(JarInput jarInput) {
148 | handleJarInput(jarInput, transformInvocation)
149 | }
150 | }, new Consumer() {
151 | @Override
152 | void accept(DirectoryInput directoryInput) {
153 | handleDirectoryInput(directoryInput, transformInvocation)
154 | }
155 | })
156 | }
157 |
158 | protected void beforeTransform() {
159 | // 每个 Variant 都会执行一次 transform 方法,在 beforeTransform 时重新创建 ClassLoader,避免多个 Variant 中存在相同类时加载类异常
160 | mUrlClassLoader = new GroovyClassLoader()
161 | // /Applications/develop/AndroidSDK/platforms/android-28/android.jar
162 | String androidBootClasspath = mProject.android.bootClasspath[0].toString()
163 | mUrlClassLoader.addClasspath(androidBootClasspath)
164 | }
165 |
166 | protected void afterTransform() {
167 | }
168 |
169 | protected abstract void handle()
170 |
171 | protected void handleJarInput(JarInput jarInput, TransformInvocation transformInvocation) {
172 | mUrlClassLoader.addClasspath(jarInput.file.absolutePath)
173 |
174 | String destJarName = getDestJarName(jarInput)
175 | // /Users/wanghao/git/AndroidStudio/xmd/erp-components-android/appinit/demo/app/build/intermediates/transforms/AppInit/vipBaidu/debug/0.jar
176 | // /Users/wanghao/git/AndroidStudio/xmd/erp-components-android/appinit/demo/app/build/intermediates/transforms/AppInit/baidu/debug/0.jar
177 | // /Users/wanghao/git/AndroidStudio/xmd/erp-components-android/appinit/demo/app/build/intermediates/transforms/AppInit/debug/0.jar
178 | File destJarFile = transformInvocation.outputProvider.getContentLocation(destJarName, jarInput.contentTypes, jarInput.scopes, Format.JAR)
179 |
180 | if (shouldScanJar(jarInput)) {
181 | scanJar(jarInput.file, destJarFile)
182 | }
183 |
184 | FileUtils.copyFile(jarInput.file, destJarFile)
185 | }
186 |
187 | protected void scanJar(File jarInputFile, File destJarFile) {
188 | try {
189 | new JarFile(jarInputFile).withCloseable { JarFile jarFile ->
190 | Enumeration enumeration = jarFile.entries()
191 | while (enumeration.hasMoreElements()) {
192 | JarEntry jarEntry = enumeration.nextElement()
193 | if (shouldScanPathInternal(jarEntry.name)) {
194 | jarFile.getInputStream(jarEntry).withCloseable {
195 | scanClass(it, destJarFile)
196 | }
197 | }
198 | }
199 | }
200 | } catch (Exception e) {
201 | AppInitLogger.e "扫描 jar 包(${jarInputFile.absolutePath})异常,请检查在此之前是否有其他插件报错"
202 | e.printStackTrace();
203 | }
204 | }
205 |
206 | protected void handleDirectoryInput(DirectoryInput directoryInput, TransformInvocation transformInvocation) {
207 | mUrlClassLoader.addClasspath(directoryInput.file.absolutePath)
208 | // /Users/wanghao/git/AndroidStudio/xmd/erp-components-android/appinit/demo/app/build/intermediates/transforms/AppInit/vipXiaomi/debug/18
209 | // /Users/wanghao/git/AndroidStudio/xmd/erp-components-android/appinit/demo/app/build/intermediates/transforms/AppInit/baidu/debug/18
210 | // /Users/wanghao/git/AndroidStudio/xmd/erp-components-android/appinit/demo/app/build/intermediates/transforms/AppInit/debug/18
211 | File destDir = transformInvocation.outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
212 |
213 | directoryInput.file.eachFileRecurse(FileType.FILES) { File file ->
214 | if (shouldScanPathInternal(file.absolutePath)) {
215 | new FileInputStream(file).withCloseable {
216 | scanClass(it, destDir)
217 | }
218 | }
219 | }
220 |
221 | FileUtils.copyDirectory(directoryInput.file, destDir)
222 | }
223 |
224 | protected abstract void scanClass(InputStream inputStream, File dest)
225 |
226 | protected boolean shouldScanJar(JarInput jarInput) {
227 | if (jarInput == null || jarInput.file == null || !jarInput.file.exists()) {
228 | return false
229 | }
230 | mExcludeJarSet.each {
231 | if (jarInput.name.contains(it)) {
232 | return false
233 | }
234 | }
235 | return true
236 | }
237 |
238 | protected boolean shouldScanPathInternal(String path) {
239 | if (AppInitCommonUtils.isEmpty(path)) {
240 | return false
241 | }
242 | if (!path.endsWith(SdkConstants.DOT_CLASS)) {
243 | // AppInitLogger.d "shouldScanPathInternal 没有以 class 结尾:${path}"
244 | return false
245 | }
246 | path = path.replaceAll("\\\\", "/")
247 | return shouldScanPath(path)
248 | }
249 |
250 | protected abstract boolean shouldScanPath(String path)
251 |
252 | protected void modifyJarClass(File inputJarFile) {
253 | if (inputJarFile == null || !inputJarFile.exists() || !inputJarFile.name.endsWith('.jar')) {
254 | return
255 | }
256 |
257 | def tempJarFile = new File(inputJarFile.getParent(), inputJarFile.name + ".temp")
258 | if (tempJarFile.exists()) {
259 | tempJarFile.delete()
260 | }
261 |
262 | new JarFile(inputJarFile).withCloseable { JarFile jarFile ->
263 | new JarOutputStream(new FileOutputStream(tempJarFile)).withCloseable { JarOutputStream jarOutputStream ->
264 | Enumeration enumeration = jarFile.entries()
265 | while (enumeration.hasMoreElements()) {
266 | JarEntry jarEntry = enumeration.nextElement()
267 | String entryName = jarEntry.name
268 | // AppInitLogger.d "entryName 为 ${entryName}"
269 | jarOutputStream.putNextEntry(new ZipEntry(entryName))
270 | jarFile.getInputStream(jarEntry).withCloseable { jarEntryInputStream ->
271 | byte[] bytes = modifyJarClass(inputJarFile, jarEntryInputStream, entryName)
272 | if (bytes == null) {
273 | bytes = IOUtils.toByteArray(jarEntryInputStream)
274 | }
275 | jarOutputStream.write(bytes)
276 | jarOutputStream.closeEntry()
277 | }
278 | }
279 | }
280 | }
281 |
282 | inputJarFile.delete()
283 | tempJarFile.renameTo(inputJarFile)
284 | }
285 |
286 | /**
287 | *
288 | * @param jarFile jar 文件
289 | * @param jarEntryInputStream class 文件输入流
290 | * @param entryName 「com/sankuai/erp/component/appinit/api/SimpleAppInit.class」「com/sankuai/erp/component/appinit/api/AppInitManager$$Lambda$0.class」
291 | * @return 修改后的字节数组,如果为 null 则标识不修改
292 | */
293 | protected abstract byte[] modifyJarClass(File jarFile, InputStream jarEntryInputStream, String entryName)
294 |
295 | protected Class> loadClass(String name) throws ClassNotFoundException {
296 | return mUrlClassLoader.loadClass(name)
297 | }
298 |
299 | private static void transformInput(TransformInvocation transformInvocation, Consumer jarInputConsumer, Consumer directoryInputConsumer) throws TransformException, InterruptedException, IOException {
300 | Pair pair = getExecutorCyclicBarrierPair(transformInvocation)
301 | transformInvocation.inputs.parallelStream().forEach { TransformInput transformInput ->
302 | if (!transformInput.jarInputs.empty) {
303 | pair.left.execute {
304 | transformInput.jarInputs.parallelStream().forEach { JarInput jarInput ->
305 | jarInputConsumer.accept(jarInput)
306 | }
307 | pair.right.await()
308 | }
309 | }
310 | if (!transformInput.directoryInputs.empty) {
311 | pair.left.execute {
312 | transformInput.directoryInputs.parallelStream().forEach { DirectoryInput directoryInput ->
313 | directoryInputConsumer.accept(directoryInput)
314 | }
315 | pair.right.await()
316 | }
317 | }
318 | }
319 | pair.right.await()
320 | }
321 |
322 | private static Pair getExecutorCyclicBarrierPair(TransformInvocation transformInvocation) {
323 | int inputsCount = 0
324 | transformInvocation.inputs.each { TransformInput transformInput ->
325 | if (!transformInput.jarInputs.empty) {
326 | inputsCount++
327 | }
328 | if (!transformInput.directoryInputs.empty) {
329 | inputsCount++
330 | }
331 | }
332 | return Pair.of(Executors.newFixedThreadPool(inputsCount), new CyclicBarrier(inputsCount + 1))
333 | }
334 |
335 | protected static String convertCanonicalNameToEntryName(String canonicalName, boolean hasClass) {
336 | if (canonicalName == null || canonicalName.length() == 0) {
337 | return ""
338 | }
339 | String result = canonicalName.replace('.', '/')
340 | if (hasClass) {
341 | result += SdkConstants.DOT_CLASS
342 | }
343 | return result
344 | }
345 |
346 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/ChildInitTableSortUtils.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import com.sankuai.erp.component.appinit.common.AppInit
4 | import com.sankuai.erp.component.appinit.common.AppInitCommonUtils
5 | import com.sankuai.erp.component.appinit.common.AppInitItem
6 | import com.sankuai.erp.component.appinit.common.AppInitLogger
7 | import com.sankuai.erp.component.appinit.common.ChildInitTable
8 |
9 | /**
10 | * 作者:王浩
11 | * 创建时间:2018/11/18
12 | * 描述:
13 | */
14 | class ChildInitTableSortUtils {
15 | private static final StringBuilder NOT_EXIST_MODULE_SB = new StringBuilder()
16 |
17 | static List sortMasterInitTable(AppInitExtension appInitExtension, List childInitTableList, StringBuilder logSb) {
18 | if (childInitTableList == null || childInitTableList.isEmpty()) {
19 | logSb.append(String.format('没有找到添加了 %s 注解的类', AppInit.class.getName()))
20 | return null
21 | }
22 | // 检测子表名称是否重复并对子表进行排序
23 | logSb.append(AppInitCommonUtils.timeStr('模块排序') {
24 | checkChildInitTableDuplicateAndSort(childInitTableList, appInitExtension.dependencyMap)
25 | })
26 |
27 | String notExistMsg = NOT_EXIST_MODULE_SB.toString()
28 | if (!AppInitCommonUtils.isEmpty(notExistMsg)) {
29 | if (appInitExtension.getAbortOnNotExist()) {
30 | throw new IllegalArgumentException(notExistMsg)
31 | } else {
32 | logSb.append(" !!!!!!不存在的模块有:\n").append(NOT_EXIST_MODULE_SB.toString()).append("\n")
33 | }
34 | }
35 |
36 | return AppInitCommonUtils.sortAppInitItem(appInitExtension.getAbortOnNotExist(), childInitTableList, null, logSb)
37 | }
38 |
39 | private static void checkChildInitTableDuplicateAndSort(List childInitTableList,
40 | Map> moduleCoordinateDependencyMap) {
41 | handleModuleCoordinateDependency(childInitTableList, moduleCoordinateDependencyMap)
42 |
43 | // 默认按 coordinate 忽略大小写自然排序
44 | Collections.sort(childInitTableList) { first, second ->
45 | return first.coordinate.compareToIgnoreCase(second.coordinate)
46 | }
47 | int priority = 0
48 | for (ChildInitTable childInitTable : childInitTableList) {
49 | childInitTable.priority = priority++
50 | }
51 |
52 | // 根据依赖关系计算 priority
53 | Map moduleCoordinateMap = new HashMap<>()
54 | for (ChildInitTable childInitTable : childInitTableList) {
55 | // 检测 coordinate 是否重复
56 | checkChildInitTableDuplicate(childInitTable, moduleCoordinateMap)
57 | // 计算优先级
58 | calculateChildInitTablePriority(childInitTable, childInitTableList)
59 | }
60 | // 根据 priority 排序
61 | Collections.sort(childInitTableList)
62 | }
63 |
64 | private static void handleModuleCoordinateDependency(List childInitTableList,
65 | Map> moduleCoordinateDependencyMap) {
66 | if (moduleCoordinateDependencyMap == null || moduleCoordinateDependencyMap.isEmpty()) {
67 | return
68 | }
69 | ChildInitTable childInitTable
70 | for (Map.Entry> dependencyEntry : moduleCoordinateDependencyMap.entrySet()) {
71 | if (AppInitCommonUtils.isEmpty(dependencyEntry.getKey()) || dependencyEntry.getValue() == null || dependencyEntry.getValue().isEmpty()) {
72 | continue
73 | }
74 | childInitTable = findChildInitTable(dependencyEntry.getKey(), childInitTableList, null)
75 | if (childInitTable != null) {
76 | childInitTable.setDependenciesSet(new HashSet<>(dependencyEntry.getValue()))
77 | }
78 | }
79 | }
80 |
81 | private static void checkChildInitTableDuplicate(ChildInitTable childInitTable, Map moduleCoordinateMap) {
82 | String moduleCoordinate = childInitTable.coordinate
83 | if (moduleCoordinateMap.containsKey(moduleCoordinate)) {
84 | String msg = String.format("不允许出现两个 ChildInitTable 的 coordinate 相同:\n%s\n%s\n",
85 | childInitTable.getModuleInfo(), moduleCoordinateMap.get(moduleCoordinate).getModuleInfo())
86 | throw new IllegalArgumentException(msg)
87 | } else {
88 | moduleCoordinateMap.put(moduleCoordinate, childInitTable)
89 | }
90 | }
91 |
92 | private static void calculateChildInitTablePriority(ChildInitTable childInitTable, List childInitTableList) {
93 | if (childInitTable.calculated) {
94 | return
95 | }
96 | if (childInitTable.dependencies == null || childInitTable.dependencies.isEmpty()) {
97 | childInitTable.calculated = true
98 | return
99 | }
100 |
101 | childInitTable.calculated = true
102 | ChildInitTable dependencyChildInitTable
103 | for (String dependencyModuleCoordinate : childInitTable.dependencies) {
104 | if (childInitTable.coordinate == dependencyModuleCoordinate) {
105 | throw new IllegalArgumentException("「AppInitPlugin」=> 不允许 ChildInitTable 依赖自己:" + childInitTable.getModuleInfo())
106 | }
107 | dependencyChildInitTable = findChildInitTable(dependencyModuleCoordinate, childInitTableList, childInitTable)
108 | if (dependencyChildInitTable == null) {
109 | continue
110 | }
111 | // 检测循环依赖
112 | checkChildInitTableCircularDependency(childInitTable, dependencyChildInitTable, childInitTableList, null)
113 | // 计算优先级
114 | calculateChildInitTablePriority(dependencyChildInitTable, childInitTableList)
115 | if (childInitTable.priority <= dependencyChildInitTable.priority) {
116 | childInitTable.priority = dependencyChildInitTable.priority + 1
117 | }
118 | }
119 | }
120 |
121 | private static void checkChildInitTableCircularDependency(ChildInitTable childInitTable, ChildInitTable dependencyChildInitTable,
122 | List childInitTableList, List transitiveDependencyList) {
123 | if (dependencyChildInitTable == null) {
124 | return
125 | }
126 | Set dependencies = dependencyChildInitTable.dependencies
127 | if (dependencies == null || dependencies.isEmpty()) {
128 | return
129 | }
130 |
131 | for (String dependencyModuleCoordinate : dependencies) {
132 | if (dependencyChildInitTable.coordinate == dependencyModuleCoordinate) {
133 | throw new IllegalArgumentException("「AppInitPlugin」=> 不允许 ChildInitTable 依赖自己:" + dependencyChildInitTable.getModuleInfo())
134 | } else if (childInitTable.coordinate == dependencyModuleCoordinate) {
135 | StringBuilder msgSb = new StringBuilder("「AppInitPlugin」=> 不允许出现两个 ChildInitTable ")
136 | if (transitiveDependencyList == null || transitiveDependencyList.size() == 0) {
137 | msgSb.append("循环依赖:\n").append(childInitTable.getModuleInfo()).append("\n").append(dependencyChildInitTable.getModuleInfo())
138 | } else {
139 | msgSb.append("传递循环依赖:\n").append(childInitTable.getModuleInfo()).append("\n")
140 | for (ChildInitTable transitiveChildInitTable : transitiveDependencyList) {
141 | msgSb.append(transitiveChildInitTable.getModuleInfo()).append("\n")
142 | }
143 | msgSb.append(dependencyChildInitTable.getModuleInfo())
144 | }
145 | throw new IllegalArgumentException(msgSb.toString())
146 | } else {
147 | ChildInitTable transitiveDependencyChildInitTable = findChildInitTable(dependencyModuleCoordinate, childInitTableList,
148 | dependencyChildInitTable)
149 | if (transitiveDependencyList == null) {
150 | transitiveDependencyList = new ArrayList<>()
151 | }
152 | transitiveDependencyList.add(dependencyChildInitTable)
153 | checkChildInitTableCircularDependency(childInitTable, transitiveDependencyChildInitTable, childInitTableList, transitiveDependencyList)
154 | }
155 | }
156 | }
157 |
158 | private static ChildInitTable findChildInitTable(String moduleCoordinate, List childInitTableList, ChildInitTable lastChildInitTable) {
159 | if (AppInitCommonUtils.isEmpty(moduleCoordinate)) {
160 | return null
161 | }
162 |
163 | ChildInitTable childInitTable = childInitTableList.find { childInitTable -> childInitTable.coordinate == moduleCoordinate }
164 | if (childInitTable != null) {
165 | return childInitTable
166 | }
167 |
168 | String msg
169 | if (lastChildInitTable != null) {
170 | msg = String.format(" %s=> 依赖的模块《%s》不存在\n", lastChildInitTable.getModuleInfo(), moduleCoordinate)
171 | } else {
172 | msg = String.format(" 模块《%s》不存在\n", moduleCoordinate)
173 | }
174 | NOT_EXIST_MODULE_SB.append(msg)
175 | AppInitLogger.e(msg)
176 | return null
177 | }
178 |
179 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/sankuai/erp/component/plugin/appinit/Logger.groovy:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.plugin.appinit
2 |
3 | import com.sankuai.erp.component.appinit.common.ILogger
4 | import org.gradle.api.Project
5 |
6 | /**
7 | * 作者:王浩
8 | * 创建时间:2018/11/2
9 | * 描述:AppInit 打印 Gradle 插件日志
10 | */
11 | class Logger implements ILogger {
12 | private Project mProject
13 | Logger(Project project) {
14 | mProject = project
15 | }
16 | @Override
17 | boolean isDebug() {
18 | return true
19 | }
20 |
21 | @Override
22 | boolean isIsMainProcess() {
23 | return true
24 | }
25 |
26 | @Override
27 | void demo(String msg) {
28 | d(msg)
29 | }
30 |
31 | @Override
32 | void d(String msg) {
33 | println "「AppInitPlugin」「${mProject.path}」=> ${msg}"
34 | }
35 |
36 |
37 | @Override
38 | void e(String msg) {
39 | mProject.logger.error "「AppInitPlugin」「${mProject.path}」=> ${msg}"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/buildSrc/src/main/resources/META-INF/gradle-plugins/bga-appinit-plugin.properties:
--------------------------------------------------------------------------------
1 | implementation-class=com.sankuai.erp.component.plugin.appinit.AppInitPlugin
--------------------------------------------------------------------------------
/common/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 |
4 | dependencies {
5 | compileOnly 'com.google.android:android:4.1.1.4'
6 | }
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/AppInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * 作者:王浩
10 | * 创建时间:2018/1/18
11 | * 描述:
12 | */
13 | @Target(ElementType.TYPE)
14 | @Retention(RetentionPolicy.CLASS)
15 | public @interface AppInit {
16 | Process process() default Process.MAIN; // 指定在哪个进程初始化
17 |
18 | int priority(); // 模块内部初始化顺序的优先级,越大越先初始化
19 |
20 | String aheadOf() default ""; // 在指定项之前初始化,用于整个项目范围内重新排序
21 |
22 | String description() default ""; // 描述
23 |
24 | boolean onlyForDebug() default false; // 只有在 debug 时才初始化
25 | }
26 |
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/AppInitCallback.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | /**
7 | * 作者:王浩
8 | * 创建时间:2018/10/30
9 | * 描述:初始化配置回调接口
10 | */
11 | public interface AppInitCallback {
12 |
13 | /**
14 | * 开始初始化
15 | *
16 | * @param isMainProcess 是否为主进程
17 | * @param processName 进程名称
18 | */
19 | void onInitStart(boolean isMainProcess, String processName);
20 |
21 | /**
22 | * 是否为 debug 模式
23 | */
24 | boolean isDebug();
25 |
26 | /**
27 | * 通过 coordinate 自定义依赖关系映射,键值都是 coordinate
28 | *
29 | * @return 如果返回的 map 不为空,则会在启动时检测依赖并重新排序
30 | */
31 | Map getCoordinateAheadOfMap();
32 |
33 | /**
34 | * 同步初始化完成
35 | *
36 | * @param isMainProcess 是否为主进程
37 | * @param processName 进程名称
38 | * @param childInitTableList 初始化模块列表
39 | * @param appInitItemList 初始化列表
40 | */
41 | void onInitFinished(boolean isMainProcess, String processName, List childInitTableList, List appInitItemList);
42 | }
43 |
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/AppInitCommonUtils.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | /**
10 | * 作者:王浩
11 | * 创建时间:2018/10/26
12 | * 描述:
13 | */
14 | public final class AppInitCommonUtils {
15 |
16 | private AppInitCommonUtils() {
17 | }
18 |
19 | public static List sortAppInitItem(boolean abortOnNotExist, List childInitTableList, Map coordinateAheadOfMap,
20 | StringBuilder logSb) {
21 | logSb.append("处理 aheadOf 前的顺序为:\n");
22 | StringBuilder notHandleAheadOfLogSb = new StringBuilder();
23 |
24 | Map coordinateMap = new HashMap<>();
25 |
26 | AppInitItem head = null;
27 | AppInitItem tail = null;
28 | AppInitItem pre;
29 | AppInitItem target;
30 |
31 | for (ChildInitTable childInitTable : childInitTableList) {
32 | logSb.append(childInitTable.getModuleInfo()).append('\n');
33 |
34 | Collections.sort(childInitTable);
35 | for (AppInitItem current : childInitTable) {
36 | current.moduleInfo = childInitTable.getModuleInfo();
37 | logSb.append(current.toString()).append("\n");
38 | // 检测 coordinate 是否重复
39 | checkAppInitDuplicate(current, coordinateMap);
40 |
41 | if (coordinateAheadOfMap != null && coordinateAheadOfMap.containsKey(current.coordinate)) {
42 | current.aheadOf = coordinateAheadOfMap.get(current.coordinate);
43 | }
44 |
45 | if (head == null) {
46 | head = current;
47 | tail = current;
48 | } else {
49 | tail.next = current;
50 | current.pre = tail;
51 | tail = current;
52 |
53 | if (isEmpty(tail.aheadOf)) {
54 | continue;
55 | }
56 |
57 | target = coordinateMap.get(tail.aheadOf);
58 | if (target == null) {
59 | notHandleAheadOfLogSb.append(String.format("%s aheadOf 的「%s」不存在,或已经在其之后了\n", tail.toString(), tail.aheadOf));
60 | continue;
61 | }
62 | // 当前结点的前一个结点设置为新的 tail
63 | tail = current.pre;
64 | tail.next = null;
65 |
66 | pre = target.pre;
67 | if (pre == null) {
68 | // pre 为空,说明 target 就是老的 head,将 current 设置为新的 head
69 | current.pre = null;
70 | head = current;
71 | } else {
72 | current.pre = pre;
73 | pre.next = current;
74 | }
75 |
76 | target.pre = current;
77 | current.next = target;
78 | }
79 | }
80 | }
81 |
82 | if (coordinateAheadOfMap != null && !coordinateAheadOfMap.isEmpty()) {
83 | for (String coordinate : coordinateAheadOfMap.keySet()) {
84 | if (!coordinateMap.containsKey(coordinate)) {
85 | notHandleAheadOfLogSb.append(String.format("getCoordinateAheadOfMap() 方法中返回的「%s」不存在\n", coordinate));
86 | }
87 | }
88 | }
89 |
90 | String notHandleAheadOfLog = notHandleAheadOfLogSb.toString();
91 | if (!isEmpty(notHandleAheadOfLog)) {
92 | notHandleAheadOfLog = "\n !!!!!!未能处理的 aheadOf 有:\n" + notHandleAheadOfLog;
93 | if (abortOnNotExist) {
94 | throw new IllegalArgumentException(notHandleAheadOfLog);
95 | }
96 | }
97 |
98 | List result = assembleAppInitItemList(head, logSb);
99 | logSb.append(notHandleAheadOfLog).append("\n");
100 | return result;
101 | }
102 |
103 | private static List assembleAppInitItemList(AppInitItem appInitItem, StringBuilder logSb) {
104 | logSb.append("\n最终的初始化顺序为:\n");
105 | List appInitItemList = new ArrayList<>();
106 | while (appInitItem != null) {
107 | if (appInitItem.pre == null || !equals(appInitItem.pre.moduleCoordinate, appInitItem.moduleCoordinate)) {
108 | logSb.append(appInitItem.moduleInfo).append("\n");
109 | }
110 | logSb.append(appInitItem.toString()).append("\n");
111 | appInitItemList.add(appInitItem);
112 | appInitItem = appInitItem.next;
113 | }
114 | return appInitItemList;
115 | }
116 |
117 | private static void checkAppInitDuplicate(AppInitItem appInitItem, Map coordinateMap) {
118 | String coordinate = appInitItem.coordinate;
119 | if (isEmpty(coordinate)) {
120 | return;
121 | }
122 |
123 | if (coordinateMap.containsKey(coordinate)) {
124 | String msg = "不允许出现两个 AppInit 的 coordinate 相同:\n" + appInitItem.toString() + "\n" + coordinateMap.get(coordinate).toString() + "\n";
125 | throw new IllegalArgumentException(msg);
126 | } else {
127 | coordinateMap.put(coordinate, appInitItem);
128 | }
129 | }
130 |
131 | // android.text.TextUtils.isEmpty
132 | public static boolean isEmpty(CharSequence str) {
133 | return str == null || str.length() == 0;
134 | }
135 |
136 | // android.text.TextUtils.equals
137 | public static boolean equals(CharSequence a, CharSequence b) {
138 | if (a == b) return true;
139 | int length;
140 | if (a != null && b != null && (length = a.length()) == b.length()) {
141 | if (a instanceof String && b instanceof String) {
142 | return a.equals(b);
143 | } else {
144 | for (int i = 0; i < length; i++) {
145 | if (a.charAt(i) != b.charAt(i)) return false;
146 | }
147 | return true;
148 | }
149 | }
150 | return false;
151 | }
152 |
153 | /**
154 | * 统计执行时间
155 | */
156 | public static long time(Runnable runnable) {
157 | if (runnable == null) {
158 | return 0;
159 | }
160 | long startTime = System.currentTimeMillis();
161 | runnable.run();
162 | return System.currentTimeMillis() - startTime;
163 | }
164 |
165 | /**
166 | * 统计执行时间
167 | */
168 | public static long time(String desc, Runnable runnable) {
169 | long time = time(runnable);
170 | AppInitLogger.d(String.format("%s耗时:%sms\n\n", desc, time));
171 | return time;
172 | }
173 |
174 | /**
175 | * 统计执行时间
176 | */
177 | public static String timeStr(String desc, Runnable runnable) {
178 | String msg = String.format("%s耗时:%sms\n\n", desc, time(runnable));
179 | AppInitLogger.d(msg);
180 | return msg;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/AppInitItem.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | /**
4 | * 作者:王浩
5 | * 创建时间:2018/1/18
6 | * 描述:
7 | */
8 | public class AppInitItem implements Comparable {
9 | public String appInitClassName;
10 | public IAppInit appInit;
11 | public Process process; // 指定在哪个进程初始化
12 | public int priority; // 开发者配置的初始化顺序优先级,越大越先初始化,用于模块返回内重新排序
13 | public String coordinate; // 唯一标识
14 | public String aheadOf; // 在指定项之前初始化,用于整个项目范围内重新排序
15 | public String description; // 描述
16 | public boolean onlyForDebug; // 只有在 debug 时才初始化
17 | public AppInitItem pre;
18 | public AppInitItem next;
19 | public String moduleInfo;
20 | public String moduleCoordinate;
21 | public long time;
22 |
23 | // 给 AppInitProcessor 生成子表时调用
24 | @SuppressWarnings("checkstyle:ParameterNumber")
25 | public AppInitItem(
26 | String appInitClassName,
27 | int processOrdinal,
28 | int priority,
29 | String coordinate,
30 | String aheadOf,
31 | String description,
32 | String onlyForDebug,
33 | String moduleCoordinate) {
34 | this.appInitClassName = appInitClassName;
35 | this.process = Process.values()[processOrdinal];
36 | this.priority = priority;
37 | this.coordinate = coordinate;
38 | this.aheadOf = aheadOf;
39 | this.description = description;
40 | this.onlyForDebug = Boolean.valueOf(onlyForDebug);
41 | this.moduleCoordinate = moduleCoordinate;
42 | }
43 |
44 | // 给 AppInitTransform 生成主表时调用
45 | @SuppressWarnings("checkstyle:ParameterNumber")
46 | public AppInitItem(
47 | IAppInit appInit,
48 | int processOrdinal,
49 | int priority,
50 | String coordinate,
51 | String aheadOf,
52 | String description,
53 | String onlyForDebug,
54 | String moduleCoordinate) {
55 | this(appInit.getClass().getCanonicalName(), processOrdinal, priority, coordinate, aheadOf, description, onlyForDebug, moduleCoordinate);
56 | this.appInit = appInit;
57 | }
58 |
59 | public boolean isForMainProcess() {
60 | return Process.MAIN == process || isForAllProcess();
61 | }
62 |
63 | private boolean isForAllProcess() {
64 | return Process.ALL == process;
65 | }
66 |
67 | public boolean isNotForMainProcess() {
68 | return Process.OTHER == process || isForAllProcess();
69 | }
70 |
71 | @Override
72 | public int compareTo(AppInitItem other) {
73 | return Integer.compare(this.priority, other.priority);
74 | }
75 |
76 | @Override
77 | public String toString() {
78 | if (AppInitCommonUtils.isEmpty(aheadOf)) {
79 | return String.format(" * [%s][%s][进程=%s][description=%s]", coordinate, priority, process.name(), description);
80 | } else {
81 | return String.format(" * [%s][%s][进程=%s][aheadOf=%s][description=%s]", coordinate, priority, process.name(), aheadOf, description);
82 | }
83 | }
84 |
85 | public String timeInfo() {
86 | if (AppInitCommonUtils.isEmpty(aheadOf)) {
87 | return String.format(" * [%s][%s][进程=%s][description=%s][耗时=%sms]", coordinate, priority, process.name(), description, time);
88 | } else {
89 | return String.format(" * [%s][%s][进程=%s][aheadOf=%s][description=%s][耗时=%sms]",
90 | coordinate, priority, process.name(), aheadOf, description, time);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/AppInitLogger.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | /**
4 | * 作者:王浩
5 | * 创建时间:2018/1/18
6 | * 描述:
7 | */
8 | public final class AppInitLogger {
9 | public static ILogger sLogger;
10 |
11 | private AppInitLogger() {
12 | }
13 |
14 | private static boolean isNotDebug() {
15 | if (sLogger == null) {
16 | return true;
17 | }
18 | return !sLogger.isDebug();
19 | }
20 |
21 | public static void demo(String msg) {
22 | if (isNotDebug()) {
23 | return;
24 | }
25 | sLogger.demo(msg);
26 | }
27 |
28 | public static void d(String msg) {
29 | if (isNotDebug()) {
30 | return;
31 | }
32 | sLogger.d(msg);
33 | }
34 |
35 | public static void e(String msg) {
36 | if (isNotDebug()) {
37 | return;
38 | }
39 | sLogger.e(msg);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/ChildInitTable.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | /**
9 | * 作者:王浩
10 | * 创建时间:2018/11/12
11 | * 描述:
12 | */
13 | public class ChildInitTable extends ArrayList implements Comparable {
14 | public int priority;
15 | public String coordinate;
16 | public Set dependencies;
17 | public boolean calculated = false; // 是否已经计算过优先级
18 | public long time;
19 |
20 | public void setCoordinate(String coordinate) {
21 | this.coordinate = coordinate;
22 | }
23 |
24 | public void setDependencies(String dependencies) {
25 | if (AppInitCommonUtils.isEmpty(dependencies)) {
26 | this.dependencies = new HashSet<>();
27 | } else {
28 | this.dependencies = new HashSet<>(Arrays.asList(dependencies.split(",")));
29 | }
30 | }
31 |
32 | void setDependenciesSet(Set dependencies) {
33 | if (dependencies == null || dependencies.isEmpty()) {
34 | this.dependencies = new HashSet<>();
35 | } else {
36 | this.dependencies = dependencies;
37 | }
38 | }
39 |
40 | public String getModuleInfo() {
41 | String dep = getDep();
42 | if (AppInitCommonUtils.isEmpty(dep)) {
43 | return String.format(" 《%s》[priority=%s]", coordinate, priority);
44 | } else {
45 | return String.format(" 《%s》[priority=%s][dependencies=%s]", coordinate, priority, dep);
46 | }
47 | }
48 |
49 | private String getDep() {
50 | if (dependencies != null && !dependencies.isEmpty()) {
51 | return dependencies.toString().replace("[", "").replace("]", "");
52 | }
53 | return null;
54 | }
55 |
56 | public String getTimeInfo() {
57 | if (time == 0) {
58 | for (AppInitItem appInitItem : this) {
59 | time += appInitItem.time;
60 | }
61 | }
62 |
63 | String dep = getDep();
64 | if (AppInitCommonUtils.isEmpty(dep)) {
65 | return String.format(" 《%s》[priority=%s][耗时=%sms]", coordinate, priority, time);
66 | } else {
67 | return String.format(" 《%s》[priority=%s][dependencies=%s][耗时=%sms]", coordinate, priority, dep, time);
68 | }
69 | }
70 |
71 | @Override
72 | public int compareTo(ChildInitTable other) {
73 | return Integer.compare(this.priority, other.priority);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/IAppInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | import android.app.Application;
4 | import android.content.ComponentCallbacks;
5 | import android.content.ComponentCallbacks2;
6 | import android.content.res.Configuration;
7 |
8 | /**
9 | * 作者:王浩
10 | * 创建时间:2018/10/29
11 | * 描述:所有方法都只会运行在你注册的进程
12 | */
13 | public interface IAppInit {
14 | /**
15 | * 是否需要异步初始化,默认为 false
16 | */
17 | boolean needAsyncInit();
18 |
19 | /**
20 | * {@link Application#onCreate()} 时调用
21 | */
22 | void onCreate();
23 |
24 | void asyncOnCreate();
25 |
26 | /**
27 | * {@link Application#onTerminate()} 时调用
28 | */
29 | void onTerminate();
30 |
31 | /**
32 | * {@link Application#onConfigurationChanged(Configuration)} 时调用
33 | *
34 | * @param newConfig The new device configuration.
35 | * @see ComponentCallbacks#onConfigurationChanged(Configuration)
36 | */
37 | void onConfigurationChanged(Configuration newConfig);
38 |
39 | /**
40 | * {@link Application#onLowMemory()} 时调用
41 | *
42 | * @see ComponentCallbacks#onLowMemory()
43 | */
44 | void onLowMemory();
45 |
46 | /**
47 | * {@link Application#onTrimMemory(int)} 时调用
48 | *
49 | * @param level The context of the trim, giving a hint of the amount of trimming the application may like to perform.
50 | * @see ComponentCallbacks2#onTrimMemory(int)
51 | */
52 | void onTrimMemory(int level);
53 | }
54 |
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/ILogger.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | /**
4 | * 作者:王浩
5 | * 创建时间:2018/11/2
6 | * 描述:
7 | */
8 | public interface ILogger {
9 | boolean isDebug();
10 |
11 | boolean isIsMainProcess();
12 |
13 | void demo(String msg);
14 |
15 | void d(String msg);
16 |
17 | void e(String msg);
18 | }
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/ModuleConsts.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | /**
4 | * 作者:王浩
5 | * 创建时间:2018/1/18
6 | * 描述:
7 | */
8 | public interface ModuleConsts {
9 | // 该库对应的 apt 模块坐标参数 key
10 | String APT_MODULE_COORDINATE_KEY = "AppInitAptModuleCoordinate";
11 | // 该库对应的 apt 模块依赖参数 key
12 | String APT_DEPENDENCIES_KEY = "APP_INIT_DEPENDENCIES";
13 |
14 | String DOT = ".";
15 | String PACKAGE_NAME_GENERATED = "com.sankuai.erp.component.appinit.generated";
16 | String PACKAGE_NAME_GENERATED_SLASH = PACKAGE_NAME_GENERATED.replace('.', '/');
17 | String PACKAGE_NAME_COMMON = "com.sankuai.erp.component.appinit.common";
18 | String PACKAGE_NAME_COMMON_SLASH = PACKAGE_NAME_COMMON.replace('.', '/');
19 | String PACKAGE_NAME_API = "com.sankuai.erp.component.appinit.api";
20 | String PACKAGE_NAME_API_SLASH = PACKAGE_NAME_API.replace('.', '/');
21 |
22 | String APP_INIT_MANAGER = "AppInitManager";
23 | String APP_INIT_MANAGER_CANONICAL_NAME = PACKAGE_NAME_API + DOT + APP_INIT_MANAGER;
24 | String APP_INIT_ITEM = "AppInitItem";
25 | String APP_INIT_ITEM_CANONICAL_NAME = PACKAGE_NAME_COMMON + DOT + APP_INIT_ITEM;
26 | String APP_INIT_INTERFACE = PACKAGE_NAME_COMMON + DOT + "IAppInit";
27 | String CHILD_INIT_TABLE_SUFFIX = "ChildInitTable";
28 | String CHILD_INIT_TABLE_CANONICAL_NAME = PACKAGE_NAME_COMMON + DOT + CHILD_INIT_TABLE_SUFFIX;
29 |
30 | String FIELD_INJECT_ABORT_ON_NOT_EXIST = "mAbortOnNotExist";
31 | String METHOD_INJECT_CHILD_INIT_TABLE_LIST = "injectChildInitTableList";
32 | String METHOD_INJECT_APP_INIT_ITEM_LIST = "injectAppInitItemList";
33 | }
34 |
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/OrderConstraintRule.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | /**
4 | * 作者:王浩
5 | * 创建时间:2018/1/18
6 | * 描述:排序类型
7 | */
8 | public enum OrderConstraintRule {
9 | // 按 priority 排序
10 | PRIORITY,
11 | // 始终最先初始化
12 | HEAD,
13 | // 始终最后初始化
14 | TAIL
15 | }
--------------------------------------------------------------------------------
/common/src/main/java/com/sankuai/erp/component/appinit/common/Process.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.common;
2 |
3 | /**
4 | * 作者:王浩
5 | * 创建时间:2018/1/18
6 | * 描述:在哪个进程初始化
7 | */
8 | public enum Process {
9 | // 主进程
10 | MAIN,
11 | // 所有进程
12 | ALL,
13 | // 非主进程
14 | OTHER
15 | }
--------------------------------------------------------------------------------
/compiler/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 |
4 | dependencies {
5 | implementation 'com.squareup:javapoet:1.9.0'
6 | implementation project(':common')
7 | }
8 |
--------------------------------------------------------------------------------
/compiler/src/main/java/com/sankuai/erp/component/appinit/compiler/BaseGenerateChildTableProcessor.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.compiler;
2 |
3 | import com.squareup.javapoet.ClassName;
4 | import com.squareup.javapoet.JavaFile;
5 | import com.squareup.javapoet.MethodSpec;
6 | import com.squareup.javapoet.TypeName;
7 | import com.squareup.javapoet.TypeSpec;
8 |
9 | import java.io.IOException;
10 | import java.util.LinkedHashSet;
11 | import java.util.Set;
12 | import java.util.regex.Matcher;
13 | import java.util.regex.Pattern;
14 |
15 | import javax.annotation.processing.AbstractProcessor;
16 | import javax.annotation.processing.ProcessingEnvironment;
17 | import javax.annotation.processing.RoundEnvironment;
18 | import javax.lang.model.element.Element;
19 | import javax.lang.model.element.Modifier;
20 | import javax.lang.model.element.TypeElement;
21 | import javax.lang.model.type.TypeMirror;
22 | import javax.tools.Diagnostic;
23 |
24 | /**
25 | * 作者:王浩
26 | * 创建时间:2018/3/14
27 | * 描述:每个模块生成一张字表
28 | */
29 | public abstract class BaseGenerateChildTableProcessor extends AbstractProcessor {
30 | protected String mClassJavaDoc;
31 | protected String mModuleCoordinate;
32 | protected String mModuleDependencies;
33 | protected String mChildTablePrefix;
34 |
35 | @Override
36 | public synchronized void init(ProcessingEnvironment processingEnvironment) {
37 | super.init(processingEnvironment);
38 | mClassJavaDoc = "Generated by " + this.getClass().getSimpleName() + ". Do not edit it!\n";
39 |
40 | mModuleCoordinate = getOption(getAptModuleCoordinateKey());
41 | if (mModuleCoordinate == null) {
42 | throw new RuntimeException("请在「build.gradle」中配置「" + getAptModuleCoordinateKey() + "」参数");
43 | }
44 | mModuleDependencies = getOption(getAptModuleDependenciesKey());
45 |
46 | initChildTablePrefix();
47 | }
48 |
49 | private void initChildTablePrefix() {
50 | mChildTablePrefix = "_" + mModuleCoordinate.replace('.', '_').replace('-', '_').replace(':', '_');
51 | StringBuilder childTablePrefixSb = new StringBuilder(mChildTablePrefix);
52 | Matcher mc = Pattern.compile("_").matcher(mChildTablePrefix);
53 | int i = 0;
54 | while (mc.find()) {
55 | int position = mc.end() - (i++);
56 | childTablePrefixSb.replace(position - 1, position + 1, childTablePrefixSb.substring(position, position + 1).toUpperCase());
57 | }
58 | mChildTablePrefix = childTablePrefixSb.toString();
59 | }
60 |
61 | protected abstract String getAptModuleCoordinateKey();
62 |
63 | protected abstract String getAptModuleDependenciesKey();
64 |
65 | @Override
66 | public Set getSupportedAnnotationTypes() {
67 | final Set annotationTypes = new LinkedHashSet<>();
68 | annotationTypes.add(getAnnotationClass().getCanonicalName());
69 | return annotationTypes;
70 | }
71 |
72 | protected abstract Class getAnnotationClass();
73 |
74 | @Override
75 | public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnv) {
76 | String annotationSimpleName = getAnnotationClass().getSimpleName();
77 | Set extends Element> childAnnotatedElementSet = roundEnv.getElementsAnnotatedWith(getAnnotationClass());
78 | if (isElementNotEmpty(childAnnotatedElementSet)) {
79 | info("===============> " + annotationSimpleName + " 不为空 START <===============");
80 |
81 | MethodSpec.Builder constructorMethod = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC);
82 |
83 | insertStatementBeforeAddItem(constructorMethod);
84 |
85 | for (Element childAnnotatedElement : childAnnotatedElementSet) {
86 | if (validateChildAnnotatedElement(childAnnotatedElement, annotationSimpleName)) {
87 | addItem((TypeElement) childAnnotatedElement, constructorMethod);
88 | }
89 | }
90 |
91 | TypeSpec type = TypeSpec.classBuilder(mChildTablePrefix + getChildTableSuffix())
92 | .superclass(getChildTableSuperClassTypeName())
93 | .addModifiers(Modifier.PUBLIC)
94 | .addMethod(constructorMethod.build())
95 | .addJavadoc(mClassJavaDoc)
96 | .build();
97 |
98 | generateClass(getGeneratedPackageName(), type);
99 | info("===============> " + annotationSimpleName + " 不为空 END <===============");
100 | } else {
101 | info("===============> " + annotationSimpleName + " 为空 <===============");
102 | }
103 | return true;
104 | }
105 |
106 | protected abstract void insertStatementBeforeAddItem(MethodSpec.Builder constructorMethod);
107 |
108 | protected abstract boolean validateChildAnnotatedElement(Element childAnnotatedElement, String annotationSimpleName);
109 |
110 | protected abstract void addItem(TypeElement element, MethodSpec.Builder constructorMethod);
111 |
112 | protected abstract String getChildTableSuffix();
113 |
114 | protected abstract TypeName getChildTableSuperClassTypeName();
115 |
116 | protected abstract String getGeneratedPackageName();
117 |
118 | protected boolean validateClassImplements(Element element, String interfaceCanonicalName, String annotationSimpleName) {
119 | if (validateClassAssignable(element, interfaceCanonicalName)) {
120 | return true;
121 | } else {
122 | error(element, annotationSimpleName + " 注解的类必须实现「" + interfaceCanonicalName + "」接口");
123 | return false;
124 | }
125 | }
126 |
127 | protected boolean validateClassAssignable(Element element, String canonicalName) {
128 | return element.getKind().isClass() && processingEnv.getTypeUtils().isAssignable(element.asType(),
129 | processingEnv.getElementUtils().getTypeElement(canonicalName).asType());
130 | }
131 |
132 | protected boolean validateClassSubtype(Element element, String type) {
133 | return processingEnv.getTypeUtils().isSubtype(element.asType(),
134 | processingEnv.getElementUtils().getTypeElement(type).asType());
135 | }
136 |
137 | protected boolean validateClassSubtype(TypeMirror typeMirror, String type) {
138 | return processingEnv.getTypeUtils().isSubtype(typeMirror,
139 | processingEnv.getElementUtils().getTypeElement(type).asType());
140 | }
141 |
142 | protected boolean validateAbstractClass(Element element) {
143 | Set modifiers = element.getModifiers();
144 | if (modifiers != null && modifiers.contains(Modifier.ABSTRACT)) {
145 | return true;
146 | }
147 | return false;
148 | }
149 |
150 | protected void generateClass(String packageName, TypeSpec typeSpec) {
151 | try {
152 | JavaFile.builder(packageName, typeSpec).build().writeTo(processingEnv.getFiler());
153 | } catch (IOException e) {
154 | e.printStackTrace();
155 | }
156 | }
157 |
158 | protected TypeName getTypeName(String canonicalName) {
159 | return ClassName.get(processingEnv.getElementUtils().getTypeElement(canonicalName));
160 | }
161 |
162 | protected String getOption(String key) {
163 | return processingEnv.getOptions().get(key);
164 | }
165 |
166 | protected void info(CharSequence info) {
167 | processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, getLogInfo(info));
168 | }
169 |
170 | protected void error(Element element, CharSequence info) {
171 | processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, getLogInfo(info), element);
172 | }
173 |
174 | private String getLogInfo(CharSequence info) {
175 | return mModuleCoordinate + info;
176 | }
177 |
178 | protected static boolean isElementNotEmpty(Set extends Element> elements) {
179 | return elements != null && !elements.isEmpty();
180 | }
181 | }
--------------------------------------------------------------------------------
/compiler/src/main/java/com/sankuai/erp/component/appinit/compiler/GenerateAppInitChildTableProcessor.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinit.compiler;
2 |
3 | import com.sankuai.erp.component.appinit.common.AppInit;
4 | import com.sankuai.erp.component.appinit.common.ModuleConsts;
5 | import com.squareup.javapoet.MethodSpec;
6 | import com.squareup.javapoet.TypeName;
7 |
8 | import javax.annotation.processing.SupportedOptions;
9 | import javax.annotation.processing.SupportedSourceVersion;
10 | import javax.lang.model.SourceVersion;
11 | import javax.lang.model.element.Element;
12 | import javax.lang.model.element.TypeElement;
13 |
14 | /**
15 | * 作者:王浩
16 | * 创建时间:2018/1/18
17 | * 描述:生成 AppInit 字表
18 | */
19 | @SupportedOptions({ModuleConsts.APT_MODULE_COORDINATE_KEY, ModuleConsts.APT_DEPENDENCIES_KEY})
20 | @SupportedSourceVersion(SourceVersion.RELEASE_7)
21 | public class GenerateAppInitChildTableProcessor extends BaseGenerateChildTableProcessor {
22 |
23 | @Override
24 | protected String getAptModuleCoordinateKey() {
25 | return ModuleConsts.APT_MODULE_COORDINATE_KEY;
26 | }
27 |
28 | @Override
29 | protected String getAptModuleDependenciesKey() {
30 | return ModuleConsts.APT_DEPENDENCIES_KEY;
31 | }
32 |
33 | @Override
34 | protected Class getAnnotationClass() {
35 | return AppInit.class;
36 | }
37 |
38 | @Override
39 | protected boolean validateChildAnnotatedElement(Element annotatedElement, String annotationSimpleName) {
40 | return validateClassImplements(annotatedElement, ModuleConsts.APP_INIT_INTERFACE, annotationSimpleName);
41 | }
42 |
43 | @Override
44 | protected void insertStatementBeforeAddItem(MethodSpec.Builder constructorMethod) {
45 | constructorMethod.addParameter(int.class, "priority");
46 | constructorMethod.addStatement("this.priority = priority");
47 |
48 | constructorMethod.addStatement("setCoordinate($S)", mModuleCoordinate);
49 | constructorMethod.addStatement("setDependencies($S)", mModuleDependencies);
50 | }
51 |
52 | @Override
53 | protected void addItem(TypeElement element, MethodSpec.Builder constructorMethod) {
54 | AppInit appInit = element.getAnnotation(AppInit.class);
55 | constructorMethod.addStatement("add(new $T($S, $L, $L, $S, $S, $S, $S, $S))",
56 | getTypeName(ModuleConsts.APP_INIT_ITEM_CANONICAL_NAME),
57 | element.getQualifiedName().toString(),
58 | appInit.process().ordinal(),
59 | appInit.priority(),
60 | mModuleCoordinate + ":" + element.getSimpleName(),
61 | appInit.aheadOf(),
62 | appInit.description(),
63 | String.valueOf(appInit.onlyForDebug()),
64 | mModuleCoordinate
65 | );
66 | }
67 |
68 | @Override
69 | protected String getChildTableSuffix() {
70 | return ModuleConsts.CHILD_INIT_TABLE_SUFFIX;
71 | }
72 |
73 | @Override
74 | protected TypeName getChildTableSuperClassTypeName() {
75 | return getTypeName(ModuleConsts.CHILD_INIT_TABLE_CANONICAL_NAME);
76 | }
77 |
78 | @Override
79 | protected String getGeneratedPackageName() {
80 | return ModuleConsts.PACKAGE_NAME_GENERATED;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor:
--------------------------------------------------------------------------------
1 | com.sankuai.erp.component.appinit.compiler.GenerateAppInitChildTableProcessor
--------------------------------------------------------------------------------
/demo/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'WMRouter'
2 | apply from: project.file('test_config.gradle')
3 |
4 | appInit {
5 | // AndroidManifest.xml 中配置的 Application 的类全名,配置后注入字节码自动回调 AppInitManager 对应的 Application 生命周期方法
6 | // applicationCanonicalName 'com.sankuai.erp.component.appinitdemo.App'
7 |
8 | // 自定义模块间初始化的依赖关系,会覆盖通过 APP_INIT_DEPENDENCIES 配置的依赖关系
9 | dependency([
10 | 'demo:app' : ['cn.bingoogolapple:appinit-test-module1', 'AppInit:module2'],
11 | 'AppInit:module2' : 'cn.bingoogolapple:appinit-test-module3',
12 | 'cn.bingoogolapple:appinit-test-module3': 'cn.bingoogolapple:appinit-test-module1'
13 | ])
14 |
15 | // 依赖的模块或 aheadOf 指定的初始化不存在时是否中断编译,默认为 true
16 | abortOnNotExist true
17 | }
18 |
19 | android {
20 | defaultConfig {
21 | multiDexEnabled true
22 | }
23 |
24 | buildTypes {
25 | debug {
26 | // 测试混淆
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29 | }
30 | release {
31 | // 测试混淆
32 | minifyEnabled false
33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34 | }
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation "com.android.support:multidex:1.0.3"
40 | implementation project(':module2')
41 |
42 | if (Boolean.valueOf(DEBUG_APP_INIT_APT)) {
43 | implementation project(':module1')
44 | } else {
45 | implementation "cn.bingoogolapple:appinit-test-module1:${rootProject.project(':module1').POM_VERSION_NAME}"
46 | }
47 | }
--------------------------------------------------------------------------------
/demo/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep public class * extends android.app.Activity
--------------------------------------------------------------------------------
/demo/app/src/androidTest/java/com/sankuai/erp/component/appinitdemo/TransformTest.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitdemo;
2 |
3 | import static org.junit.Assert.assertNotNull;
4 |
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | /**
11 | * 作者:王浩
12 | * 创建时间:2018/11/21
13 | * 描述:
14 | */
15 | @RunWith(AndroidJUnit4.class)
16 | public class TransformTest {
17 |
18 | @Test
19 | public void testGetInstance() {
20 | assertNotNull(System.out);
21 | }
22 | }
--------------------------------------------------------------------------------
/demo/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/demo/app/src/main/java/com/sankuai/erp/component/appinitdemo/App.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitdemo;
2 |
3 | import android.content.res.Configuration;
4 | import android.support.multidex.MultiDexApplication;
5 | import android.util.Log;
6 |
7 | import com.sankuai.erp.component.appinit.api.AppInitApiUtils;
8 | import com.sankuai.erp.component.appinit.api.AppInitManager;
9 | import com.sankuai.erp.component.appinit.api.SimpleAppInitCallback;
10 | import com.sankuai.erp.component.appinit.common.AppInitItem;
11 | import com.sankuai.erp.component.appinit.common.ChildInitTable;
12 |
13 | import java.util.HashMap;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | public class App extends MultiDexApplication {
18 | private String mInitLogInfo;
19 |
20 | public String getInitLogInfo() {
21 | return mInitLogInfo;
22 | }
23 |
24 | @Override
25 | public void onCreate() {
26 | super.onCreate();
27 | // appInit 新增 START
28 | AppInitManager.get().init(this, new SimpleAppInitCallback() {
29 | /**
30 | * 开始初始化
31 | *
32 | * @param isMainProcess 是否为主进程
33 | * @param processName 进程名称
34 | */
35 | @Override
36 | public void onInitStart(boolean isMainProcess, String processName) {
37 | // TODO 初始化 MtGuard,保证在所有网络请求之前
38 | }
39 |
40 | /*
41 | * 是否为 debug 模式
42 | */
43 | @Override
44 | public boolean isDebug() {
45 | return BuildConfig.DEBUG;
46 | }
47 |
48 | /**
49 | * 通过 coordinate 自定义依赖关系映射,键值都是 coordinate。「仅在需要发热补的情况下才自定义,否则返回 null」
50 | *
51 | * @return 如果返回的 map 不为空,则会在启动是检测依赖并重新排序
52 | */
53 | @Override
54 | public Map getCoordinateAheadOfMap() {
55 | Map coordinateAheadOfMap = new HashMap<>();
56 | coordinateAheadOfMap.put("AppInit:module2:Module2FiveInit", "cn.bingoogolapple:appinit-test-module1:Module1FiveInit");
57 | return coordinateAheadOfMap;
58 | // return null;
59 | }
60 |
61 | /**
62 | * 同步初始化完成
63 | *
64 | * @param isMainProcess 是否为主进程
65 | * @param processName 进程名称
66 | * @param childInitTableList 初始化模块列表
67 | * @param appInitItemList 初始化列表
68 | */
69 | @Override
70 | public void onInitFinished(boolean isMainProcess, String processName, List childInitTableList, List appInitItemList) {
71 | String initLogInfo = AppInitApiUtils.getInitOrderAndTimeLog(childInitTableList, appInitItemList);
72 | Log.d("statisticInitInfo", initLogInfo);
73 | mInitLogInfo = initLogInfo;
74 | }
75 | });
76 | // appInit 新增 END
77 | }
78 |
79 | // 没有在 appInit 扩展中配置 applicationCanonicalName 时,需要接入方手动写以下代码
80 |
81 | @Override
82 | public void onTerminate() {
83 | super.onTerminate();
84 | // appInit 新增 START
85 | AppInitManager.get().onTerminate();
86 | // appInit 新增 END
87 | }
88 |
89 | @Override
90 | public void onConfigurationChanged(Configuration newConfig) {
91 | super.onConfigurationChanged(newConfig);
92 | // appInit 新增 START
93 | AppInitManager.get().onConfigurationChanged(newConfig);
94 | // appInit 新增 END
95 | }
96 |
97 | @Override
98 | public void onLowMemory() {
99 | super.onLowMemory();
100 | // appInit 新增 START
101 | AppInitManager.get().onLowMemory();
102 | // appInit 新增 END
103 | }
104 |
105 | @Override
106 | public void onTrimMemory(int level) {
107 | super.onTrimMemory(level);
108 | // appInit 新增 START
109 | AppInitManager.get().onTrimMemory(level);
110 | // appInit 新增 END
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/demo/app/src/main/java/com/sankuai/erp/component/appinitdemo/AppService.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitdemo;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 | import android.support.annotation.Nullable;
7 |
8 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
9 |
10 | /**
11 | * 作者:王浩
12 | * 创建时间:2018/10/25
13 | * 描述:
14 | */
15 | public class AppService extends Service {
16 |
17 | @Override
18 | public void onCreate() {
19 | super.onCreate();
20 | AppInitLogger.demo("AppService onCreate");
21 | }
22 |
23 | @Override
24 | public int onStartCommand(Intent intent, int flags, int startId) {
25 | AppInitLogger.demo("AppService onStartCommand");
26 | return START_STICKY;
27 | }
28 |
29 | @Nullable
30 | @Override
31 | public IBinder onBind(Intent intent) {
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/demo/app/src/main/java/com/sankuai/erp/component/appinitdemo/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitdemo.activity;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.Fragment;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.widget.TextView;
7 |
8 | import com.sankuai.erp.component.appinit.api.AppInitManager;
9 | import com.sankuai.erp.component.appinitdemo.App;
10 | import com.sankuai.erp.component.appinitdemo.R;
11 | import com.sankuai.waimai.router.Router;
12 |
13 | public class MainActivity extends AppCompatActivity {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_main);
19 |
20 | TextView logTv = findViewById(R.id.tv_main_log);
21 | App app = (App) AppInitManager.get().getApplication();
22 | logTv.setText(app.getInitLogInfo());
23 |
24 | Fragment topFragment = Router.getService(Fragment.class, "fragment1");
25 | if (topFragment != null) {
26 | getSupportFragmentManager().beginTransaction().add(R.id.fl_top, topFragment).commit();
27 | }
28 | Fragment centerFragment = Router.getService(Fragment.class, "fragment2");
29 | if (centerFragment != null) {
30 | getSupportFragmentManager().beginTransaction().add(R.id.fl_center, centerFragment).commit();
31 | }
32 | Fragment bottomFragment = Router.getService(Fragment.class, "fragment3");
33 | if (bottomFragment != null) {
34 | getSupportFragmentManager().beginTransaction().add(R.id.fl_bottom, bottomFragment).commit();
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/demo/app/src/main/java/com/sankuai/erp/component/appinitdemo/init/RouterInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitdemo.init;
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit;
4 | import com.sankuai.erp.component.appinit.common.AppInit;
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
6 | import com.sankuai.waimai.router.Router;
7 | import com.sankuai.waimai.router.common.DefaultRootUriHandler;
8 |
9 | @AppInit(priority = 40, description = "初始化路由")
10 | public class RouterInit extends SimpleAppInit {
11 |
12 | @Override
13 | public void onCreate() {
14 | AppInitLogger.demo("onCreate " + TAG);
15 | // SimpleAppInit 中包含了 mApplication 和 mIsDebug 属性,可以直接在子类中使用
16 | Router.init(new DefaultRootUriHandler(mApplication));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/demo/app/src/main/java/com/sankuai/erp/component/appinitdemo/init/ShellOneInit.kt:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitdemo.init
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit
4 | import com.sankuai.erp.component.appinit.common.AppInit
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger
6 |
7 | /**
8 | * 作者:王浩
9 | * 创建时间:2018/11/28
10 | * 描述:
11 | */
12 | @AppInit(priority = 0, description = "壳工程最先初始化服务")
13 | class ShellOneInit : SimpleAppInit() {
14 |
15 | override fun onCreate() {
16 | AppInitLogger.demo("onCreate $TAG")
17 | }
18 | }
--------------------------------------------------------------------------------
/demo/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
22 |
23 |
24 |
29 |
30 |
37 |
38 |
44 |
45 |
51 |
52 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/demo/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bingoogolapple/AppInit/cedc9f3225062494d2e9c1f220909ffb258b6d5b/demo/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bingoogolapple/AppInit/cedc9f3225062494d2e9c1f220909ffb258b6d5b/demo/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bingoogolapple/AppInit/cedc9f3225062494d2e9c1f220909ffb258b6d5b/demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bingoogolapple/AppInit/cedc9f3225062494d2e9c1f220909ffb258b6d5b/demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bingoogolapple/AppInit/cedc9f3225062494d2e9c1f220909ffb258b6d5b/demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/demo/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AppInitDemo
3 |
4 |
--------------------------------------------------------------------------------
/demo/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/demo/app/test_config.gradle:
--------------------------------------------------------------------------------
1 | android {
2 | testOptions {
3 | unitTests.returnDefaultValues = true
4 | }
5 | }
6 |
7 | dependencies {
8 | testImplementation 'junit:junit:4.12'
9 | testImplementation 'org.mockito:mockito-core:2.8.9'
10 | testImplementation "org.powermock:powermock-module-junit4:1.7.3"
11 | testImplementation "org.powermock:powermock-module-junit4-rule:1.7.3"
12 | testImplementation "org.powermock:powermock-api-mockito2:1.7.3"
13 | testImplementation "org.powermock:powermock-classloading-xstream:1.7.3"
14 |
15 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
16 | androidTestImplementation 'com.android.support.test:rules:1.0.2'
17 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
18 | androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
19 | androidTestImplementation 'com.android.support.test.espresso:espresso-idling-resource:3.0.2'
20 | androidTestImplementation 'com.jakewharton.espresso:okhttp3-idling-resource:1.0.0'
21 | implementation "com.android.support:appcompat-v7:27.1.1"
22 | }
--------------------------------------------------------------------------------
/demo/module1/build.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | if (project.hasProperty('publishLib')) {
3 | implementation "cn.bingoogolapple:appinit-test-module3:${rootProject.project(':module3').POM_VERSION_NAME}"
4 | } else {
5 | implementation project(':module3')
6 | }
7 | }
8 |
9 | if (!project.hasProperty("publishLib")) {
10 | return
11 | }
12 | apply plugin: 'maven'
13 | // ./gradlew :模块名称:clean :模块名称:build :模块名称:uploadArchives -PpublishLib
14 | uploadArchives {
15 | repositories {
16 | mavenDeployer {
17 | repository(url: uri("${System.properties['user.home']}/.gradle/local_repo"))
18 | pom.version = POM_VERSION_NAME
19 | pom.artifactId = POM_ARTIFACT_ID
20 | pom.groupId = POM_GROUP_ID
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/demo/module1/gradle.properties:
--------------------------------------------------------------------------------
1 | # 用于发布到本地仓库测试依赖 aar 的场景
2 | POM_ARTIFACT_ID=appinit-test-module1
--------------------------------------------------------------------------------
/demo/module1/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/module1/src/main/java/com/sankuai/erp/component/appinitmodule1/Module1Activity.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule1;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.Fragment;
5 | import android.support.v7.app.AppCompatActivity;
6 |
7 | import com.sankuai.waimai.router.Router;
8 | import com.sankuai.waimai.router.annotation.RouterUri;
9 |
10 | @RouterUri(path = "/module1")
11 | public class Module1Activity extends AppCompatActivity {
12 |
13 | @Override
14 | protected void onCreate(Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | setContentView(R.layout.activity_module1);
17 |
18 | Fragment topFragment = Router.getService(Fragment.class, "fragment2");
19 | if (topFragment != null) {
20 | getSupportFragmentManager().beginTransaction().add(R.id.fl_top, topFragment).commit();
21 | }
22 | Fragment bottomFragment = Router.getService(Fragment.class, "fragment3");
23 | if (bottomFragment != null) {
24 | getSupportFragmentManager().beginTransaction().add(R.id.fl_bottom, bottomFragment).commit();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/demo/module1/src/main/java/com/sankuai/erp/component/appinitmodule1/Module1Fragment.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule1;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v4.app.Fragment;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 | import com.sankuai.waimai.router.Router;
11 | import com.sankuai.waimai.router.annotation.RouterService;
12 |
13 | @RouterService(interfaces = Fragment.class, key = "fragment1")
14 | public class Module1Fragment extends Fragment {
15 |
16 | @Override
17 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
18 | return inflater.inflate(R.layout.fragment_module1, container, false);
19 | }
20 |
21 | @Override
22 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
23 | getView().findViewById(R.id.btn_go).setOnClickListener(v -> {
24 | Router.startUri(getContext(), "/module1");
25 | getActivity().finish();
26 | });
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/demo/module1/src/main/java/com/sankuai/erp/component/appinitmodule1/Module1Service.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule1;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 | import android.support.annotation.Nullable;
7 |
8 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
9 |
10 | /**
11 | * 作者:王浩
12 | * 创建时间:2018/10/25
13 | * 描述:
14 | */
15 | public class Module1Service extends Service {
16 |
17 | @Override
18 | public void onCreate() {
19 | super.onCreate();
20 | AppInitLogger.demo("Module1Service onCreate");
21 | }
22 |
23 | @Override
24 | public int onStartCommand(Intent intent, int flags, int startId) {
25 | AppInitLogger.demo("Module1Service onStartCommand");
26 | return START_STICKY;
27 | }
28 |
29 | @Nullable
30 | @Override
31 | public IBinder onBind(Intent intent) {
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/demo/module1/src/main/java/com/sankuai/erp/component/appinitmodule1/init/Module1FiveInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule1.init;
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit;
4 | import com.sankuai.erp.component.appinit.common.AppInit;
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
6 |
7 | @AppInit(priority = 20, description = "模块15的描述")
8 | public class Module1FiveInit extends SimpleAppInit {
9 | @Override
10 | public void onCreate() {
11 | AppInitLogger.demo("onCreate " + TAG);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/demo/module1/src/main/java/com/sankuai/erp/component/appinitmodule1/init/Module1FourInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule1.init;
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit;
4 | import com.sankuai.erp.component.appinit.common.AppInit;
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
6 |
7 | @AppInit(priority = 300, description = "模块14的描述")
8 | public class Module1FourInit extends SimpleAppInit {
9 | @Override
10 | public void onCreate() {
11 | AppInitLogger.demo("onCreate " + TAG);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/demo/module1/src/main/java/com/sankuai/erp/component/appinitmodule1/init/Module1OneInit.kt:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule1.init
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit
4 | import com.sankuai.erp.component.appinit.common.AppInit
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger
6 |
7 | /**
8 | * 作者:王浩
9 | * 创建时间:2018/11/28
10 | * 描述:
11 | */
12 | @AppInit(priority = 100, aheadOf = "cn.bingoogolapple:appinit-test-module1:Module1TwoInit", description = "模块11的描述")
13 | class Module1OneInit : SimpleAppInit() {
14 |
15 | override fun onCreate() {
16 | AppInitLogger.demo("onCreate $TAG")
17 | }
18 | }
--------------------------------------------------------------------------------
/demo/module1/src/main/java/com/sankuai/erp/component/appinitmodule1/init/Module1ThreeInit.kt:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule1.init
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit
4 | import com.sankuai.erp.component.appinit.common.AppInit
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger
6 |
7 | /**
8 | * 作者:王浩
9 | * 创建时间:2018/11/28
10 | * 描述:
11 | */
12 | @AppInit(priority = 300, description = "模块13的描述")
13 | class Module1ThreeInit : SimpleAppInit() {
14 |
15 | override fun onCreate() {
16 | AppInitLogger.demo("onCreate $TAG")
17 | }
18 | }
--------------------------------------------------------------------------------
/demo/module1/src/main/java/com/sankuai/erp/component/appinitmodule1/init/Module1TwoInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule1.init;
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit;
4 | import com.sankuai.erp.component.appinit.common.Process;
5 | import com.sankuai.erp.component.appinit.common.AppInit;
6 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
7 |
8 | @AppInit(process = Process.OTHER, priority = 90, description = "模块12的描述")
9 | public class Module1TwoInit extends SimpleAppInit {
10 |
11 | @Override
12 | public void onCreate() {
13 | AppInitLogger.demo("onCreate " + TAG);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/demo/module1/src/main/res/layout/activity_module1.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
28 |
29 |
--------------------------------------------------------------------------------
/demo/module1/src/main/res/layout/fragment_module1.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
13 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/demo/module3/build.gradle:
--------------------------------------------------------------------------------
1 | if (!project.hasProperty("publishLib")) {
2 | return
3 | }
4 | apply plugin: 'maven'
5 | // ./gradlew :模块名称:clean :模块名称:build :模块名称:uploadArchives -PpublishLib
6 | uploadArchives {
7 | repositories {
8 | mavenDeployer {
9 | repository(url: uri("${System.properties['user.home']}/.gradle/local_repo"))
10 | pom.version = POM_VERSION_NAME
11 | pom.artifactId = POM_ARTIFACT_ID
12 | pom.groupId = POM_GROUP_ID
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/demo/module3/gradle.properties:
--------------------------------------------------------------------------------
1 | # 用于发布到本地仓库测试依赖 aar 的场景
2 | POM_ARTIFACT_ID=appinit-test-module3
3 |
4 |
5 |
6 | APP_INIT_DEPENDENCIES=cn.bingoogolapple:appinit-test-module1
--------------------------------------------------------------------------------
/demo/module3/src/baidu/java/com/sankuai/erp/component/appinitmodule3/BaiduInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule3;
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit;
4 | import com.sankuai.erp.component.appinit.common.AppInit;
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
6 |
7 | @AppInit(priority = 60, description = "初始化 baidu 的描述")
8 | public class BaiduInit extends SimpleAppInit {
9 | @Override
10 | public void onCreate() {
11 | AppInitLogger.demo("onCreate " + TAG);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/demo/module3/src/free/java/com/sankuai/erp/component/appinitmodule3/FreeInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule3;
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit;
4 | import com.sankuai.erp.component.appinit.common.AppInit;
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
6 |
7 | @AppInit(priority = 60, description = "初始化 free 的描述")
8 | public class FreeInit extends SimpleAppInit {
9 | @Override
10 | public void onCreate() {
11 | AppInitLogger.demo("onCreate " + TAG);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/demo/module3/src/freeBaidu/java/com/sankuai/erp/component/appinitmodule3/FreeBaiduInit.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule3;
2 |
3 | import com.sankuai.erp.component.appinit.api.SimpleAppInit;
4 | import com.sankuai.erp.component.appinit.common.AppInit;
5 | import com.sankuai.erp.component.appinit.common.AppInitLogger;
6 |
7 | @AppInit(priority = 60, description = "初始化 freeBaidu 的描述")
8 | public class FreeBaiduInit extends SimpleAppInit {
9 | @Override
10 | public void onCreate() {
11 | AppInitLogger.demo("onCreate " + TAG);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/demo/module3/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/module3/src/main/java/com/sankuai/erp/component/appinitmodule3/Module3Activity.java:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule3;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.Fragment;
5 | import android.support.v7.app.AppCompatActivity;
6 |
7 | import com.sankuai.waimai.router.Router;
8 | import com.sankuai.waimai.router.annotation.RouterUri;
9 |
10 | @RouterUri(path = "/module3")
11 | public class Module3Activity extends AppCompatActivity {
12 |
13 | @Override
14 | protected void onCreate(Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | setContentView(R.layout.activity_module3);
17 |
18 | Fragment topFragment = Router.getService(Fragment.class, "fragment1");
19 | if (topFragment != null) {
20 | getSupportFragmentManager().beginTransaction().add(R.id.fl_top, topFragment).commit();
21 | }
22 | Fragment bottomFragment = Router.getService(Fragment.class, "fragment2");
23 | if (bottomFragment != null) {
24 | getSupportFragmentManager().beginTransaction().add(R.id.fl_bottom, bottomFragment).commit();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/demo/module3/src/main/java/com/sankuai/erp/component/appinitmodule3/Module3Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.sankuai.erp.component.appinitmodule3
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.Button
9 | import com.sankuai.waimai.router.Router
10 | import com.sankuai.waimai.router.annotation.RouterService
11 |
12 | /**
13 | * 作者:王浩
14 | * 创建时间:2018/11/28
15 | * 描述:
16 | */
17 | @RouterService(interfaces = [Fragment::class], key = ["fragment3"])
18 | class Module3Fragment : Fragment() {
19 |
20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
21 | return inflater.inflate(R.layout.fragment_module3, container, false)
22 | }
23 |
24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
25 | getView()!!.findViewById