美文网首页Android技术知识2021面试学习
Android高度组件化并远程依赖

Android高度组件化并远程依赖

作者: 奔跑吧李博 | 来源:发表于2021-03-26 23:11 被阅读0次

    什么是组件化

    组件(Component)是对数据和方法的简单封装,功能单一,高内聚,并且是业务能划分的最小粒度。

    组件化是基于组件可重用的目的上,将一个大的软件系统按照分离关注点的形式,拆分成多个独立的组件,使得整个软件系统也做到电路板一样,是单个或多个组件元件组装起来,哪个组件坏了,整个系统可继续运行,而不出现崩溃或不正常现象,做到更少的耦合和更高的内聚。

    模块化与组件化
    • 模块化
      模块化就是将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能相关的内容,模块我们相对熟悉,比如登录功能可以是一个模块,搜索功能可以是一个模块等等。

    • 组件化
      组件化就是更关注可复用性,更注重关注点分离,如果从集合角度来看的话,可以说往往一个模块包含了一个或多个组件,或者说模块是一个容器,由组件组装而成。简单来说,组件化相比模块化粒度更小,两者的本质思想都是一致的,都是把大往小的方向拆分,都是为了复用和解耦,只不过模块化更加侧重于业务功能的划分,偏向于复用,组件化更加侧重于单一功能的内聚,偏向于解耦。

    组件化优势

    1.提高编译速度,从而提高并行开发效率
    2.每个组件有自己独立的版本,可以独立编译、测试、打包和部署
    3.避免模块之间的交叉依赖,做到低耦合、高内聚
    4.组件之间可以灵活组建,快速生成不同类型的定制产品,可拆可装

    组件化需要考虑问题

    • 组件之间跳转

    组件之间是无法相互引用的,所以做跳转和通信需要做处理,实现办法就是路由。

    什么是路由?
    app的一个页面就可以类比于一个个网站里面的页面,浏览器的每个页面由url定义,给不同url传递不同参数,页面的表现形式还稍有不通过,这里的映射关系就是url对应页面,每个app的每个页面也可以类比于网站的页面,那是不是可以采用url的方式来定义每个页面呢?这样是不是也就有了url对应app页面的映射关系,如果有了这样的映射关系,给定一个url,那是不是就可以知道跳转到某一个具体的Activity了?Android路由也是一个映射表,用来映射Uri和对应的页面跳转,这个url就是组件名+页面名来拼接。

    我这里做跳转用的是ARouter。这里是我的另一篇ARouter解析

    • 组件化解决重复依赖

    组件从开始设计的时候就需要严格分好依赖层级,组件之间不可相互依赖,不可重复依赖,业务组件只可依赖必须的base组件。主app壳组件依赖其他所有组件。

    • 组件单独运行

    只需要把 Apply plugin: 'com.android.library' 切换成Apply plugin: 'com.android.application' 。我们可以通过 Gradle脚本配置方式,修改properties配置可让某个组件单独运行。

    • 组件化时资源名冲突

    color,shape,drawable,图片资源,布局资源,或者anim资源等等,都有可能造成资源名称冲突。大家都在不同组件下,通常不会交流,有可能造成冲突,所以在项目创建初期,需要定义好公共资源,很少修改。在版本不断升级,业务不断复杂,肯定还是避免不了不同的资源文件,所以需要要有按模块区分命名规则。

    • 子模块application监听主模块application的生命周期,进行初始化操作

    1.在Base库中定义BaseApplication,实现IApplication接口,在BaseApplication的onCreate中进行通用的初始化。
    2.Module中的Application继承BaseApplication,并且实现专属初始化的方法。
    3.整体运行时,使用CC-Register将每个Module的Application注入到ApplicationManager中,在壳app的Application初始化时调用ApplicationManager的初始化方法进行初始化。
    参考文章

    • 组件之间相互调用

    组件之间相互通信是少不了的,各组件间不能直接调用。组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦。

    需要暴露功能给别的组件调用的组件,需要在公共模块base里面去声明接口,继承ARouter库中的Iprovider接口。然后在自己模块中实现该接口的功能。别的模块直接调用该暴露的接口而实现功能。

    在公共模块给各个组件定义一个包,里面创建需要提供给外部使用的接口SwitchFarmProvider,需要继承ARouter提供的IProvider接口。

    interface SwitchFarmProvider: IProvider {
    
        fun showSwitchFarmDialog(pos: Int, fragmentManager: FragmentManager)
    
    }
    

    然后在自己组件中去实现该方法的功能,这样就能提供给外部组件调用内部的方法,而不用将该功能将低到公共组件中。

    @Route(path = "/xxx/xxx", name = "xxx")
    class SwitchFarmImp: SwitchFarmProvider {
    
        override fun showSwitchFarmDialog(pos: Int, fragmentManager: FragmentManager) {
            SwitchFarmAllDialog().switchTab(pos).show(fragmentManager, "")
        }
    
        override fun init(context: Context?) {
    
        }
    }
    

    调用方式,通过ARouter提供的ARouter.getInstance().navigation(Class)方法获取该实现类,调用其公共方法。

    ARouter.getInstance().navigation(SwitchFarmProvider.class).showSwitchFarmDialog(2, getFragmentManager());
    
    • 将组件发布到远程仓库

    不同部门的开发分工更加明确之后,不属于自己维护的组件范围不能随意地修改。基本上自己负责自己模块下的组件,尽可能少地改动别的组件代码。这一块的配置是全文终点,敲黑板了。

    我这里将各个组件发布到阿里云 maven库中,发布方法见我另一篇文章——发布开源库到阿里云 maven仓库。发布之后,可以看到远程仓库里的库。这里需要注意的是,组件不要依赖本地组件,而是从底层开始逐渐依赖,按照依赖顺序上传,否则很可能会依赖错误。

    然后各个模块引入库,在app下都引入远程依赖,在settings.gradle中移除各个组件的include。那么项目文件夹就变为了无本地依赖的状态:

    此时你的项目仍然是能够运行起来的,不过编译运行的代码就不是你本地的了,而是直接运行各个远程的aar包。

    那么平时开发怎么修改我们本地的代码呢?做配置,可分别设置某个组件是依赖本地还是远程,依赖本地的组件可尽心开发修改,发布上传新的版本。
    做法是在各组件下新建gradle.properties读取里面的配置,比如设置true表示依赖远程。在settings.gradle中读取该文件的属性,看是否需要依赖本地的组件。在项目的build.gradle中配置,读取该true/false属性,判断是依赖本地库还是远程库。

    settings.gradle中配置:

    includeCompat ':module-play'
    includeCompat ':module-notice'
    includeCompat ':module-community'
    includeCompat ':module-user'
    includeCompat ':module-login'
    includeCompat ':module-home'
    includeCompat ':module-entrance'
    includeCompat ':library-res'
    includeCompat ':library-network'
    includeCompat ':library-base'
    includeCompat ':app'
    rootProject.name = "MvvmFrame"
    
    def includeCompat(String name) {
        if (!isMaven(name)) {
            include(name)
        }
    }
    
    def isMaven(String name) {
        println("isMaven" + name)
        Properties properties = new Properties()
        def file = new File("${name.replace(":", "")}/maven.properties")
        if (file.exists()) {
            InputStream inputStream = file.newDataInputStream()
            properties.load(inputStream)
            def str = properties.getProperty('MAVEN')
            if (str == null) {
                return false
            } else {
                return Boolean.parseBoolean(str)
            }
        }
        return false
    }
    

    项目的build.gradle中配置:

        //根据是否为远程依赖设置依赖远程库还是本地库
        ext.projectCompat = { name ->
            def realName = name.replace(":", "")
            if (isMaven(realName)) {
                return "com.libo:${realName}:${mavenVersion(realName)}"
            } else {
                return project(name)
            }
        }
    
        //判断当前组件是否为远程依赖
        ext.isMaven = { name ->
            Properties properties = new Properties()
            def file = rootProject.file("${name}/maven.properties")
            if (file.exists()) {
                InputStream inputStream = file.newDataInputStream()
                properties.load(inputStream)
                def str = properties.getProperty('MAVEN')
                if (str == null) {
                    return false
                } else {
                    return Boolean.parseBoolean(str)
                }
            }
            return false
        }
    
        //读取maven.property文件中库版本
        ext.mavenVersion = { name ->
            println("mavenVersion::${name}")
            Properties properties = new Properties()
            def file = rootProject.file("${name}/maven.properties")
            if (file.exists()) {
                InputStream inputStream = file.newDataInputStream()
                properties.load(inputStream)
                def str = properties.getProperty('VERSION')
                if (str == null) {
                    throw Exception(file.path + "    VERSION == null")
                } else {
                    return str
                }
            }
            return ""
        }
    

    app下的build.gradle中这样依赖,判断是依赖远程还是本地。

        implementation projectCompat(":library-res")
        implementation projectCompat(":library-base")
        implementation projectCompat(":library-network")
        implementation projectCompat(":module-entrance")
        implementation projectCompat(":module-home")
        implementation projectCompat(":module-community")
        implementation projectCompat(":module-notice")
        implementation projectCompat(":module-user")
        implementation projectCompat(":module-login")
        implementation projectCompat(":module-play")
    

    该项目配置的github地址,需要细看的戳这里

    • 组件化各组件初始化如何做的?

    使用AutoRegister插件。
    使用此插件后,在编译期(代码混淆之前)扫描所有打到apk包中的类,将符合条件的类收集起来,并生成注册代码到指定的类的static块中,自动完成注册。
    app的build.gradle下加入插件,加入配置信息:

    id 'auto-register'
    
    autoregister {
        registerInfo = [
                [
                        'scanInterface'             : 'com.example.base.network.application.AppRegister'   //注册的接口
                        , 'codeInsertToClassName'   : 'com.example.base.network.application.InitCls'   //注册绑定的类
                        //未指定codeInsertToMethodName,默认插入到static块中,故此处register必须为static方法
                        , 'registerMethodName'      : 'register'   //注册的方法
                ]
        ]
    }
    

    在项目build.gradle中加入如下依赖,来自https://github.com/ooftf/AutoRegister,修正后的引用,

    repositories {
            maven {
                url "https://dl.bintray.com/ooftf/maven"
            }
        }
    buildscript {
        dependencies {
            classpath 'com.android.tools.build:gradle:3.0.0'
            classpath 'com.ooftf:autoregister:x.x.x'
        }
    }
    

    在base组件中,创建AppRegister接口和InitCls类:

    interface AppRegister {
    
        fun initSdk(application: Application)
    }
    
    object InitCls {
        var registers = ArrayList<AppRegister>()
    
        @JvmStatic
        fun register(register: AppRegister) {
            registers.add(register)
        }
    }
    

    在子模块中实现AppRegister,实现需要初始化的initSdk方法:

    class SdkInit: AppRegister {
    
        override fun initSdk(application: Application) {
            Toast.makeText(application, "初始化成功", Toast.LENGTH_SHORT).show()
            LeakCanary.install(application)
        }
    
    }
    

    在application中的onCreate方法中实现接口回调通知:

    InitCls.registers.forEach {
                it.initSdk(this)
            }
    

    该插件做了扫描各个类,扫描到实现的AppRegister(对应scanInterface)接口的类,会在加载InitCls(对应codeInsertToClassName)类时,在该类的静态代码块中,循环将各个类调用register(对应registerMethodName)方法,添加到registers集合中,在application初始化的时候,遍历registers调用initSdk让各个组件初始化方法调用。

    相关文章

      网友评论

        本文标题:Android高度组件化并远程依赖

        本文链接:https://www.haomeiwen.com/subject/jhmlhltx.html