Android 架构四:组件化架构实战


Android 架构系列:
Android 架构一:Android 架构浅析
Android 架构二:纵向横向结合构建项目
Android 架构三:组件化思想
Android 架构四:组件化架构实战
Android 架构五:MVVM+Data Binding构建项目
……


一、前言,目前项目架构的困境

承接上一文,我们开始组件化的实战。

由于以前的员工对项目的整体规划不足,采取了单工程的方式搭建架构,随着时间的发展,项目冗余功能和代码增多,让app的运行bug频出以及维护成本和维护难度的增加。

记得在14年的时候做过一次基于Eclipse的项目的结构优化,主要是分为是三个工程:

Library: 基本库工程
后台sdk:服务端SDK工程
主工程: 客户端展示工程

依赖关系图

服务端SDK与客户端分开进程,后台sdk依赖Library,主工程依赖后台SDK,这样一个粗略的组件化调整,使得项目维护与管理成本减低了。

两三年过去了,现在的项目还在用着那套框架,随着APP越来越庞大、开发成员越来越多,并且存在多个业务并发进行的情况,这个结构也越来越不适应了,越来越难维护了,于是下定决心再一次重构,搭建组件化项目框架。

二、解决办法,组件化架构

什么是组件化

组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程,它是一种高效的处理复杂应用系统,更好的明确功能模块作用的方式,分离组件边界和责任,便于独立升级和维护。

大家都知道现在组件化很火,几乎每家公司都在的使用。为什么他们都要抢着去搭建组件化架构,个人感觉主要是因为它有以下优点:

  • 1、提高了代码的复用度、降低耦合度;
  • 2、实现功能的复用;
  • 3、业务隔离,跨团队开发代码控制和版本风险控制的实现
  • 4、模块化对代码的封装性、合理性都有一定的要求,提升开发同学的设计能力。

它有这么多的优点,并且都一一击中了我们之前项目架构的痛点,我们有不用的道理么?

三、如何搭建组件化架构

在我们开始按照组件化的架构去重构我们的项目之前,我们先要了解如何利用IDE去搭建组件化架构。

现在Android官方在强推Android Studio,Eclipse已经被逐步弃用,那么我们也紧跟步伐,基于Android Studio搭建组件化。

这一次的组件化架构是基于之前粗略的组件化方式,作出大量的优化调整,然后利用子工程与壳工程互相配合实现。

实现目标结构如下:

壳工程:单个,项目骨架配置相关
组件工程:多个,多个业务并存且互相独立。
库工程:多个,基础库、工具库、数据库、第三方框架库、网络库……

组件化的结构已经订好,那么我们就可以着手去搭建了。

1、新建项目

如果直接在之前的项目整理,可想而知,那绝对是个灾难,我们的计划是现在新项目上搭建好组件化架构,再把旧项目拆分重整到新项目里来。

打开Android Studio,选择 Start a new Android Studio project

newproject

后面选择一个空白的,然后下一步输入项目名MyModularization及相关信息,点击OK就可以了。
此时,Android Studio已经为我们添加了默认的Module: app,即我们的壳工程,它是一个app的骨架性配置,一些库的初始化之类的,没有什么代码实现。

2、为MyModularization添加组件

壳工程建立完毕,接下来通过添加Module的方式添加库组件和业务组件。

2.1、为MyModularization添加Library

右键点击MyModularization,选择new,再选择module

Jietu20190418-100611.jpg

在新窗口上选择Android Library,next,再输入相关信息即可
AndroidLibrary.jpg

2.2、为MyModularization添加业务组件

跟1.2差不多,只是在新窗口上选择的不是Android Library,而是Phone & Tablet Module,这样,我们为项目添加了两种类型的组件LibraryModule,一般组件的类型就是这两种。

3、两种组件的区别

这两种组件(其实就是eclipse下的子工程)之间有什么区别呢,我们打开各自的build.gradle,看一下第一行
lib_base:

apply plugin: 'com.android.library'

module_login:

apply plugin: 'com.android.application'

这里,我们可以看出,库组件只仅仅是个库,不能单独运行,而业务组件则是application,能单独运行。

项目及组件添加完成后,我们还要做一些配置。

4、库模块的配置

如果,我们在添加的时候,已经选择了Library,IDE基本上已经帮我们配置完成,但是如果需要把一个非Library类型的组件修改为业务组件,则需要如下操作

4.1、配置apply plugin

build.gradle中的apply plugin: ‘com.android.application’修改成apply plugin: ‘com.android.library’

4.2、调整manifest

manifest中的代码作如下修改

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="yb.demo.modularization.lib_base" />

5、业务组件中的配置

因为组件化的其中一个优势,组件需要具备单独开发调试测试,故,作为整体的一部分时,组件是个Library,而开发过程中,组件是个Application,故配置如下:

5.1、添加调试开关

projectbuild.gradle中添加是否Application的开关

ext {
    isModule = true;
}
5.2、配置plugin类型

build.gradle的顶部添加如下代码,读取开关,做相应的配置

if(rootProject.ext.isModule.toBoolean()){
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
5.3、添加applicationId

build.gradle添加applicationId

android {
    …………

    defaultConfig {
        if(rootProject.ext.isModule.toBoolean()){
            applicationId "com.example.login"
        }

    …………
}
5.4、添加两种AndroidManifest.xml

在Module中添加一个/src/main/module的文件夹,并在module创建AndroidManifest.xml*配置文件,用来在模块开发时能够单独打包,配置如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.login">

    <application
        android:name=".debug.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        …………
</manifest>

在module包下添加文件/src/main/java/包下/debug/**MyApplication.java **,做一些调试的开发相关的实现。

而作为 Library 的配置文件 /src/main/AndroidManifest.xml的配置调整如下

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.login">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.login.MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
        </activity>
        …………
</manifest>
5.5、配置选择AndroidManifest

前面一步已经让module拥有了两个配置文件,接下来就是根据前面添加的开关,判断使用哪个配置文件。
还是在build.gradle文件作出以下调整:


android {

        …………

    sourceSets {
        …………

        main {
            if (rootProject.ext.isModule.toBoolean()) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                //集成开发模式下排除debug文件夹中的所有Java文件
                java {
                    exclude 'debug/**'
                }
            }
        }

        
        …………
    }

    …………
}
5.6、依赖库工程

业务组件需要依赖对应的库工程才能,进行相关功能的实现。
在业务的build.gradle文件做如下配置:

dependencies {
    ……

    api project(':lib_base')
}
5.7、让业务工程具备独立调试的能力

在业务的build.gradle文件做如下配置:

dependencies {
    ……

    if(rootProject.ext.isModule.toBoolean()){
        // andorid text
        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'
    }

    api project(':lib_base')
}

6、壳工程依赖库工程及业务工程

appbuild.gradle文件做如下配置:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

//旧版本Android Studio配置关键字
//    compile project(':module_login');
//    compile project(':lib_base');
    api project(':module_login');
    api project(':lib_base');
}

ok,这样我们组件化基本架构就搭建完毕了,在壳工程与业务工程、库工程关联完毕后,就可以作为一个项目来进行开发了。

当然,这里只是举例了lib_basemodule_login的相关配置,其他的子工程也无非这两种类型的其中一种,配置都是一样的,他们统统做相应的配置即可。

四、利用ARouter降耦合

进过上面的配置,我们的组件化基本架构已经完成,这里还有个问题存在,就是每个子模块之间不能直接调用,那么我们怎么进行模块之间的跳转与数据交流呢。
首先想到的解决的办法有两个,隐式跳转和反射,但是,这两种方式一种实现维护麻烦,一种对性能会有影响,都不是理想的解决办法,此时类似ARouter和ActivityRouter等的路由框架就派上用场了。

我们选择的是阿里的ARouter路由框架,,我们先来简单了解一下ARouter的原理

路由框架会扫描所有添加@Route注解的Activity类,然后将Route注解中的path地址和Activity.class文件建立映射关系并且保存到它自己生成的java文件中,以实现跳转。

详解ARouter路由框架原理。(貌似也还没有写,加下文提到的“纵向横向重构项目”,后面会抽时间完成)
接下来,我们就可以把ARouter运用到我们的组件化中了。

1、配置引用ARouter

我们一般把公用第三方框架放在lib_base中,故在lib_basebuild.gradle文件做如下配置:

dependencies {
    ………………

    // ARouter
    // 替换成最新版本, 需要注意的是api
    // 要与compiler匹配使用,均使用最新版可以保证兼容
    api 'com.alibaba:arouter-api:1.4.1'
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'

}

2、子工程引入路由器

在需要对外提供跳转的子工程中添加路由器,在对应的module的build.gradle文件做如下配置:

if(rootProject.ext.isModule.toBoolean()){
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    ………………

    defaultConfig {
        ………………

        // ARouter
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }

    }

    ………………
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')

    if(rootProject.ext.isModule.toBoolean()){
        // andorid text
        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'
    }

    // ARouter
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'

    api project(':lib_base')
}

3、添加注解

在需要夸module跳转的activity中添加注解:

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/com/example/login/MainActivity")
public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {
    ………………
}

4、使用路由器夸module跳转

注解添加后,我们就可以通过path实现夸module跳转了。在需要startActivity的地方做调整:

    //弃用    
    private void showActivity(Class<?> cls){
        Intent show = new Intent(this, cls);
        startActivity(show);
    }

    private void showActivity(String path){
        // 应用内简单的跳转(通过URL跳转在'进阶用法'中),还有带参跳转,以后在单独见ARouter的时候一起讲。
        ARouter.getInstance().build(path).navigation();
    }

5、Kotlin项目中的配置方式

// 可以参考 module-kotlin 模块中的写法
apply plugin: 'kotlin-kapt'

kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

dependencies {
    compile 'com.alibaba:arouter-api:x.x.x'
    kapt 'com.alibaba:arouter-compiler:x.x.x'
    ...
}

OK,这样就可以把ARouter运用到我们的架构中了,为架构降耦作贡献。

五、组件化思维重构项目

以上,我们就搭起了组件化的架构,接下来才是头疼的事情。

重构的关键是要有组件化的思维,我们首先要做的是,理清冗余杂乱重复的功能模块、工具具模块以及纷繁错乱的业务。

由于篇幅的原因,我们这里只讲搭建组件化架构,不讲重构,如想了解我们是怎么重构的,另见Android组件化二:纵向横向重构项目, 貌似还没写!!;

既然不讲过程,那么就直接来结果吧

组件的划分:


组件化结构.jpg

组件化结构:


组件化结构.jpg

然后,既然讨论清楚,也就是设计完毕啦,接下来就可以进入-炼狱了,你懂的。

六、结语

虽然重构的过程是痛苦的,甚至有时候会觉得生不如死,但是,我们一定要坚持下来,因为,坚持到最后,项目组件化起来了,大大降低了项目维护的成本,各种爽各种嗨。
本文详细的介绍了,如何搭建一个简单易懂的组件化架构,如果你们的项目还没有接入组件化架构,不妨参考一下。
能力有限,如有哪里描述不当或者不清楚的地方,望各位大大指出,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容