基础知识Android开发Android开发经验谈

“终于懂了” 系列:Android组件化,全面掌握! (上)双节

2020-10-11  本文已影响0人  jett老师

作者:胡飞洋
链接:https://juejin.im/post/6881116198889586701

导语

这篇文章最近很火,我也有一些自己的看法:现在去很多公司面试,除了你具备基本的能够写一个高性能app的能力后,一般都会在自己的app里面加一些现有的相对较666的技术,这些技术我们称之为开源框架。
比如,我们以前木匠做一个桌子,我们需要把木头削成木板,然后把木板拼成桌面,然后再去用同样的方式做桌子的腿;现在,我们只需要买做好的桌子板和做好的桌子腿,然后我们就可以用他们拼接就可以自己做一个桌子了。
所以,现在开放代码已经不再需要你去锯木头了,你只需要拿着一个SDK就可以实现一个功能了,那么这个就是我们的开源技术。

那么,这些技术一般包含热升级,热修复,AppInstant,强制更新,组件化路由架构Arouter,RxJava,IOC架构方法,Hook技术,图片加载(Glide),网络访问(Retrofit,okHttp)等等,当然,这些技术你不能只会用,你需要知道他的原理,有时候,你还需要知道如何对这些架构进行改进。

另外,非常重要的一点,Android开发者不愿意接受新知识,所以对于现在Google推出的新的技术方案毫无感觉,这些都是被controlC和controlV所毒害,开源让开发者失去了自我,失去了基本的编码能力,失去了深入学习的信心和毅力,所以对于很多google推出的新技术好组件(GoogleI/O大会新技术),好框架已经很多人不愿意去学习了这是不应该的,我们需要好好学习啊。

注意

为此我把网络上一些学习架构比较好的资源进行了一个整理, 需要的可以查看我的【GitHub】需要直接下载的可以点击这里【设计思想解读开源框架系统学习

PS:(文章结尾会介绍一下相关学习资源,视频+文档)

一、背景

随着项目逐渐扩展,业务功能越来越多,代码量越来越多,开发人员数量也越来越多。此过程中,你是否有过以下烦恼?

  1. 项目模块多且复杂,编译一次要5分钟甚至10分钟?太慢不能忍?

  2. 改了一行代码 或只调了一点UI,就要run整个项目,再忍受一次10分钟?

  3. 合代码经常发生冲突?很烦?

  4. 被人偷偷改了自己模块的代码?很不爽?

  5. 做一个需求,发现还要去改动很多别人模块的代码?

  6. 别的模块已实现的类似功能,自己要用只能去复制一份代码再改改?

  7. “这个不是我负责的,我不管”,代码责任范围不明确?

  8. 只做了一个模块的功能,但改动点很多,所以要完整回归测试?

  9. 做了个需求,但不知不觉导致其他模块出现bug?

    如果有这些烦恼,说明你的项目需要进行 组件化 了。

上半年,我所在项目进行了大重构,也完成了组件化改造。所以终于学习实践了这样一个“高端知识”,也看了一些文章,于是就有了这篇文章来作为总结和分享~

二、组件化的理解

2.1 模块化

在介绍组件化之前,先说说模块化。我们知道在Android Studio中,新建工程默认有一个App module,然后还可以通过File->New->New Module 新建module。那么这里的“module” 实际和我们说的“模块”基本是一个概念了。也就是说,原本一个 App模块 承载了所有的功能,而模块化就是拆分成多个模块放在不同的Module里面,每个功能的代码都在自己所属的 module 中添加。

已京东为例,大致可以分为 “首页”、“分类”、“发现”、“购物车”、“我的”、“商品详情” 六个模块。

这是一般项目都会采用的结构。另外通常还会有一个通用基础模块module_common,提供BaseActivity/BaseFragment、图片加载、网络请求等基础能力,然后每个业务模块都会依赖这个基础模块。 那么业务模块之间有没有依赖呢?很显然是有的。例如 “首页”、“分类”、“发现”、“购物车”、“我的”,都是需要跳转到“商品详情” 的,必然是依赖“商品详情” ;而“商品详情”是需要能添加到“购物车”能力的;而“首页”点击搜索显然是“分类”中的搜索功能。所以这些模块之间存在复杂的依赖关系

模块化 在各个业务功能比较独立的情况下是比较合理的,但多个模块中肯定会有页面跳转数据传递方法调用 等情况,所以必然存在以上这种依赖关系,即模块间有着高耦合度。 高耦合度 加上 代码量大,就极易出现上面提到的那些问题了,严重影响了团队的开发效率及质量。

为了 解决模块间的高耦合度问题,就要进行组件化了。

2.2 组件化介绍 — 优势及架构

组件化去除模块间的耦合,使得每个业务模块可以独立当做App存在,对于其他模块没有直接的依赖关系。 此时业务模块就成为了业务组件

而除了业务组件,还有抽离出来的业务基础组件,是提供给业务组件使用,但不是独立的业务,例如分享组件、广告组件;还有基础组件,即单独的基础功能,与业务无关,例如 图片加载、网络请求等。这些后面会详细说明。

组件化带来的好处 就显而易见了:

  1. 加快编译速度:每个业务功能都是一个单独的工程,可独立编译运行,拆分后代码量较少,编译自然变快。
  2. 提高协作效率:解耦 使得组件之间 彼此互不打扰,组件内部代码相关性极高。 团队中每个人有自己的责任组件,不会影响其他组件;降低团队成员熟悉项目的成本,只需熟悉责任组件即可;对测试来说,只需重点测试改动的组件,而不是全盘回归测试。
  3. 功能重用:组件 类似我们引用的第三方库,只需维护好每个组件,一建引用集成即可。业务组件可上可下,灵活多变;而基础组件,为新业务随时集成提供了基础,减少重复开发和维护工作量。

下图是我们期望的组件化架构

  1. 组件依赖关系是上层依赖下层,修改频率是上层高于下层。
  2. 基础组件是通用基础能力,修改频率极低,作为SDK可共公司所有项目集成使用。
  3. common组件,作为支撑业务组件、业务基础组件的基础(BaseActivity/BaseFragment等基础能力),同时依赖所有的基础组件,提供多数业务组件需要的基本功能,并且统一了基础组件的版本号。所以 业务组件、业务基础组件 所需的基础能力只需要依赖common组件即可获得。
  4. 业务组件业务基础组件,都依赖common组件。但业务组件之间不存在依赖关系,业务基础组件之间不存在依赖关系。而 业务组件 是依赖所需的业务基础组件的,例如几乎所有业务组件都会依赖广告组件 来展示Banner广告、弹窗广告等。
  5. 最上层则是主工程,即所谓的“壳工程”,主要是集成所有的业务组件、提供Application唯一实现、gradle、manifest配置,整合成完备的App。

2.3 组件化开发的问题点

我们了解了组件化的概念、优点及架构特点,那么要想实施组件化,首先要搞清楚 要解决问题点有哪些?

核心问题是 业务组件去耦合。那么存在哪些耦合的情况呢?前面有提到过,页面跳转、方法调用、事件通知。 而基础组件、业务基础组件,不存在耦合的问题,所以只需要抽离封装成库即可。 所以针对业务组件有以下问题:

  1. 业务组件,如何实现单独运行调试?
  2. 业务组件间 没有依赖,如何实现页面的跳转?
  3. 业务组件间 没有依赖,如何实现组件间通信/方法调用?
  4. 业务组件间 没有依赖,如何获取fragment实例?
  5. 业务组件不能反向依赖壳工程,如何获取Application实例、如何获取Application onCreate()回调(用于任务初始化)?

下面就来看看如何解决这些问题。

三、组件独立调试

每个 业务组件 都是一个完整的整体,可以当做独立的App,需要满足单独运行及调试的要求,这样可以提升编译速度提高效率。

如何做到组件独立调试呢?有两种方案:

  1. 单工程方案,组件以module形式存在,动态配置组件的工程类型;
  2. 多工程方案,业务组件以library module形式存在于独立的工程,且只有这一个library module。

3.1 单工程方案

3.1.1 动态配置组件工程类型

单工程模式,整个项目只有一个工程,它包含:App module 加上各个业务组件module,就是所有的代码,这就是单工程模式。 如何做到组件单独调试呢?

我们知道,在 AndroidStudio 开发 Android 项目时,使用的是 Gradle 来构建,Android Gradle 中提供了三种插件,在开发中可以通过配置不同的插件来配置不同的module类型。

区别比较简单, App 插件来配置一个 Android App 工程,项目构建后输出一个 APK 安装包,Library 插件来配置一个 Android Library 工程,构建后输出 ARR 包。

显然我们的 App module配置的就是Application 插件,业务组件module 配置的是 Library 插件。想要实现 业务组件的独立调试,这就需要把配置改为 Application 插件;而独立开发调试完成后,又需要变回Library 插件进行集成调试

如何让组件在这两种调试模式之间自动转换呢? 手动修改组件的 gralde 文件,切换 Application 和 library ?如果项目只有两三个组件那么是可行的,但在大型项目中可能会有十几个业务组件,一个个手动修改显得费力笨拙。

我们知道用AndroidStudio创建一个Android项目后,会在根目录中生成一个gradle.properties文件。在这个文件定义的常量,可以被任何一个build.gradle读取。 所以我们可以在gradle.properties中定义一个常量值 isModule,true为即独立调试;false为集成调试。然后在业务组件的build.gradle中读取 isModule,设置成对应的插件即可。代码如下:

//gradle.properties
#组件独立调试开关, 每次更改值后要同步工程
isModule = false

//build.gradle
//注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换
if (isModule.toBoolean()){
    apply plugin: 'com.android.application'
}else {
    apply plugin: 'com.android.library'
}

3.1.2 动态配置ApplicationId 和 AndroidManifest

我们知道一个 App 是需要一个 ApplicationId的 ,而组件在独立调试时也是一个App,所以也需要一个 ApplicationId,集成调试时组件是不需要ApplicationId的;另外一个 APP 也只有一个启动页, 而组件在独立调试时也需要一个启动页,在集成调试时就不需要了。所以ApplicationId、AndroidManifest也是需要 isModule 来进行配置的。

//build.gradle (module_cart)
android {
...
    defaultConfig {
...
        if (isModule.toBoolean()) {
            // 独立调试时添加 applicationId ,集成调试时移除
            applicationId "com.hfy.componentlearning.cart"
        }
...
    }

    sourceSets {
        main {
            // 独立调试与集成调试时使用不同的 AndroidManifest.xml 文件
            if (isModule.toBoolean()) {
                manifest.srcFile 'src/main/moduleManifest/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }
...
}

可见也是使用isModule分别设置applicationId、AndroidManifest。其中独立调试的AndroidManifest是新建于目录moduleManifest,使用 manifest.srcFile 即可指定两种调试模式的AndroidManifest文件路径。

moduleManifest中新建的manifest文件 指定了Application、启动activity:

//moduleManifest/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hfy.module_cart" >
    <application android:name=".CartApplication"
        android:allowBackup="true"
        android:label="Cart"
        android:theme="@style/Theme.AppCompat">
        <activity android:name=".CartActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

原本自动生成的manifest,未指定Application、启动activity:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hfy.module_cart">
    <application>
        <activity android:name=".CartActivity"></activity>
    </application>
</manifest>

独立调试、集成调试 ,分别使用“assembleDebug”构建结果如下:

3.2 多工程方案

3.2.1 方案概述

多工程方案,业务组件以library module形式存在于独立的工程。独立工程 自然就可以独立调试了,不再需要进行上面那些配置了。

例如,购物车组件 就是 新建的工程Cart 的 module_cart模块,业务代码就写在module_cart中即可。app模块是依赖module_cart。app模块只是一个组件的入口,或者是一些demo测试代码。

那么当所有业务组件都拆分成独立组件时,原本的工程就变成一个只有app模块的壳工程了,壳工程就是用来集成所有业务组件的。

3.2.1 maven引用组件

那么如何进行集成调试呢?使用maven引用组件:1、发布组件的arr包 到公司的maven仓库,2、然后在壳工程中就使用implemention依赖就可以了,和使用第三方库一毛一样。另外arr包 分为 快照版本(SNAPSHOT) 和 正(Realease)式版本,快照版本是开发阶段调试使用,正式版本是正式发版使用。具体如下:

首先,在module_cart模块中新建maven_push.gradle文件,和build.gradle同级目录

apply plugin: 'maven'

configurations {
    deployerJars
}

repositories {
    mavenCentral()
}

//上传到Maven仓库的task
uploadArchives {
    repositories {
        mavenDeployer {
            pom.version = '1.0.0' // 版本号
            pom.artifactId = 'cart' // 项目名称(通常为类库模块名称,也可以任意)
            pom.groupId = 'com.hfy.cart' // 唯一标识(通常为模块包名,也可以任意)

            //指定快照版本 maven仓库url, todo 请改为自己的maven服务器地址、账号密码
            snapshotRepository(url: 'http://xxx/maven-snapshots/') {
                authentication(userName: '***', password: '***')
            }
            //指定正式版本 maven仓库url, todo 请改为自己的maven服务器地址、账号密码
            repository(url: 'http://xxx/maven-releases/') {
                authentication(userName: '***', password: '***')
            }
        }
    }
}

// type显示指定任务类型或任务, 这里指定要执行Javadoc这个task,这个task在gradle中已经定义
task androidJavadocs(type: Javadoc) {
    // 设置源码所在的位置
    source = android.sourceSets.main.java.sourceFiles
}

// 生成javadoc.jar
task androidJavadocsJar(type: Jar) {
    // 指定文档名称
    classifier = 'javadoc'
    from androidJavadocs.destinationDir
}

// 打包main目录下代码和资源的task,生成sources.jar
task androidSourcesJar(type: Jar) {
    classifier = 'sources'
    from android.sourceSets.main.java.sourceFiles
}

//配置需要上传到maven仓库的文件
artifacts {
    archives androidSourcesJar
    archives androidJavadocsJar
}

maven_push.gradle主要就是发布组件ARR的配置:ARR的版本号、名称、maven仓地址账号等。

然后,再build.gradle中引用:

//build.gradle
apply from: 'maven_push.gradle'

接着,点击Sync后,点击Gradle任务uploadArchives,即可打包并发布arr到maven仓。

最后,壳工程要引用组件ARR,需要先在壳工程的根目录下build.gradle中添加maven仓库地址:

allprojects {
    repositories {
        google()
        jcenter()
        //私有服务器仓库地址
        maven {
            url 'http://xxx'
        }
    }
}
复制代码

接着在app的build.gradle中添加依赖即可:

dependencies {
    ...
    implementation 'com.hfy.cart:cart:1.0.0'
    //以及其他业务组件
}

可见,多工程方案 和我们平时使用第三方库是一样的,只是我们把组件ARR发布到公司的私有maven仓而已。

实际上,我个人比较建议 使用多工程方案的。

注意,我在Demo里 使用的是多工程方案,并且是 把ARR发到JitPack仓,这样是为了演示方便,和发到公司私有maven仓是一个意思。 1、需要根目录下build.gradle中添加JitPack仓地址:maven { url 'jitpack.io' } ; 2、JitPack是自定义的Maven仓库,不过它的流程极度简化,只需要输入Github项目地址就可发布项目。

设计思想解读开源框架

第一章、 热修复设计


第二章、 插件化框架设计


第三章、 组件化框架设计

需要这份1042页PDF的Android框架学习笔记的可以查看我的【GitHub 】觉得还不错的,记得点个 star!

附:2246页学习笔记

上一篇 下一篇

猜你喜欢

热点阅读