美文网首页
Kotlin之模块化开发

Kotlin之模块化开发

作者: Null_Point | 来源:发表于2017-08-21 21:03 被阅读0次

    一、模块化浅谈

    1. 什么是模块化开发?

    模块化就是将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能相关的内容。比如登录功能可以是一个模块。

    2. 模块化开发的优势?

    • 解耦性强:随着业务的增多,代码变的越来越复杂,每个模块之间的
      代码耦合变得越来越严重,解耦问题急需解决。
    • 编译时间大大减少:以为业务场景对,代码越来越大,同时编译时间也会越来越长。
    • 提高团队协同开发:团队协同开发存在较多的冲突.不得不花费更多的时间去沟通和协调,影响开发效率 。

    二、模块化开发的架构分层

    本项目是一个移动端的后台运营管理系统。按照功能划分,暂有统计模块、直播模块、音频模块和个人中心。


    基本项目结构图

    命名建议:
    底层:Library
    中间层:Module + 业务或功能名字
    上层:App + 项目名字

    1. 基本结构

    app模块是每个项目初始都有的,实质上仍然是一个module。Android Stuido项目中,我们和代码打交道的大部分时间都在module中。当然,具体的module机制我们不去深究,有时间可以另开一坑去研究。下面的三个module大家可以理解为kotlin中的library,当然这样说不准确。
    app作为主要的module,主要承担了应用启动以及最上层的通用逻辑。比如在这个例子中,应用启动、首页展示和模块切换的业务逻辑都在这个模块。
    而下属的三个子模块承担了各自细分的业务,并且可以被app模块或者其他模块引用,每个模块只负责和考虑自己的业务。以此达到业务逻辑和代码分离的逻辑。

    按照之前的业务逻辑划分,项目结构可以先大致分为如下的结构:


    其中箭头方向表示了引用关系

    2. 系统层分离

    仅仅这样是不够的。因为在AS中,module的引用是单向的。如果A module引用了B module,那么对A来讲,B是可见的,B的所有公开功方法理论上都可以在A中使用,但是对B来说,A是不可见的。所以,这样的结构出现的问题就是我们有大量的底层通用方法都放在app模块中,对于子模块来讲是不可见的,子模块无法引用封装好的底层方法,例如网络请求,图片加载,文件拷贝等,这肯定是不行的。所以这个结构还得再优化。

    按照module单向引用的原则,我们可以把公共底层通用方法单独分出来作为一个moduleBase,同时这个moduleBase也是最底层的module,保证对于其他module来说它都是可见的。这样还有一个好处就是这个moduleBase中的方法和配置大部分都是可以高度复用的。在新开其他项目的时候这个模块可以直接迁移到新项目中,而不用在为代码分离和剔除浪费时间。所以,项目的结构进一步细化成了如下的结构:


    基本的模块化项目结构

    3. 公共层分离

    上述的模块如果在使用中,有很大概率会遇到一个问题,部分的实体类、自定义view、布局文件或者资源文件在各个模块都需要用到,但是这些如果放在系统层的moduleBase里面,又会破坏系统层的通用性。所以,我们还需要一个公共层Provider来专门提供上层业务逻辑模块的公共资源。直接上图:


    完整的模块化项目结构

    4. 扩展

    待完善...

    三、如何进行安卓模块化开发

    1. 创建Module模块

    将所需的模块在对应项目的Project目录下拷贝出来,粘贴到要开发的项目的Project根目录下即可。也可以直接在project下新建一个Module。

    File --> New --> New Module --> Android Library (建议选择这个) --> Finish
    

    一个模块这样就创建完成了。默认的名字是app,根据项目的需要将模块的名字进行修改,直接Refactor > Rename...即可


    模块的名称修改

    2. 将Module模块引入主项目中

    设置setting.gradle 中

    include ':app', ':UserCenter'
    

    设置后发现项目目录下增加了一个模块


    新增的用户登录模块

    在主模块的build.gradle中设置

    api project(':UserCenter')
    

    3. 模块中application和library状态切换配置

    1. 设置一个开关控制application和library状态切换
    我们在开发的时候,Module如果是一个库,会使用com.android.library插件,如果是一个应用,则使用com.android.application插件,接下来根据这个变量来进行判断并且实现状态切换。
    Project根目录下gradle.properties中设置变量来控制。

    isUserModule = false
    

    isUserModule=false:表示这个模块是一个UserCenter的Module;
    isUserModule=true:表示这个模块是一个app;

    2. 依赖项目中build.gradle配置
    在模块的build.gradle的开头处设置。

    //顶部插件引入
    if(isUserModule.toBoolean()){
        apply plugin: 'com.android.library'
    }else {
        apply plugin: 'com.android.application'
    }
    android {
        ...
        sourceSets{
            main{
                if (isUserModule.toBoolean()){
                    manifest.srcFile 'src/main/release/AndroidManifest.xml'
                    //release模式下排除debug文件夹中的所有Java文件
                    java{
                        exclude 'debug/**'
                    }
                }else{
                    manifest.srcFile 'src/main/debug/AndroidManifest.xml'
                }
            }
        }
    }
    

    3. 提供两套 AndroidManifest.xml并进行动态切换
    mainfest文件也需要提供两套

    android {
        ...
        sourceSets{
            main{
                if (isUserModule.toBoolean()){
                    manifest.srcFile 'src/main/release/AndroidManifest.xml'
                    //release模式下排除debug文件夹中的所有Java文件
                    java{
                        exclude 'debug/**'
                    }
                }else{
                    manifest.srcFile 'src/main/debug/AndroidManifest.xml'
                }
            }
        }
    }
    

    四、Kotlin模块化开发过程中遇到的问题

    1. 资源名称冲突

    Android Studio 默认 library 的所有的 resource 为 public , 所以在模块化开发过程中总会遇到资源冲突问题。列出两种解决方法。

    • 方法一:
      保护某些 resources 不被外部访问,可以创建res/values/public.xml,因为 public 是关键词,搜易需要用 new file 的方式创建。至少添加一行,为添加的视为 private
    <resources>
        <public name="mylib_app_name" type="string"/>
    </resources>
    
    • 方法二:
      在 library 的 build.gradle 中添加 resourcePrefix , 则所有的资源须以此 prefix 开头,否则报错。注意,图片资源虽然不提示报错误,但是也需要修改名字。
    android {
        ...
        buildTypes {
        ...
        }
        resourcePrefix 'my_prefix_'
    }
    

    2.重复依赖

    将所有的依赖都写在library层(BaseLibrary、Provider)的module,将所有的依赖统一成一个入口给上层的app去引用。

    3.控制台报错解决

    Error:Module 'qsp_release:libLive:unspecified' depends on one or more Android Libraries but is a jar
    

    报这个错误的场景,Moudle A 的build.gradle下

    apply plugin: 'java'
    ...
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile project(':library')
        ...
    }
    

    可以看出 Moudle A 是一个jar形式的依赖模块,而library是apply plugin: 'com.android.library'所以引用报如上错误。

    4. 使用Dagger2时,需要分别在app层级下的build.gradle中添加如下代码

    apply plugin: 'kotlin-kapt'//kapt 插件
    dependencies {
        //Dagger2
        kapt "com.google.dagger:dagger-compiler:$dagger_version"
    }
    

    5. implementation、api与多模块依赖

    自从gradle升级3.+版本后,gradle原来的依赖方法全部都被替换了,之前的compile替换成了implementation和api,新建工程时发现gradle默认使用的也是implementation。
    其中 api 与之前的 compile 功能基本一致,不再赘述;implementation 就比较高级了,其作用就是,使用 implementation 添加的依赖不会再编译期间被其他组件引用到,但在运行期间是完全可见的。这也是一种代码隔离。举个例子:

    组件A依赖lib1,既A implementation lib1
    组件B依赖组件A,既B api A
    

    在 gradle3.0.0 之前,B是完全可以引用到 lib1 里面的类的,但是现在B在编译期间就做不到了,只能在运行期可以。
    在kotlin中,为了避免以后依赖库引用无效的悲剧发生 ,尽量用 implementation ,只有要用到依赖中的依赖,再用api

    6. compileDebugKotlin 解决方案之一(api错用导致的重复引用)

    首先先补下 依赖的知识 ,依赖可以用2种方式implementation和 api,举例场景:
    app 依赖 bModule , bModule依赖cModule

    api:app既可以引用bModule的方法和类又可以引用cModule中代码 
    implementation: app能引用bModule中的方法和类  app不能引用cModule中的方法    这样的好处是提高编译效率
    

    我项目的依赖关系是:

    app api bModule            
    app implementation cModule
    

    导致 重复引用cModule,一直报错compileDebugKotlin
    解决办法就是app implementation bModule
    为了避免以后这种悲剧发生 尽量只用 implementation ,只有要用到依赖中的依赖,再用api

    相关文章

      网友评论

          本文标题:Kotlin之模块化开发

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