从入门到不可自拔的Android 组件化

前言

组件化技术,在 Android 开发中有着举足轻重的作用。

随着时间推移,软件项目很多都会变得越来越庞杂。此时,采用组件化技术,对项目进行改造,是一种较优的方案。

本文对组件化技术进行了分析和总结,并搭建了一个基础的组件化项目Demo,源码在 Github 上:点这里点这里

谈谈模块化

要聊组件化,惯例是要谈谈模块化的,毕竟它与组件化确实有一些相同点,在组件化的项目中它也会与组件化发生关联。

什么是模块化

模块化开发,是每个开发者都熟悉的。

即将常用的UI、网络请求、数据库操作、第三方库的使用等公共部分抽离封装成基础模块,或者将大的业务上拆分为多个小的业务模块,这些业务模块又依赖于公共基础模块的开发方式。

更宏观上,又会将这些不同的模块组合为一个整体,打包成一个完成的项目。

模块化的好处

模块化有哪些好处呢?

复用

首先,基础模块,可为业务模块所复用;

其次,子业务模块,可为父业务模块,甚至不同的项目所复用。

解耦

降低模块间的耦合,避免出现一处代码修改,牵一发而动全身的尴尬局面。

协同开发

项目越来越大,团队人数越来越多,模块化开发可在尽量解耦的情况下,使不同的开发人员专注于自己负责的业务,同步开发,显著提供开发效率。

模块化的弊端

那,模块化开发有没有什么弊端呢?

有。

任凭模块化做得多么好,还是跳不出是组合在单一项目下的。随着项目的发展与迭代,模块化开发渐渐显现了以下的问题:

项目代码量越来越大

每次的编译速度越来越慢,哪怕几行代码的修改,都需要花费好几分钟的时间,等着编译器编译运行结束后,才能查看代码的执行结果,这极大的降低了开发效率;

业务模块越来越多

不可避免地产生越来越多且复杂的耦合,哪怕一次小的功能更新,也需要对修改代码耦合的模块进行充分测试;

团队人数越来越多

这就要求开发人员了解与之业务相关的每一个业务模块,防止出现某位开发人员修改代码导致其他模块出现 bug 的情况,这个要求对于开发人员显然是不友好的;

那怎样解决模块化开发的这些弊端呢?

当然是组件化喽!

聊聊组件化

组件化可以说是 Android 中级开发工程师必备技能了,能有效解决许多单一项目下开发中出现的问题。

并且我要强调的是,组件化真的不难,还没搞过的小伙伴不要怂。

什么是组件化

组件,顾名思义,“组装的零件”,术语上叫做软件单元,可用于组装在应用程序中。

所以,组件化,要更关注可复用性、更注重关注点分离、功能单一、高内聚、粒度更小、是业务上能划分的最小单元,毕竟是“组装的零件”嘛!

从这个角度上看,组件化的粒度,似乎要比模块化的粒度更小。

不过,我个人认为,要把组件化拆分到如此小的粒度,不可能,也没有必要。在组件化项目的实际开发中,组件化的粒度,是要比模块化的粒度更大的。

组件化的好处

首先要说的是,上述模块化的好处,组件化都有,不再赘述;上述模块化的弊端,组件化都给解决了,具体如下:

  1. 组件,既可以作为 library,又可以单独作为 application,便于单独编译单独测试,大大的提高了编译和开发效率;

  2. (业务)组件,可有自己独立的版本,业务线互不干扰,可单独编译、测试、打包、部署;

  3. 各业务线共有的公共模块可开发为组件,作为依赖库供各业务线调用,减少重复代码编写,减少冗余,便于维护;

  4. 通过 gradle 配置文件,可对第三方库进行统一管理,避免版本冲突,减少冗余;

  5. 通过 gradle 配置文件,可实现 application 与 library 灵活组合与拆分,可以更快速的响应需求方对功能模块的选择。

组件化实践

首先要说明的是,下述是一个简单的不能再简单的组件化案例,只求帮助大家搭建起组件化的架构,功能上极其简约。

九层之台,起于累土。我们这就开始搭组件化的架构吧!

组件化架构

先上一张组件化项目整体架构图

其中的“业务组件”,既可以作为 application 单独打包为 apk,又可以作为 library 灵活组合为综合一些的应用程序。

大多数开发者做组件化时面对的业务需求,都是上面这种情况。

我司的需求略有不同,不是将子业务组件组合为整体应用程序,而是反其道而行之,需要将已上线项目拆分给不同的业务公司使用,在不同业务系统中,项目的逻辑和代码会有区别,且版本不统一。

基于此,我搭建项目架构如下图所示,其中“m_moudle_main”是公司主要的、且逻辑和代码相同的业务组件,“b_moudle_north”和“b_moudle_south”是拆分出来的业务组件,管理各自私有的逻辑和代码,且版本有差别。

从Android工程看,结构如下图所示:

注:取moudle名,手动加上“b_” “m_” “x_”这样的前缀,只是为了便于分辨组件层次。

统一配置文件

在项目根目录下,自建 config.gradle 文件,对项目进行全局统一配置,并对版本和依赖进行统一管理,源码如下:

/**
 * 全局统一配置
 */
ext {
    /**
     * module开关统一声明在此处
     * true:module作为application,可单独打包为apk
     * false:module作为library,可作为宿主application的组件
     */
    isNorthModule = false
    isSouthModule = false

    /**
     * 版本统一管理
     */
    versions = [
            applicationId           : "com.niujiaojian.amd",//应用ID
            versionCode             : 100,//版本号
            versionName             : "1.0.0",//版本名称

            compileSdkVersion       : 28,minSdkVersion           : 21,targetSdkVersion        : 28,androidSupportSdkVersion: "28.0.0",constraintlayoutVersion : "1.1.3",runnerVersion           : "1.1.0-alpha4",espressoVersion         : "3.1.0-alpha4",junitVersion            : "4.12",annotationsVersion      : "28.0.0",appcompatVersion        : "1.0.0-beta01",designVersion           : "1.0.0-beta01",multidexVersion         : "1.0.2",butterknifeVersion      : "10.1.0",arouterApiVersion       : "1.4.1",arouterCompilerVersion  : "1.2.2",arouterAnnotationVersion: "1.0.4"
    ]

    dependencies = [
            "appcompat"           : "androidx.appcompat:appcompat:${versions["appcompatVersion"]}","constraintlayout"    : "androidx.constraintlayout:constraintlayout:${versions["constraintlayoutVersion"]}","runner"              : "androidx.test:runner:${versions["runnerVersion"]}","espresso_core"       : "androidx.test.espresso:espresso-core:${versions["espressoVersion"]}","junit"               : "junit:junit:${versions["junitVersion"]}",//注释处理器
            "support_annotations" : "com.android.support:support-annotations:${versions["annotationsVersion"]}","design"              : "com.google.android.material:material:${versions["designVersion"]}",//方法数超过65535解决方法64K MultiDex分包方法
            "multidex"            : "androidx.multidex:multidex:2.0.0",//阿里路由
            "arouter_api"         : "com.alibaba:arouter-api:${versions["arouterApiVersion"]}","arouter_compiler"    : "com.alibaba:arouter-compiler:${versions["arouterCompilerVersion"]}","arouter_annotation"  : "com.alibaba:arouter-annotation:${versions["arouterAnnotationVersion"]}",//黄油刀
            "butterknife"         : "com.jakewharton:butterknife:${versions["butterknifeVersion"]}","butterknife_compiler": "com.jakewharton:butterknife-compiler:${versions["butterknifeVersion"]}"
    ]
}

然后在project的build.gradle中引入config.gradle文件:

apply from: "config.gradle"

基础公共组件

基础公共组件 common 将一直作为 library 存在,所有业务组件都需要依赖 common 组件。

common 组件主要负责封装公共部分,如网络请求、数据存储、自定义控件、各种工具类等,以及对第三方库进行统一依赖等。

下图是我的 common 组件的包结构图:

前文有言,common 组件还负责对第三方库进行统一依赖,这样上层业务组件就不需要再对第三方库进行重复依赖了,其 build.gradle 源码如下所示:

apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'

……

dependencies {
    // 在项目中的libs中的所有的.jar结尾的文件,都是依赖
    implementation fileTree(dir: 'libs',include: ['*.jar'])

    //把implementation 用api代替,它是对外部公开的,所有其他的module就不需要添加该依赖
    api rootProject.ext.dependencies["appcompat"]
    api rootProject.ext.dependencies["constraintlayout"]
    api rootProject.ext.dependencies["junit"]
    api rootProject.ext.dependencies["runner"]
    api rootProject.ext.dependencies["espresso_core"]
    //注释处理器,butterknife所必需
    api rootProject.ext.dependencies["support_annotations"]

    //MultiDex分包方法
    api rootProject.ext.dependencies["multidex"]

    //Material design
    api rootProject.ext.dependencies["design"]

    //黄油刀
    api rootProject.ext.dependencies["butterknife"]
    annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]

    //Arouter路由
    annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
    api rootProject.ext.dependencies["arouter_api"]
    api rootProject.ext.dependencies["arouter_annotation"]

}

业务组件

业务组件在 library 模式下,向上组合为整体性项目;在 application 模式下,可独立运行。

其 build.gradle 源码如下:

if (Boolean.valueOf(rootProject.ext.isModule_North)) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
apply plugin: 'com.jakewharton.butterknife'

……

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

    //公用依赖库
    implementation project(':x_module_common')
    implementation project(':m_module_main')
    //黄油刀
    annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]
    //Arouter路由
    annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
}

至此,组件化架构的搭建就算完成了。

可还有几个问题,是组件化开发中必须要关注的,也是项目做组件化改造时可能会遭遇的难点,我们一起来看看吧。

组件化必须要关注的几个问题

Application

在 common 组件中有 BaseAppliaction,提供全局唯一的 context,上层业务组件在组件化模式下,均需继承于 BaseAppliaction。

/**
 * 基础 Application,所有需要模块化开发的 module 都需要继承自此 BaseApplication。
 */
public class BaseApplication extends Application {

    //全局唯一的context
    private static BaseApplication application;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        application = this;
        //MultiDexf分包初始化,必须最先初始化
        MultiDex.install(this);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        initARouter();
    }

    /**
     * 初始化路由
     */
    private void initARouter() {
        if (BuildConfig.DEBUG) {
            ARouter.openLog();  // 打印日志
            ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
        }
        ARouter.init(application);// 尽可能早,推荐在Application中初始化
    }

    /**
     * 获取全局唯一上下文
     *
     * @return BaseApplication
     */
    public static BaseApplication getApplication() {
        return application;
    }

applicationId 管理

可为不同组件设置不同的 applicationId,也可缺省,在Android Studio中,默认的 applicationId 与包名一致。

组件的 applicationId 在其 build.gradle 文件的 defaultConfig 中进行配置:

if (Boolean.valueOf(rootProject.ext.isModule_North)) {
    //组件模式下设置applicationId
    applicationId "com.niujiaojian.amd.north"
}

manifest.xml 管理

组件在 library 模式和 application 模式下,需要配置不同的 manifest.xml 文件,因为在 application 模式下,程序入口 Activity 和自定义的 Application 是不可或缺的。

在组件的 build.gradle文件 的 android 中进行 manifest 的管理:

/*
    * java插件引入了一个概念叫做SourceSets,通过修改SourceSets中的属性,
    * 可以指定哪些源文件(或文件夹下的源文件)要被编译,
    * 哪些源文件要被排除。
    * */
    sourceSets {
        main {
            if (Boolean.valueOf(rootProject.ext.isModule_North)) {//apk
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    //library模式下,排除java/debug文件夹下的所有文件
                    exclude '*module'
                }
            }
        }
    }

资源名冲突问题

资源名冲突问题,相信大家多多少少都遇到过,以前最常见的就是第三方 sdk 导致的资源名冲突了。

这个问题没有特别好的解决办法,只能通过设置资源名前缀 resourcePrefix 以及约束自己开发习惯进行解决。

资源名前缀 resourcePrefix ,是在 Project 的 build.gradle 中进行设置的:

/**
 * 限定所有子类xml中的资源文件的前缀
 * 注意:图片资源,限定失效,需要手动添加前缀
 * */
subprojects {
    afterEvaluate {
        android {
            resourcePrefix "${project.name}_"
        }
    }
}

这样设置完之后,string、style、color、dimens 等中资源名,必须以设置的字符串为前缀,而 layout、drawable 文件夹下的 shape 的 xml 文件的命名,必须以设置的字符串为前缀,否则会报错提示。

另外,资源前缀的设置对图片的命名无法限定,建议大家约束自己的开发习惯,自觉加上前缀。

建议:将 color、shape、style 这些放在基础库组件中去,这些资源不会太多,且复用性极高,所有业务组件又都会依赖基础库组件。

Butterknife R2 问题

Butterknife 存在的问题是控件 id 找不到,只要将 R 替换为 R2 即可解决问题。

需要注意的是,在如下代码示例外的位置,不要这样做,保持使用 R 即可,如 setContentView(R.layout.b_module_north_activity_splash)

public class SplashActivity extends BaseActivity {

    @BindView(R2.id.btn_toMain)
    Button btnToMain;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.b_module_north_activity_splash);
        ButterKnife.bind(this);
    }

    ……

    @OnClick(R2.id.btn_toMain)
    public void onViewClicked() {
    }
}

另外要注意的是,每一个使用 Butterknife 的组件,在其 build.gradle 的 dependencies 都要配置注解处理器处理其 compiler 库:

apply plugin: 'com.jakewharton.butterknife'

……

dependencies {

    ……

    annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]
}

组件间跳转

由于业务组件间不存在依赖关系,不可以通过 Intent 进行显式跳转。

若需跳转,是要借助于路由的,我使用的是阿里的开源框架 ARouter

注:我在案例中只使用了 ARouter 的基础的页面跳转功能,更复杂的诸如携带参数跳转、声明拦截器等功能的使用方法,大家可到 Github 上查看其使用文档。

在每一个需要用到 ARouter 的组件的 build.gradle 文件中对其进行配置:

android {
   ...
       defaultConfig {
         ...
        //Arouter路由配置
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
                includeCompileClasspath = true
            }
        }
    }
}
dependencies{
     ...
    //Arouter路由
    annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
}

跳转目标页面配置:

@Route(path = "/main/MainActivity")
public class MainActivity extends BaseActivity {
   ……
}

跳转来源页面的跳转代码:

...
   ARouter.getInstance()
          .build("/main/MainActivity")
          .navigation();
...

后记

组件化优势多多,用起来爽的不要不要的。

其中快感来的最快的,当属大大提升了编译速度了。

参考视频:

组件化开发以及路由框架实现
Android应用的进阶宿命,组件化架构与阿里组件化路由解析与实现
超大型项目的终极架构,窥探阿里ARouter组件化路由框架的原理
项目越做越复杂?组件化开发替你解决所有问题

为了能让大家刚好的了解Android 组件化相关方面的知识点,小编为大家进行整理了一份Android组件化强化实战的学习文档,里面不仅记录了各大厂的技术实战,还有一些知识点解析,有需要参考的小伙伴可以私信回复我 666 即可获取 !!!

原文地址:https://blog.csdn.net/weixin_61845324

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


更新Android SDK到3.0版本时,遇到Failed to rename directory E:\android\tools to E:\android\temp\ToolPackage.old01问题,导致无法更新,出现该问题的原因是由于3.0版本与较早的sdk版本之间文件结构有冲突,解决
Android 如何解决dialog弹出时无法捕捉Activity的back事件 在一些情况下,我们需要捕捉back键事件,然后在捕捉到的事件里写入我们需要进行的处理,通常可以采用下面三种办法捕捉到back事件: 1)重写onKeyDown或者onKeyUp方法 2)重写onBackPressed方
Android实现自定义带文字和图片的Button 在Android开发中经常会需要用到带文字和图片的button,下面来讲解一下常用的实现办法。一.用系统自带的Button实现 最简单的一种办法就是利用系统自带的Button来实现,这种方式代码量最小。在Button的属性中有一个是drawable
Android中的"Unable to start activity ComponentInfo"的错误 最近在做一款音乐播放器的时候,然后在调试的过程中发现一直报这个错误"Unable to start activity ComponentInfo",从字面
Android 关于长按back键退出应用程序的实现最近在做一个Android上的应用,碰到一个问题就是如何实现长按back键退出应用程序。在网上查找了很多资料,发现几乎没有这样的实现,大部分在处理时是双击back键来退出应用程序。参考了一下双击back键退出应用程序的代码,网上主流的一种方法是下面
android自带的时间选择器只能精确到分,但是对于某些应用要求选择的时间精确到秒级,此时只有自定义去实现这样的时间选择器了。下面介绍一个可以精确到秒级的时间选择器。 先上效果图: 下面是工程目录: 这个控件我也是用的别人的,好像是一个老外写的,com.wheel中的WheelView是滑动控件的主
Android平台下利用zxing实现二维码开发 现在走在大街小巷都能看到二维码,而且最近由于项目需要,所以研究了下二维码开发的东西,开源的二维码扫描库主要有zxing和zbar,zbar在iPos平台上应用比较成熟,而在Android平台上主流还是用zxing库,因此这里主要讲述如何利用zxing
Android ListView的item背景色设置以及item点击无响应等相关问题 在Android开发中,listview控件是非常常用的控件,在大多数情况下,大家都会改掉listview的item默认的外观,下面讲解以下在使用listview时最常见的几个问题。1.如何改变item的背景色和按
如何向Android模拟器中导入含有中文名称的文件在进行Android开发的时候,如果需要向Android模拟器中导入文件进行测试,通过DDMS下手动导入或者在命令行下通过adb push命令是无法导入含有中文文件名的文件的。后来发现借用其他工具可以向模拟器中导入中文名称的文件,这个工具就是Ultr
Windows 下搭建Android开发环境一.下载并安装JDK版本要求JDK1.6+,下载JDK成功后进行安装,安装好后进行环境变量的配置【我的电脑】-——>【属性】——>【高级】 ——>【环境变量】——>【系统变量】中点击【新建】:变量名:CLASSPATH变量值:……
如何利用PopupWindow实现弹出菜单并解决焦点获取以及与软键盘冲突问题 在android中有时候可能要实现一个底部弹出菜单,此时可以考虑用PopupWindow来实现。下面就来介绍一下如何使用PopupWindow实现一个弹出窗。 主Activity代码:public void onCreat
解决Android中的ERROR: the user data image is used by another emulator. aborting的方法 今天调试代码的时候,突然出现这个错误,折腾了很久没有解决。最后在google上找到了大家给出的两种解决方案,下面给出这两种方法的链接博客:ht
AdvserView.java package com.earen.viewflipper; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory;
ImageView的scaleType的属性有好几种,分别是matrix(默认)、center、centerCrop、centerInside、fitCenter、fitEnd、fitStart、fitXY。 |值|说明| |:--:|:--| |center|保持原图的大小,显示在ImageVie
文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml<?xml version="1.0" encoding="utf-8"?><RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具