阅读 172

组件化/模块化的快速入门及使用之:Android中通过对gradle的管理实现组件化;并配合ARouter,随意跳转切换

前言:这篇模块化与组件化的文章,用2篇文章介绍。可能有些人觉得网上已经有了文章,为什么还要写。第一:为了记录自己的正常也算当做笔记。第二:网上固然有好文,但最近看了一篇居然有150多赞,但是介绍的迷迷糊糊,很多知识点略过。本文重点是让你快速入门,理解以及使用。

本次模块化/组件化讲解总共分2篇(必须先了解ARouter,或第三方路由框架):
1、阿里路由框架ARouter的基本使用
2、Android中通过对gradle的管理实现组件化;并配合ARouter,随意跳转切换

其实在没了解过模块化/组件化之前,我觉得非常高端,甚至不敢触碰。可能是因为其他人的博客很高端。其实接触后发现,其实就是通过gradle的管理实现的。高端的只不过是各模块module之间的通信,可是ARouter已经帮我们解决了所有事情。跟着我一步一步。一起实现,末尾加上本文demo。

在本文文章开始前,先讲个小知识点
A module 引入了B module,B module引入了C module,如果使用的是implementation方式,那么C对于A来说是不可见的;而使用api方式C是可以被A访问的。同理,把C换成开源库、so文件、aar文件、jar包文件结论也适用。 如果是jar包的,父类要引入子类的话必须要加上路劲如: dirs 'libs', '../moduleB/libs' 。父类引入moduleB中的libs

1、本文demo和最终实现的效果

这里也和大部分网上一样,以微信为例。把微信分为4个模块module:home、chat、search、mine。

  • 对于主程序app来说,这个4个module是其library。运行app后生成我们的主app;

通过对gradle配置开光的更改,我们可以单独把home模块,chat模块,search模块,mine模块,自身作为app运行。这样模块化的好处是,解耦。把各模块给开发人员开发,互不影响,且代码管理也不会起冲突等等。

最终效果是这样,这里我只写了home模块,chat模块。其实其他2个是一样的。(并且这是一个项目,通过gradle生成的)

这些app的生成,只是改变gradle的配置开光即可生成。

完整app home_module单独运行app chat_module单独运行app
从最外层看

2、新建一个项目,用config.gradle统一管理版本号,以免版本冲突

新建项目,我这里的项目是CatModuleStu,在项目build.gradle的目录下,new一个config.gradle。不会的直接复制项目build.gradle,改下名字即可。里面的内容呢,根据我们的app里build.gradle进行更改,app里的build.gradle如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.lihang.catmodulestu"
        minSdkVersion 22
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
复制代码


意思就是把这些具体的引用用常量表示,每个module都使用这些常量,所以我们的confing.gradle如下:

ext {
    //这里是配置开关。是否需要单独运行。注意,这里只能打开一个。因为一次只能运行一个。。
    //true 表示需要单独运行。false表示不需要单独运行。
    isNeedHomeModule = false
    isNeedChatModule = false
    isNeedFindModule = false
    isNeedMineModule = false

    android = [
            compileSdkVersion: 28,
            buildToolsVersion: "28.0.0",
            minSdkVersion    : 22,
            targetSdkVersion : 28,
            versionCode      : 1,
            versionName      : "1.0.0",
            applicationId    : "com.lihang.catmodulestu",
            applicationHomeId: "com.lihang.homemodule",
            applicationChatId: "com.lihang.chatmodule"
    ]

    //这个对依赖库版本version的管理,就更加细致化了
    version = [
            androidSupportSdkVersion: "28.0.0"
    ]

    //系统依赖
    dependencies = [
            "support:appcompat-v7": "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
            "test:runner"         : 'com.android.support.test:runner:1.0.2',
            "test.espresso"       : 'com.android.support.test.espresso:espresso-core:3.0.2',
            "junit"               : 'junit:junit:4.12'
    ]

    //第三方库(请原谅我的英语)
    //这样依赖库看起来比较清晰(dependencies : 代表系统依赖库;thridencies代表第三依赖库)
    thridencies = [
            "butterknife"         : 'com.jakewharton:butterknife:8.8.1',
            "butterknife-compiler": 'com.jakewharton:butterknife-compiler:8.8.1',
            "arouter-compiler"    : 'com.alibaba:arouter-compiler:1.1.4',
            "arouter"             : 'com.alibaba:arouter-api:1.3.1',
    ]
}
复制代码


这些写好之后,在我们的项目build.gradle最顶部,引用一下我们的config.gradle: apply from: "config.gradle"


做完这些后,在来看我们的app的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion rootProject.ext.android["compileSdkVersion"]
    defaultConfig {
        applicationId rootProject.ext.android["applicationId"]
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
        versionCode rootProject.ext.android["versionCode"]
        versionName rootProject.ext.android["versionName"]
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation rootProject.ext.dependencies["support:appcompat-v7"]
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["test:runner"]
    androidTestImplementation rootProject.ext.dependencies["test.espresso"]
}
复制代码

3、 新建baseModule。

新建baseModule,把常用的网络请求,图片加载,意思就是其他module共用的东西放进去.包括共用的资源文件,BaseActivity,BaseFragment和application也放在这,上篇说的ARouter的使用,初始化也放在这。这个baseModule是完全作为library的。被其他module和app引用。但是这里有2个坑
1、引入ARouter的时候,在baseModule的build.gradle里dependencies标签下加上:

api rootProject.ext.thridencies["arouter"]
annotationProcessor rootProject.ext.thridencies["arouter-compiler"]
复制代码

在android的defaultConfig标签加上

javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
复制代码

其他module引用baseModule的时候,同样也要在defaultConfig标签下加上javaCompileOptions。同时还要引入如下依赖,对!你没有看错。如果不加,那么将不成功。:

annotationProcessor rootProject.ext.thridencies["arouter-compiler"]
复制代码

2、这里用butterknife。比如baseModule引用了butterknife后,其他module引用baseModule的时候会出现个bug、错误提示:元素必须为常量。然后butterknife官方提供了一个处理方案,用R2。但是在切换module作为app运行的时候,R2会报错,意思作为app的时候R2要改成R。假如你module里都用了R2,这个时候要全部改成R。所以不建议baseModule引用butterknife。如有好的解决方法,望告知!!


4、新建homeModule

首先看homeModule的build.gradle:

//这里就用到了config的配置isNeedHomeModule
//开头设置,如果打开开光,当成项目运行,否则当成library引用
if (isNeedHomeModule.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    compileSdkVersion rootProject.ext.android["compileSdkVersion"]

    defaultConfig {
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
        if (isNeedHomeModule.toBoolean()) {
            //同时在conifg.gradle配置上homeModule的包名。
            //当作为application运行的时候,给他配置上独立的包名
            applicationId rootProject.ext.android["applicationHomeId"]
        }
        versionCode rootProject.ext.android["versionCode"]
        versionName rootProject.ext.android["versionName"]
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        //ARouter的使用记得要加哦
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            if (isNeedHomeModule.toBoolean()) {
                //这里目前的做法是2套AndroidManifest,作为app运行的时候要指定启动页
                manifest.srcFile 'src/main/buildApp/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/buildModule/AndroidManifest.xml'
            }
        }
    }

}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation rootProject.ext.dependencies["support:appcompat-v7"]
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["test:runner"]
    androidTestImplementation rootProject.ext.dependencies["test.espresso"]
    implementation project(':baseModule')
    annotationProcessor rootProject.ext.thridencies["arouter-compiler"]
}
复制代码

从上代码中我们可以看到有3点注意的地方:

  • 1、通过isNeedHomeModule判断是作为app运行还是library运行
if (isNeedHomeModule.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
复制代码

  • 2、通过isNeedHomeModule判断,如果作为app运行,给其配置appId
    if (isNeedHomeModule.toBoolean()) {
            applicationId rootProject.ext.android["applicationHomeId"]
        }
复制代码

  • 3、通过isNeedHomeModule判断,配置2个AndroidManifest,一个有启动页的,一个是没有启动页的。
if (isNeedHomeModule.toBoolean()) {
                //这里目前的做法是2套AndroidManifest,作为app运行的时候要指定启动页
                manifest.srcFile 'src/main/buildApp/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/buildModule/AndroidManifest.xml'
            }
复制代码

这里2个AndroidManifest就不贴了,可以通过我的demo自己去看。其实就是指定一个启动页Activity。我这里为了demo的清晰,就新建了个SelectHomeActivity,然后就是homeModule模块,用到的所有Activity配置什么的都放在这里。如果是作为library,也是把所有的配置放在这。被主app引用的时候,他会自动来这里找的,不用我们担心。

做完这些,当homeModule作为app运行的时候,我们主app当然也要判断,就引用不了homeModule了,build.gradle如下:

dependencies {
   ... //省略部分代码,便于理解
    if (!isNeedHomeModule.toBoolean()) {
        implementation project(':homeModule')
    }
}
复制代码

做完这些,就完成了。其他module也是和这个同样的配置。通过更改config.gradle的配置isNeedHomeModule,就可以模块单独之间运行了。

5、主app里使用各module类时。

当成library引用后。比如我这个demo是点击下方按钮进行切换。因为我们使用了ARouter,就可以这样(当然这里引用,你也可以直接用类名):

//这样就生成了一个HomeFragment的实例
HomeFragemnt fragment_one = (HomeFragemnt) ARouter.getInstance().build(Constance.FRAGMENT_HOME_PATH).withString("wo", "1").navigation();
复制代码

结束语:至此,关于android中的模块化和组件化,就到这里

这里我说个我自己的理解:
模块化:就像这样,把我们的“微信”,分为4个模块。我理解为模块化。

组件化:就像这里的baseModule,或者你封装的dialog,popwindow。再比如,你用的网上第三方的一些效果。我理解为模块化。和模块化一样,都是为了解耦。

你的点赞是我最大的动力,github地址