Android组件化框架设计与实践

业务模块互相耦合

组件化的指导思想是:分而治之,并行开发,一切皆组件。要实现组件化,无论采用什么样的技术方式,需要考虑以下七个方面问题:

  1. 代码解耦。如何将一个庞大的工程分成有机的整体?

  2. 组件单独运行。因为每个组件都是高度内聚的,是一个完整的整体,如何让其单独运行和调试?

  3. 组件间通信。由于每个组件具体实现细节都互相不了解,但每个组件都需要给其他调用方提供服务,那么主项目与组件、组件与组件之间如何通信就变成关键?

  4. UI 跳转。UI 跳转指的是特殊的数据传递,跟组件间通信区别有什么不同?

  5. 组件生命周期。这里的生命周期指的是组件在应用中存在的时间,组件是否可以做到按需、动态使用、因此就会涉及到组件加载、卸载等管理问题。

  6. 集成调试。在开发阶段如何做到按需编译组件?一次调试中可能有一两个组件参与集成,这样编译时间就会大大降低,提高开发效率。

  7. 代码隔离。组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用,也就是如何从根本上杜绝耦合的产生?

组件化架构目标:告别结构臃肿,让各个业务变得相对独立,业务组件在组件模式下可以独立开发,而在集成模式下又可以变为 AAR 包集成到“ APP 壳工程”中,组成一个完整功能的 APP。

先给出框架设计图,然后再对这七个问题进行一一解答。

A组件化框架架构

从图中可以看到,业务组件之间是独立的,互相没有关联,这些业务组件在集成模式下是一个个 Library,被 APP 壳工程所依赖,组成一个具有完整业务功能的 APP 应用,但是在组件开发模式下,业务组件又变成了一个个 Application,它们可以独立开发和调试,由于在组件开发模式下,业务组件们的代码量相比于完整的项目差了很远,因此在运行时可以显著减少编译时间。

各个业务组件通信是通过路由转发,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NdemG3Lx-1615520318619)(https://upload-images.jianshu.io/upload_images/788498-2c26405226fd9ed8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

这是组件化工程模型下的业务关系,业务之间将不再直接引用和依赖,而是通过“路由”这样一个中转站间接产生联系。

那么针对以上提出的七个问题,具体解决如下:

1,代码解耦问题

对已存在的项目进行模块拆分,模块分为两种类型,一种是功能组件模块,封装一些公共的方法服务等,作为依赖库对外提供,一种是业务组件模块,专门处理业务逻辑等功能,这些业务组件模块最终负责组装APP。

2,组件单独运行问题

通过 Gradle 脚本配置方式,进行不同环境切换。比如只需要把 Apply plugin: ‘com.android.library’ 切换成Apply plugin: ‘com.android.application’ 就可以,同时还需要在 AndroidManifest 清单文件上进行设置,因为一个单独调试需要有一个入口的 Activity。比如设置一个变量 isModule,标记当前是否需要单独调试,根据isModule 的取值,使用不同的 gradle 插件和 AndroidManifest 清单文件,甚至可以添加 Application 等 Java 文件,以便可以做一下初始化的操作。

3,组件间通信问题

通过接口+实现的结构进行组件间的通信。每个组件声明自己提供的服务 Service API,这些 Service 都是一些接口,组件负责将这些 Service 实现并注册到一个统一的路由 Router 中去,如果要使用某个组件的功能,只需要向Router 请求这个 Service 的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。在组件化架构设计图中 Common 组件就包含了路由服务组件,里面包括了每个组件的路由入口和跳转。

4,UI 跳转问题

可以说 UI 跳转也是组件间通信的一种,但是属于比较特殊的数据传递。不过一般 UI 跳转基本都会单独处理,一般通过短链的方式来跳转到具体的 Activity。每个组件可以注册自己所能处理的短链的 Scheme 和 Host,并定义传输数据的格式,然后注册到统一的 UIRouter 中,UIRouter 通过 Scheme 和 Host 的匹配关系负责分发路由。但目前比较主流的做法是通过在每个 Activity 上添加注解,然后通过 APT 形成具体的逻辑代码。目前方式是引用阿里的 ARouter 框架,通过注解方式进行页面跳转。

5,组件生命周期问题

在架构图中的核心管理组件会定义一个组件生命周期接口,通过在每个组件设置一个配置文件,这个配置文件是通过使用注解方式在编译时自动生成,配置文件中指明具体实现组件生命周期接口的实现类,来完成组件一些需要初始化操作并且做到自动注册,暂时没有提供手动注册的方式。

6,集成调试问题

每个组件单独调试通过并不意味着集成在一起没有问题,因此在开发后期我们需要把几个组件机集成到一个 APP 里面去验证。由于经过前面几个步骤保证了组件之间的隔离,所以可以任意选择几个组件参与集成,这种按需索取的加载机制可以保证在集成调试中有很大的灵活性,并且可以加大的加快编译速度。需要注意的一点是,每个组件开发完成之后,需要把 isModule 设置为 true并同步,这样主项目就可以通过参数配置统一进行编译。

7,代码隔离问题

如果还是 compile project(xxx:xxx.aar) 来引入组件,我们就完全可以直接使用到其中的实现类,那么主项目和组件之间的耦合就没有消除,那之前针对接口编程就变得毫无意义。我们希望只在 assembleDebug 或者 assembleRelease 的时候把 AAR 引入进来,而在开发阶段,所有组件都是看不到的,这样就从根本上杜绝了引用实现类的问题。

目前做法是主项目只依赖 Common 的依赖库,业务组件通过路由服务依赖库按需进行查找,用反射方式进行组件加载,然后在主工程中调用组件服务,组件与组件之间调用则是通过接口+实现进行通信,后续规划通过自定义Gradle 插件,通过字节码自动插入组件的依赖进行编译打包,实现自动筛选 assembleDebug 或 assembleRelease 这两个编译命任务,只有属于包含这两个任务的命令才引入具体实现类,其他的则不引入。

代码,具体项目实践

一,创建工程

1,APP空壳工程

通过AndroidStudio创建一个APP空壳工程,如图:

APP空壳工程

然后在 APP 工程添加依赖具体业务组件 Module。比如:

依赖关系

2,具体业务组件Module

需要遵循一定组件命名规范,为何需要规范呢,因为需要通过组件命名规范来约束和保证组件的统一性和一致性,避免出现冲突。比如登陆组件,那么名称:b(类型)-ga(部门缩写)-login(组件名称),这就是我们基于共同的约定进行命名的,为后期维护和扩展都带来辨识度。

Login业务组件

二,业务组件配置文件

1,build.gradle配置文修改。如下:

if (isModule.toBoolean()) {

apply plugin: ‘com.android.application’

} else {

apply plugin: ‘com.android.library’

}

android {

compileSdkVersion rootProject.ext.compileSdkVersion

buildToolsVersion rootProject.ext.buildToolsVersion

defaultConfig {

minSdkVersion rootProject.ext.minSdkVersion

targetSdkVersion rootProject.ext.targetSdkVersion

if (isModule.toBoolean()) {

applicationId “com.hik.ga.business.login”

versionCode 1

versionName “1.0”

} else {

//ARouter

javaCompileOptions {

annotationProcessorOptions {

arguments = [ moduleName : project.getName() ]

}

}

}

}

sourceSets {

main {

if (isModule.toBoolean()) {

manifest.srcFile ‘src/main/module/AndroidManifest.xml’

} else {

manifest.srcFile ‘src/main/AndroidManifest.xml’

//集成开发模式下排除debug文件夹中的所有Java文件

java {

exclude ‘debug/**’

}

}

}

}

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’

}

}

}

dependencies {

implementation project(‘:b-ga-common-function-comlib’)

if (!isModule.toBoolean()) {

annotationProcessor “com.alibaba:arouter-compiler:${rootProject.annotationProcessor}”

}

}

这里需要有几点说明一下:

1,通过 isModule.toBoolean() 方法来进行组件间集成模式和组件模式的切换,包括模块是属于Application 还是 Library,由于集成了 ARouter,所以需要对 ARouter 配置文件进行处理。

2,如果组件模式下, 则需要重新设置 AndroidManifest.xml 文件,里面配置新的Application路径。比如Login组件单独运行 AndroidManifest 清单文件

<manifest xmlns:android=“http://schemas.android.com/apk/res/android”

package=“hik.ga.business.applogin” >

<application

android:name=“debug.LoginApplication”

android:allowBackup=“false”

android:icon=“@mipmap/ic_launcher”

android:label=“@string/login_btn_str”

android:supportsRtl=“true”

android:theme=“@style/Theme.AppCompat.Light.NoActionBar”>

<activity

android:name=“hik.ga.business.applogin.login.views.LoginActivity”

android:label=“@string/login_btn_str”

android:launchMode=“singleTop”

android:screenOrientation=“portrait”

android:theme=“@style/AppTheme.NoActionBarFullScreen”>

3,实现组件全局应用配置类,这个类的目的是在组件加载时初始化一些组件自身的资源,如下:

public class LoginApplicationDelegate implements IApplicationDelegate {

private static final String TAG = “LoginApplicationDelegate”;

@Override

public void onCreate() {

EFLog.d(TAG, “*------------------onCreate()---------------->”);

}

@Override

public void enterBackground() {

EFLog.d(TAG, “*------------------enterBackground()---------------->”);

}

@Override

public void enterForeground() {

EFLog.d(TAG, “*------------------enterForeground()---------------->”);

}

@Override

public void receiveRemoteNotification(Map<String, String> message) {

EFLog.d(TAG, "receiveRemoteNotification msg = " + message);

}

@Override

public void onTerminate() {

EFLog.d(TAG, “*------------------onTerminate()---------------->”);

}

@Override

public void onConfigurationChanged(Configuration configuration) {

EFLog.d(TAG, “*------------------onConfigurationChanged()---------------->”);

}

@Override

public void onLowMemory() {

EFLog.d(TAG, “*------------------onLowMemory()---------------->”);

}

@Override

public void onTrimMemory(int var1) {

EFLog.d(TAG, “*------------------onTrimMemory()---------------->”);

}

}

三,路由服务

1,定义公共组件路由API和入口,通过路由服务组件查找,如图:

公共路由服务

2,组件路由实现

每个组件对外提供什么能力,首先需要在路由服务组件创建一个接口文件,如下是登陆组件接口声明和实现。

Login 接口:

定义Login接口

具体实现:

Login接口具体实现

路由使用:比如我们想从设置页面跳转到登陆页面,使用 Login 接口里的方法,使用如下:

ILoginProvider loginService = (ILoginProvider) ARo
uter.getInstance().build(RouterPath.ROUTER_PATH_TO_LOGIN_SERVICE).navigation();

if(loginService != null){

loginService.accountToLogin(AccountActivity.this);

}

小结

总的来说,通过应用组件化框架,使得我们工作中的具体项目变得更轻、好组装、编译构建更快,不仅提高工作效率,同时自我对移动应用开发认知有进一步的提升。因为组件化框架具有通用性,特别适用于业务模块迭代多,量大的大中型项目,是一个很好的解决方案。至于组件化框架之后演化的道路,则是打造组件仓库,完善组件开发规范,丰富组件功能库,有一些粒度大的业务组件可以进一步的细化,对组件功能进行更单一的内聚,同时基于现有组件化框架,便于过度在未来打造插件化框架,进一步升级 APP 动态能力,比如热加载、热修复等,那又是另一种使用场景和设计架构了,其实组件化和插件化框架最大的区别就是在是否具备动态更新能力。

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

如果你需要这些资料, ⬅ 专栏获取
框架最大的区别就是在是否具备动态更新能力。

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-SDHq1iy7-1719178974070)]

[外链图片转存中…(img-qw0gji3x-1719178974071)]

[外链图片转存中…(img-NxXfaP8T-1719178974071)]

[外链图片转存中…(img-EJRJDNy9-1719178974072)]

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

如果你需要这些资料, ⬅ 专栏获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值