├── .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 | [![Download](https://api.bintray.com/packages/bingoogolapple/maven/bga-appinit-plugin/images/download.svg)](https://bintray.com/bingoogolapple/maven/bga-appinit-plugin/_latestVersion) 4 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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 | ![业界初始化方案对比](docs/imgs/comparison.png) 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 | ![停止从 Meituan-Dianping 账号开源后的已 Fork 列表](docs/imgs/disclaimer.png) 46 | 47 | ## 作者联系方式 48 | 49 | | 个人主页 | 邮箱 | 50 | | ------------- | ------------ | 51 | | bingoogolapple.cn | bingoogolapple@gmail.com | 52 | 53 | | 个人微信号 | 微信群 | 公众号 | 54 | | ------------ | ------------ | ------------ | 55 | | 个人微信号 | 微信群 | 公众号 | 56 | 57 | | 个人 QQ 号 | QQ 群 | 58 | | ------------ | ------------ | 59 | | 个人 QQ 号 | QQ 群 | 60 | 61 | ## 打赏支持作者 62 | 63 | 如果您觉得 BGA 系列开源库或工具软件帮您节省了大量的开发时间,可以扫描下方的二维码打赏支持。您的支持将鼓励我继续创作,打赏后还可以加我微信免费开通一年 [上帝小助手浏览器扩展/插件开发平台](https://github.com/bingoogolapple/bga-god-assistant-config) 的会员服务 64 | 65 | | 微信 | QQ | 支付宝 | 66 | | ------------- | ------------- | ------------- | 67 | | 微信 | QQ | 支付宝 | 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 set, RoundEnvironment roundEnv) { 76 | String annotationSimpleName = getAnnotationClass().getSimpleName(); 77 | Set 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 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 |