安卓进阶_安卓中高级工程师(2/15)之组件化开发

牛客高级系列专栏:

安卓

嵌入式

本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人对安卓进阶必备知识点的理解;

网上安卓资料千千万,笔者将继续维护专栏,一杯奶茶价格不止提供答案解析,更有专栏内容免费技术答疑,15大安卓进阶必备知识点包您懂,助您提高安卓进阶技术能力,为您高薪面试保驾护航!

正文开始⬇

企业级App的架构是如何的?组件化开发究竟是什么?单组件能否独立开发调试?多组件之间如何通讯?带着这些问题,我们一起看看组件化开发。

目录

  • 1、组件化开发
    • 1.1 什么是组件化开发
    • 1.2 为什么需要组件化开发
    • 1.3 组件化改造的优势
    • 1.4 组件化开发的思路和框架
  • 2、组件化开发的实现
    • 2.1 每个子模块可以单独编译,也可以作为库被壳工程集成
      • 2.1.1 application和library动态切换
      • 2.1.2 动态改变清单文件资源指向
    • 2.2 全局Context的获取及组件数据初始化
    • 2.3 library依赖问题
    • 2.4 各个模块统一依赖,统一版本号
    • 2.5 ARouter框架实现各个子模块之间可以跳转/通信
      • 2.5.1 ARouter配置
      • 2.5.2 初始化SDK
      • 2.5.3 实现Activity跳转和返回
      • 2.5.4 服务跳转
      • 2.5.5 其他常见API

1、组件化开发

1.1 什么是组件化开发

将一个庞大的App分成多个模块,每个模块都是一个组件(Module),每个组件之间相互独立,并在组件模式中每个组件可以单独进行调试,但是在集成模式中这些组件又能作为一个个子库合并成一个Apk,这就是组件化开发。

如下图,lib_audio和lib_base和lib_common_ui就是3个单独的模块,彼此相互独立,组件模式中可以作为单独的App进行单独调试。在集成模式中这3个模块又作为子库,提供特定的功能供imooc_voice这个主模块使用,最后由主模块集成3个子库,打包为一个完整的APk。

alt

PS:在本文,模块(module)和组件是同个概念。

1.2 为什么需要组件化开发

当App的业务功能越来越多,开发人员也越来越多,那么就会导致以下问题:

  • 编译时间久:代码量级增加,编译时间自然增加,改1行代码就需要重新编译整个App工程;
  • 误引入bug:因为代码耦合性高,做一个新需求时,可能不小心破坏了其他地方的逻辑,引入bug;
  • 代码冲突/篡改:因为代码耦合性高,经常和其他同事修改同样位置的代码,产生代码冲突。或者相互修改了对方的代码;
  • 完整测试:修改一个功能模块,可能影响到整体逻辑,导致需要完整项目的完整测试;
  • 重复造轮:明明其他地方有同样功能的代码,却要复制过来再修改后才能使用;

如果你在日常开发中遇到上述问题,证明你的App工程需要进行组件化改造了。

1.3 组件化改造的优势

上述很多问题产生的根本就在于代码耦合,组件化开发可以有效降低代码之间的耦合性,使整体框架更加明显,甚至可以把某一业务当成单一项目来开发,因此可以灵活地对业务模块进行组装和拆分。并且还能降低团队成员熟悉项目的成本,降低项目的维护难度。

让我们再次看看组件化改造后,上述问题的解决方法:

  • 编译时间久 ==》改造后:可加快编译速度:可以单独编译单一模块,即把单一模块当作一个App来进行调试,编译时间大幅度缩减,提高开发效率,当然总的编译时间是不会减少的;
  • 误引入bug ==》改造后:提高协作效率,控制产品质量:只在新需求对应的模块下进行修改,最小程序地影响其他模块代码,避免误引入bug,也避免误修改到其他同事的代码,提升协作效率,并控制产品质量;
  • 代码冲突/篡改 ==》改造后:责任明确,控制代码权限:各司其职,大家各自修改自己负责的模块代码,减少代码冲突;
  • 完整测试 ==》改造后:降低测试成本:修改单一模块,则只需要把该模块当作一个独立App进行提测即可,开发调试时不需要对整个项目进行编译;
  • 重复造轮 ==》改造后:功能重用:每个模块都能提供独立的能力,其他模块可以直接拿来用,因此造一个轮子,到处都可以用;

1.4 组件化开发的思路和框架

最理想的组件化开发,就是将传统的一个Module大杂烩,拆分成若干个Module,由主App提供统一的入口,每个拆分后的Module通过相关配置,可以独立运行调试,也可以供主App依赖使用。我们以一个音乐播放的App例子,理想的组件化架构如下:

alt

几个名词解释:

  • 集成模式:主要是集成所有的业务组件、提供Application唯一实现、gradle、manifest配置,将所有的组件由“壳工程”进行打包,组成一个完整的APK,
  • 组件模式:每个组件可以独立开发,一个组件就是一个APP,可以独立进行安装调试;
  • 壳工程:也叫“宿主”工程,负责管理各个组件,以及负责打包完整的APK,其内部包含少量的业务功能甚至没有具体业务功能;
  • 业务组件:根据项目需求而独立形成的一个个组件,如一个音乐App,一般都会包含首页、登录、搜索、个人信息页等几个服务。
  • 基础业务组件:为了更好的解耦各个组件,可以把上述不同的业务组件里,重复性高的,可复用的代码再抽离出来,作为基础业务组件。比如将各个业务组件用到的通用的UI控件,BaseActivity,BaseHandler等,抽离为“通用UI”基础业务组件。或者将分享功能的代码抽离为“分享”基础业务组件,这时候首页业务和个人信息业务这两个业务组件,都可能用到分享的功能,就不用重复写代码了;
  • 基础功能组件:提供开发APP的基础功能,如图片加载、视频播放、保活机制、网络能力等这种单一能力;如果是比较优秀的功能组件,通常会作为公司通用的基础能力,修改频率极低,可以多处复用于公司多个项目集成使用;
  • SDK:这里指的是第三方库,如常见的OkHttp、RxJava等;
  • 组件依赖关系是上层依赖下层,修改频率是上层高于下层:
    • 一个业务组件可能依赖多个基础业务组件和多个功能组件;
    • 一个业务组件可能不包括基础业务组件,直接依赖多个功能组件;
    • 一个基础业务组件可能依赖多个功能组件;
    • 业务组件之间不存在依赖关系;
    • 基础业务组件之间不存在依赖关系
  • 存在形式:
    • 壳工程、业务组件、基础业务组件都是以子模块的形式存在;
    • 功能模块可以使用源码直接加载到工程,也可以将源码编译为aar包再放进工程;
    • SDK:也就是第三方库,一般都是使用别人做的架包,这种情况一般都是使用远程依赖即可,也方便后续修改版本。

2、组件化开发的实现

为了实现组件化开发,首先需要根据业务,将各个业务进行解耦,并为每个业务创建一个子模块,最后加上壳工程就是完整的组件化开发架构。具体哪种需求适合封装成子模块,以及要以源码形式还是aar包形式集成到项目里,可以参考1.4小节自行定位该模块在项目所处的位置进行决定。现在讨论组件化开发实现的几个需求点:

  • 每个子模块可以单独编译,也可以作为库被壳工程集成;
  • 全局Context的获取及组件数据初始化;
  • library依赖问题;
  • 各个模块统一依赖,统一版本号;
  • ARouter框架实现各个子模块之间可以跳转/通信;

先附上‘二流小码农’大佬开源的github项目:AndroidAssembly,说明文档:《Android组件化开发,其实就这么简单》,本人学习过程中也从中学到不少。

2.1 每个子模块可以单独编译,也可以作为库被壳工程集成

2.1.1 application和library动态切换

一个模块可以设置为application和library,这是在子模块里的build.gradle去设置的,我们看看这两者的build.gradle有什么区别:

  • 设置为application:
plugins {
    id 'com.android.application'
}

android {
    ...

    defaultConfig {
        applicationId "com.xurui.test"
        ...
    }
}
  • 设置为library:
plugins {
    id 'com.android.library'
}

android {
    ...

    defaultConfig {
        ...
    }
}

如果希望实现两种状态的动态切换,我们很容易想到用if语句做条件判断,因此可以将上述代码合二为一,gradle使用groovy语言编程,还不了解的同学可见我另外的Gradle专栏:

plugins {
    if(is_Module.toBoolean()){
        apply plugin: 'com.android.application'
    }else{
        apply plugin: 'com.android.library'
    }
}

android {
    ...

    defaultConfig {
        if(is_Module.toBoolean()){
            applicationId "com.xurui.test"
        }
    }
}

我们知道根目录的gradle.properties文件是Gradle构建工具的属性文件(对APP工程目录不了解的同学可见另一个APP开发全流程解析的专栏,将详细介绍每个文件的作用)。所以可以在此设置一个名为is_Module的变量,因为gradle.properties 中的数据类型都是String类型,使用其他数据类型需要自行转换,此时需要通过toBoolean()转为Boolen类型。通过is_Module变量就可以根据配置动态切换每个模块了。比如:

  • is_Module变量为false,那么工程处于集成模式,只能编译壳工程,其他子模块作为库包含到壳工程里。
  • is_Module变量为true,我们在再定义一个变量,表示当前可以编译为应用程序的子模块的名字,比如工程有A,B,C三个子模块,定义
moduleName=A

此时A模块也可以编译为APP形式运行到手机上。

2.1.2 动态改变清单文件资源指向

除了build.gradle文件外,application和library模式还有另一个区别文件,就是AndroidManifestxml文件,即清单文件;

  • application形式:在 Android Studio 中,每个组件都有其对应的 AndroidManifest.xml 文件,用于声明需要的权限、应用程序、Activity、Service、Broadcast 等。当子模块处于application模式时,业务组件的 AndroidManifest.xml 文件应当包含所有 Android 应用程序所具有的属性,特别是声明 Application 和要启动的 Activity,比如:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xurui.code">

     <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:supportsRtl="true"
        android:theme="@style/appTheme"
        android:usesCleartextTraffic="true">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>
  • library形式:当模块处于library形式,代表会被壳工程所集成,此时该子模块就不能有自己的 Application 和 主Activity,不然编译出来的APK就不知道主Activity是哪个了,此时的清单文件可能如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.abner.code">

    <application>
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait" />
    </application>

</manifest>

因为每个子模块的功能是不一样的,所以清单文件内容肯定是不一样的。那么,没有共性,就无法上build.gradle文件一样提取公共代码,通过if语句做判断了。因此,可以一个子模块都保存上述两个清单文件,在build.gradle去判断当前使用哪个清单文件,如在build.gradle这么写:

sourceSets {
        main {
            if (is_Module.toBoolean()) {
                manifest.srcFile 'src/main/mainfest/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }

其中,main/AndroidManifest.xml是原有的AndroidManifest.xml,我们可以在main目录下创建一个新的名为mainfest的文件夹,将原来的AndroidManifest.xml拷贝一份过去,修改为library形式时的AndroidManifest.xml即可。

2.2 全局Context的获取及组件数据初始化

无论当前子模块是处于application还是library模式,都难免在开发的时候需要获取全局的context,即 ApplicationContext。此时,不可能每个子模块都各定义一个Application类,否则打包起来就会出现问题,毕竟一个APP只能有一个Application。对于这种情况,就可以创建一个BaseApplication类,然后每个子模块都来集成BaseApplication类。比如可以创建一个基础业务组件-通用UI组件,暂时命名为lib_common。该组件可以被其他的子模块所引用。在lib_common创建一个BaseApplication类:

public class BaseApplication extends Application {

      private static Application sContext;

      @Override
      public void onCreate() {
            super.onCreate();
            sContext = this;
      }

      public static Context getAppContext(){
            return sContext;
      }
}

此时就可以在其他子模块,比如app子模块引用lib_common,进一步在MApp文件继承BaseApplication类:

class MApp : BaseApplication() {

    override fun onCreate() {
        super.onCreate()
        ...
    }
}

2.3 library依赖问题

举个场景,有如下4个模块

  • ‘业务模块-用户个人信息模块’,子模块存在;
  • ‘基础业务模块-分享’,子模块存在;
  • ‘基础业务模块-推送’,子模块存在;
  • ‘功能组件-日志组件’,aar架包存在;

其中‘用户个人信息‘模块依赖‘分享’和‘推送’模块,然后‘分享’和‘基推送’都分别依赖了‘日志组件’。那么

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Android进阶知识体系解析 文章被收录于专栏

#提供免费售后答疑!!花一杯奶茶的钱获得安卓知识体系答疑服务,稳赚不赔# 当你已经掌握了Android基础知识,你可能会想要进一步深入学习安卓进阶知识。在这个专栏中,我们将探讨一些高级安卓开发技术,无论你是初学者还是有经验的开发者,这个专栏都将为你提供有价值的知识和经验。让我们一起开始探索安卓进阶知识的奇妙世界吧!

全部评论

相关推荐

评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务