如果module A想使用module B中的某个功能,要不是将类下沉为基类,或者是提供一个注册中心通过回调函数来调用。前者会造成基础module越来越臃肿,通过注册中心则会造成代码耦合度增加,内存消耗严重,很多不会立即用到的数据都要放到注册中心
- Android提供了.java api化的功能,具体操作就是将.java文件的后缀改为.api,这样该类就会以接口的形式被外部引用。改为.api后IDE可能无法识别,需要在studio中做如下设置
- 即便是把.java改成了.api,文件依然不能被外部引用,因为该类还是存在与module B中,这时候需要在setting.gradle添加一段脚本,每次build的时候都会自动创建一个-api工程,省去了手动创建的麻烦
def includeWithApi(String moduleName) { //先正常加载这个模块 include(moduleName) //找到这个模块的路径 String originDir = project(moduleName).projectDir //这个是新的路径 String targetDir = "${originDir}-api" //新模块的路径 def sdkName = "${project(moduleName).name}-api" //manifest中package的值 String packageValue = "${moduleName.substring(1, moduleName.length())}_api" //存放模板manifest和gradle的文件夹路径,里面存放着ApiBuildGradle.gradle和AndroidManifest.xml两个文件 String apiGradle = 'api-config' // 每次编译删除之前的文件 deleteDir(targetDir) //复制.api文件到新的路径 copy() { from originDir into targetDir exclude '**/build/' exclude '**/res/' include '**/*.api' } //直接复制公共模块的AndroidManifest文件到新的路径,作为该模块的文件 copy() { from "${apiGradle}/AndroidManifest.xml" into "${targetDir}/src/main/" } //修改manifest文件的package值,如果不同module存在相同的package value编译会报错 modifyManifestPackage("${targetDir}/src/main/AndroidManifest.xml", "com.jack.${packageValue}") //复制 gradle文件到新的路径,作为该模块的gradle copy() { from "${apiGradle}/ApiBuildGradle.gradle" into "${targetDir}/" } //重命名一下gradle def build = new File(targetDir + "/ApiBuildGradle.gradle") if (build.exists()) { build.renameTo(new File(targetDir + "/build.gradle")) } //删除空文件夹 deleteEmptyDir(new File(targetDir)) // 重命名.api文件,生成正常的.java文件 renameApiFiles(targetDir, '.api', '.java') //正常加载新的模块 include ":$sdkName" } private void deleteEmptyDir(File dir) { if (dir.isDirectory()) { File[] fs = dir.listFiles(); if (fs != null && fs.length > 0) { for (int i = 0; i < fs.length; i++) { File tmpFile = fs[i]; if (tmpFile.isDirectory()) { deleteEmptyDir(tmpFile); } if (tmpFile.isDirectory() && tmpFile.listFiles().length <= 0) { tmpFile.delete() } } } if (dir.isDirectory() && dir.listFiles().length == 0) { dir.delete() } } } private void deleteDir(String targetDir) { FileTree targetFiles = fileTree(targetDir) targetFiles.exclude "*.iml" targetFiles.each { File file -> file.delete() } } /** * rename api files(java, kotlin...) */ private def renameApiFiles(root_dir, String suffix, String replace) { FileTree files = fileTree(root_dir).include("**/*$suffix") files.each { File file -> file.renameTo(new File(file.absolutePath.replace(suffix, replace))) } } static def modifyManifestPackage(String manifestFilePath, String packageName) { //参数配置 def updatedContent = new File(manifestFilePath).getText('UTF-8') .replaceAll("com.api.package", packageName) new File(manifestFilePath).write(updatedContent, 'UTF-8')
- 在setting.gradle中除了要include基本的module,还需要调用includeWithApi(“:要暴露接口的module名称”)
-
在config.gradle中添加如下脚本,不然会出现依赖.api接口出现文件找不到的错误
//自动添加***-api依赖 autoImportApiDependency = { extension -> //extension project对象 def children = project.rootProject.childProjects //遍历所有child project children.each { child -> //判断 是否同时存在 *** module 和 ***-api module if (child.key.contains("-api") && children.containsKey(child.key.substring(0, child.key.length() - 4))) { print "\n" def targetKey = child.key.substring(0, child.key.length() - 4) def targetProject = children[targetKey] targetProject.afterEvaluate { print targetProject.dependencies //通过打印 所有dependencies,推断需要添加如下两个依赖 targetProject.dependencies.add("implementation", targetProject.dependencies.create(project(":" + child.key))) // targetProject.dependencies.add("implementationDependenciesMetadata", targetProject.dependencies.create(project(":" + child.key))) //打印 module 添加的依赖 targetProject.configurations.each { configuration -> print '\n---------------------------------------\n' configuration.allDependencies.each { dependency -> print configuration.name + "--->" + dependency.group + ":" + dependency.name + ":" + dependency.version + '\n' } } } } } }
- 配置都完成了,开始使用了
- 5.1在module-test中新建一个UserService (BaseModuleService存在于基类module),并将UserService的后缀改为.api
public interface UserService extends BaseModuleService {
String getName();
void setAge(int age);
}
- 5.2 新建UserServiceImpl,实现UserService接口
public class UserServiceImpl implements UserService {
@Override
public String getName() {
return "i am jack";
}
@Override
public void setAge(int age) {
System.out.println("age is==============" + age);
}
}
- 5.3 重新build工程后就能得到一个module-test-api工程,现在如果有外部module可以直接引用module-test-api了,该工程只有暴露的接口,没有具体的实现,这也符合开发原则。当然了也可以暴露一些具体的实现类,比如将TestUtils改为.api,这样这个TestUtils.api也会出现在module-test-api中,外部类就可以直接使用(不推荐,因为将具体的实现也暴露出去了)
- 注:拿TestUtils来举例,如果TestUtils有其它的依赖类,那么其它的依赖类也需要api化,不然会出现找不到依赖的错误,所以在暴露接口的时候尽量减少类的依赖
6. 接口暴露出去后外部module也只能拿到一个UserService,这样肯定是不能直接调用具体的实现的,所以在基础module中要有一个注册中心
public class RegisterCenter {
private static final RegisterCenter INSTANCE = new RegisterCenter();
public static RegisterCenter getInstance() {
return INSTANCE;
}
private RegisterCenter() {
}
public void registerSrvice(String serviceName, BaseModuleService service) {
serviceMap.put(serviceName, service);
}
private Map<String, BaseModuleService> serviceMap = new ConcurrentHashMap<>();
private void unregisterService(String serviceName) {
serviceMap.remove(serviceName);
}
public <T extends BaseModuleService> T getService(String serviceName) {
try {
return (T) serviceMap.get(serviceName);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void unregisterAllServices() {
serviceMap.clear();
}
}
7. 向注册中心注册一个接口
UserService userService = new UserServiceImpl();
RegisterCenter.getInstance().registerSrvice(Constant.USER_SERVICE_NAME, userService);
8.在其它module中拿到UserService对象并使用(获取到的Service可能为空,需要判空处理,最好是将类型也判断一下)
UserService userService = RegisterCenter.getInstance().getService(Constant.USER_SERVICE_NAME);
if (userService != null) {
String name = userService.getName();
System.out.println("===========================" + name);
}
已知问题:
- 由于自动生成的-api module中的build.gradle和androidManifest.xml都是从一个模板复制过来的,而且每次build都会自动覆盖,导致无法手动添加其它的一些属性
注意事项:
禁止手动修改-api工程中的任何文件,因为rebuild后会自动覆盖,不会起作用