Android 组件化通信-api化

如果module A想使用module B中的某个功能,要不是将类下沉为基类,或者是提供一个注册中心通过回调函数来调用。前者会造成基础module越来越臃肿,通过注册中心则会造成代码耦合度增加,内存消耗严重,很多不会立即用到的数据都要放到注册中心

  1. Android提供了.java api化的功能,具体操作就是将.java文件的后缀改为.api,这样该类就会以接口的形式被外部引用。改为.api后IDE可能无法识别,需要在studio中做如下设置
  2. 即便是把.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')
  3. 在setting.gradle中除了要include基本的module,还需要调用includeWithApi(“:要暴露接口的module名称”)
  4. 在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. 配置都完成了,开始使用了
  • 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);
}

已知问题:

  1. 由于自动生成的-api module中的build.gradle和androidManifest.xml都是从一个模板复制过来的,而且每次build都会自动覆盖,导致无法手动添加其它的一些属性

注意事项:

禁止手动修改-api工程中的任何文件,因为rebuild后会自动覆盖,不会起作用

发布了21 篇原创文章 · 获赞 21 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u013894711/article/details/92105863