推荐阅读:

滴滴Booster移动App质量优化框架-学习之旅 一

Android 模块Api化演练

不一样视角的Glide剖析(一)

在将业务进行模块化时,避免不了模块页面路由和模块通信, 大多数我们会用到ARouter,EventBus三方库。 模块化过程中有一个尴尬的问题摆在面前:Event事件、Router path放在哪里? 因为很多业务module都需要收发Event事件,进行页面路由,所以只能将Event, Router path下沉到基础库。 这样导致的结果是基础库越来越大,至多 把Event事件、Router path摆放在独立的module,然后基础库依赖这个库,如下图所示:

我们希望业务模块发送的事件,注解使用的Router path都在模块自己这里定义,而不是下层到基础库,当其他module需要路由、事件、 接口就暴露出来。关于这点《微信Android模块化架构重构实践》 也提到了这件事情,并且自创性的使用了一种叫“.api”化的方式来解决这件事情。原理是在编译期将公用接口下沉到基础库同层级, 供其他module使用,而这段代码的维护仍然放到非基础库中。这种base库不会膨胀,代码维护的责任制更明确,确定挺不错。如下图:

在ModuleA,B把XXXBusEvents、XXXRouterParams,暴露的公用接口文件后缀名以.api (并不要求一定.api后者,只要跟后续的自动Api化插件或者脚本一致就行)命名, rebuild之后自动生成ModuleA-api,ModuleB-api 模块,ModuleA,B也会自动添加各自对应 api module依赖。

讲完了原理,下面就可以实现,这里使用ARouter,EventBus,只对Java文件进行Api化,步骤如下:

新建工程,创建base、moduleA、moduleB 模块在moudleA,moduleB中创建api文件

默认情况下,Android stuio 是不能识别.api文件,如果想编辑.api后缀的java文件, 为了能让Android Studio继续高亮该怎么办?可以在File Type中把.api作为java文件类型,操作如下图:

设置好后,可以在.api文件中像java文件一样愉快撸代码了,其他类可以引用.api中的类。

查看setting.gradle文件脚本如下:

  1. include ':app', ':base',':modulea',':moduleb'

include 4个module,做个测试,在setting.gradle include test,同步后,test目录下只有iml文件, 没有build.gradle、AndroidManifest.xml等文件,所以除了拷贝.api文件到对应目录并重命名为.java, 还需要额外创建这两个文件,这里我事先在base module中准备了通用module的build.gradle文件, 拷贝到对应目录即可,AndroidManifest.xml就拷贝base module目录下的,脚本实现如下:

  1. def includeWithApi(String moduleName,String baseModuleName) {
  2. //先正常加载这个模块
  3. include(moduleName)
  4.  
  5. //找到这个模块的路径
  6. String originDir = project(moduleName).projectDir
  7. //这个是新的路径
  8. String targetDir = "${originDir}-api"
  9. //新模块的路径
  10. def sdkName = "${project(moduleName).name}-api"
  11. //新模块名字
  12. String apiName="${moduleName.substring(1,moduleName.length())}-api"
  13.  
  14. //这个是公共模块的位置,我预先放了一个 ApiBuildGralde.gradle 文件进去
  15. String apiGradle = project(baseModuleName).projectDir
  16.  
  17. // 每次编译删除之前的文件
  18. deleteDir(targetDir)
  19.  
  20. //复制.api文件到新的路径
  21. copy() {
  22. from originDir
  23. into targetDir
  24. exclude '**/build/'
  25. exclude '**/res/'
  26. include '**/*.api'
  27. }
  28.  
  29. //直接复制公共模块的AndroidManifest文件到新的路径,作为该模块的文件
  30. copy() {
  31. from "${apiGradle}/src/main/AndroidManifest.xml"
  32. into "${targetDir}/src/main/"
  33. }
  34.  
  35. //file("${targetDir}/src/main/java/com/dhht/${apiName}/").mkdirs()
  36.  
  37. //修改AndroidManifest文件
  38. //fileReader("${targetDir}/src/main/AndroidManifest.xml",apiName);
  39.  
  40. //复制 gradle文件到新的路径,作为该模块的gradle
  41. copy() {
  42. from "${apiGradle}/ApiBuildGralde.gradle"
  43. into "${targetDir}/"
  44. }
  45.  
  46. //删除空文件夹
  47. deleteEmptyDir(new File(targetDir))
  48.  
  49. //重命名一下gradle
  50. def build = new File(targetDir + "/ApiBuildGralde.gradle")
  51. if (build.exists()) {
  52. build.renameTo(new File(targetDir + "/build.gradle"))
  53. }
  54.  
  55. // 重命名.api文件,生成正常的.java文件
  56. renameApiFiles(targetDir, '.api', '.java')
  57.  
  58. //正常加载新的模块
  59. include ":$sdkName"
  60.  
  61. }

修改setting.gradle文件如下:

  1. include ':app', ':base'
  2. includeWithApi(":modulea",":base")
  3. includeWithApi(":moduleb",":base")

rebuild后,就可以看到moduleA-api,moduleB-api,并有对应的java文件如下图:

添加moduleA路由到moduleB,moduleB给moduleA发送事件逻辑,进行打包,会报如下错误:

很显然,ARouter注解处理器无法识别.api文件,path置为null处理,在moduleA,B添加对应的***-api模块依赖,就可以打包成功了。

奔着偷懒的原则,不想每次手动添加***-api模块依赖,自动动态添加依赖,实现gradle脚本如下:

  1. ext{
  2. //自动添加***-api依赖
  3. autoImportApiDependency = {extension -> //extension project对象
  4. def children = project.rootProject.childProjects
  5. //遍历所有child project
  6. children.each {child ->
  7. //判断 是否同时存在 *** module 和 ***-api module
  8. if(child.key.contains("-api") && children.containsKey(child.key.substring(0,child.key.length() - 4))){
  9. print "\n"
  10.  
  11. def targetKey = child.key.substring(0,child.key.length() - 4)
  12. def targetProject = children[targetKey]
  13.  
  14. targetProject.afterEvaluate {
  15.  
  16. print '*********************\n'
  17. print targetProject.dependencies
  18. //通过打印 所有dependencies,推断需要添加如下两个依赖
  19. targetProject.dependencies.add("implementation",targetProject.dependencies.create(project(":" + child.key)))
  20. targetProject.dependencies.add("implementationDependenciesMetadata",targetProject.dependencies.create(project(":" + child.key)))
  21.  
  22. //打印 module 添加的依赖
  23. targetProject.configurations.each {configuration ->
  24. print '\n---------------------------------------\n'
  25. configuration.allDependencies.each { dependency ->
  26.  
  27. print configuration.name + "--->" +dependency.group + ":" + dependency.name + ":" + dependency.version +'\n'
  28. }
  29.  
  30. }
  31.  
  32. print '*********************\n'
  33. }
  34.  
  35. }
  36.  
  37. }
  38. }
  39. }

autoImportApiDependency 方法封装在Config.gradle,在根build.gradle中调用:

  1. apply from: 'Config.gradle'
  2. ext.autoImportApiDependency(this)

可以正常打包,并成功运行了。

遇坑集锦

1.kotlin集成ARouter,尽管设置了AROUTER_MODULE_NAME,依然报如下错误: ARouter::Compiler An exception is encountered, [null] 可以考虑是否是gradle和 kotlin 版本的问题。

2.业务模块moduleA处于集成模式时,即集成到App壳工程中去,也会将单一模块做 成App启动的源码和资源打包apk中,尽管设置了sourceSets,也没效果。

问题就出在debug文件夹的名字,把debug文件夹改成其他名字,就没有这个问题了,是不是很奇怪!没去究其原因。

传送门:懒人模式开启Android模块自动化Api之旅github

参考资料:

微信 Android 模块化架构重构实践(上)

Android实现模块 api 化

美团猫眼电影Android模块化实战总结

如果您对博主的更新内容持续感兴趣,请关注公众号!

懒人模式开启Android模块自动化Api之旅的更多相关文章

  1. 【原创】窥视懒人的秘密---android下拉刷新开启手势的新纪元

    小飒的成长史原创作品:窥视懒人的秘密---android下拉刷新开启手势的新纪元转载请注明出处 **************************************************** ...

  2. 懒人模式Singleton模式Meyers版本号

    直接看代码: /* Singleton模式保证:在一个程序,,一个类有且只有一个实例.并提供一个访问 它的全局访问点 在编程其中.很多情况下,需要确保有一类的一个实例 比如: windopws系统中仅 ...

  3. Android Studio “懒人”必备插件android layout id converter

    在一个布局文件里.假设定义了非常多非常多id,代码中一个个findview是一件非常枯燥而且浪费时间的事情. 所以这里向大家推荐一个必备插件android layout id converter. 配 ...

  4. Android 拍摄(横\竖屏)视频的懒人之路

    想一想,我们聊过AudioReord,AudioTrack,MediaPlayer,那多媒体四大金刚,就剩下了MediaRecorder了(SoundPool?我这里信号不好···).其实MediaR ...

  5. C# 嵌入dll 动软代码生成器基础使用 系统缓存全解析 .NET开发中的事务处理大比拼 C#之数据类型学习 【基于EF Core的Code First模式的DotNetCore快速开发框架】完成对DB First代码生成的支持 基于EF Core的Code First模式的DotNetCore快速开发框架 【懒人有道】在asp.net core中实现程序集注入

    C# 嵌入dll   在很多时候我们在生成C#exe文件时,如果在工程里调用了dll文件时,那么如果不加以处理的话在生成的exe文件运行时需要连同这个dll一起转移,相比于一个单独干净的exe,这种形 ...

  6. VopSdk一个高逼格微信公众号开发SDK:自动化生产(装逼模式开启)

    VopSdk一个高逼格微信公众号开发SDK(源码下载) VopSdk一个高逼格微信公众号开发SDK:自动化生产(装逼模式开启) 针对第一版,我们搞了第二版本,老规矩先定个目标. 一 我们的目标 a.移 ...

  7. 开启Android Apk调试与备份选项的Xposed模块的编写

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80963610 在进行Android应用程序逆向分析的时候,经常需要进行Andro ...

  8. 强制开启android webview debug模式使用Chrome inspect

    强制开启android webview debug模式使用Chrome inspect https://blog.csdn.net/zhulin2609/article/details/5143782 ...

  9. 网络编程懒人入门(十):一泡尿的时间,快速读懂QUIC协议

    1.TCP协议到底怎么了? 现时的互联网应用中,Web平台(准确地说是基于HTTP及其延伸协议的客户端/服务器应用)的数据传输都基于 TCP 协议. 但TCP 协议在创建连接之前需要进行三次握手(如下 ...

随机推荐

  1. 高性能流媒体服务器EasyDSS前端重构(三)- webpack + vue + AdminLTE 多页面引入 element-ui

    接上篇 接上篇<高性能流媒体服务器EasyDSS前端重构(二) webpack + vue + AdminLTE 多页面提取共用文件, 优化编译时间> 本文围绕着实现EasyDSS高性能流 ...

  2. android菜鸟学习笔记1----环境搭建

    Step1 JDK安装及配置: 1.下载并安装JDK: 根据自己系统情况,选择安装相应的JDK版本 当前系统:64位WIN8,内存8G 选择了Java SE 8u45 即JDK 1.8.0_45,可以 ...

  3. framemarker的使用

    1 什么是framemarker framemarker是网页模版和数据模型的结合体.装载网页的时候,framemarker自动从数据模型中提取数据并生成html页面. 2 framemarker怎么 ...

  4. CUDA:纹理内存

    纹理内存: 与常量内存类似,纹理内存是另一种形式的只读内存,并且同样缓存在芯片上.因此某些情况下能够减少对内存的请求并提供高效的内存带宽.纹理内存是专门为那些在内存访问模式中存在大量空间局部性的图形应 ...

  5. 使用 Spring 容器管理 Filter

    当我们用Filter时,往往需要使用一些辅助的service,在普通的java中,只要声明(set,get方法)后在spring-application配置文件中配置就可以了,但是由于Filter与L ...

  6. 爬虫之重要的requests模块

    一 . requests模块 什么是requests模块 requests模块是python中原生的基于网络请求的模块,其主要作用是用来模拟浏览器发起请求.功能强大,用法简洁高效.在爬虫领域中占据着半 ...

  7. NiFi汉化

    ①在源文件中的 source-nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src中修 ...

  8. Linux--struct file结构体【转】

    本文转载自:https://www.cnblogs.com/hanxiaoyu/p/5677677.html struct file(file结构体): struct file结构体定义在includ ...

  9. 数据库,序列化数据为json字符串

    create PROCEDURE [dbo].[usp_SerializeJSON] @ParameterSQL as varchar(max) AS BEGIN declare @SQL nvarc ...

  10. PS滤镜— —波浪效果

    clc; clear all; close all; addpath('E:\PhotoShop Algortihm\Image Processing\PS Algorithm'); I=imread ...