您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 
 订阅
Android架构建设之组件化、模块化建设
 
作者: 西南黑少
  4056  次浏览      16 次
 2022-2-28
 
编辑推荐:
在本文中,我们首先针对目前的项目架构进行分析,其次介绍如何用Android架构建设组件化、模块化 ,希望对您的学习有所帮助。
本文来自于CSDN ,由火龙果软件Alice编辑推荐。

一、 背景

1、 再此之前我们已经做好了Data Repository(数据统一输出口),各种数据源与业务进行了很好的分离;同时我们也接入了Route,解决了activity间的跳转耦合,其实Route能够真正发挥它强大作用的是组件化、模块化项目中。以上这些工作我们都为后面业务的剥离打下了良好的基础。

2、 抢单、订单等各种业务柔和在主工程中,各方面耦合严重。

3、 业务分离也可能面临一些解耦等各种难题,而且业务的分离牵扯的东西比较多,比如像图片等资源和业务逻辑代码。相对于基础组件难度系数更高。颗粒度可以逐渐从粗到细,我们可以从业务独立性比较强的模块入手,比如登录、注册,只要我们能够把一个业务模块化之后,其它的业务模块化也是异曲同工之妙。

二、 文档目的

1、 能够给参与组件化的开发成员对组件化、模块化整体的一个认知,达到共识,从而形成一个统一的技术方案、避免组件化、模块化总体方向偏离。

2、 架构方案沉淀。

3、 能够为组件化、模块化建设提供一定的理论基础。

4、 鉴于物流项目构建初期我尚未加入公司,以下对业务或项目框架的论述可能难免有偏差,如果文章中发现描述不对的地方还望各位批评指正。

三、 没做业务分离之前我们的项目是这样的

按照目前项目架构的呈现来看,它由基础组

件层和业务层组成的两层架构,如图:

从项目的长远来看,我认为架构愿景应该是由基础组件层、业务组件层和业务层组成的三层架构。如下图:

四、针对目前的项目架构分析

4-1、优点:

1、 抢单、订单等各个业务集中在主工程中,业务间跳转和通信处理起来方便。

2、 只要维护一个主工程即可,从空间的范围角度来讲,维护成本比较低。

4-2、缺点:

1、 各业务柔和在主工程中,造成了代码结构混乱、层次不清,各业务技术方案不统一,冗余代码充斥项目的各个角落;甚至连基本的包结构也是胡乱不堪,项目架构更是无从谈起。

2、 新业务接入时,大家只不过是不停地往主工程堆砌代码添加新功能罢了,从而导致主工程将越来越越来庞大。克隆项目、编译项目时长不断变长等一系列问题影响着整个团队的项目开发效率。

3、各业务耦合在主工程中,存在着牵一发动全身的后患;各个业务没法进行单元测试,并行开发。

五、组件化、模块化解决了什么问题?

1、架构更清晰,实现解耦。

2、单个业务模块单独编译打包,加快编译速度。

3、业务模块间解耦,业务分工明确,开发人员仅专注与自己的业务。

4、多团队间可以并行开发、测试。

5、组件、业务独立更新版本,可回滚,持续集成。

6、避免重复造轮子,节省开发维护成本。

7、降低项目复杂性,提升开发效率。

8、多个团队公用同一个组件,在一定层度上确保了技术方案的统一性。

我个人认为项目实施组件化、模块化的终极目标应该是能够快速组装出新的App。

六、组件、模块、业务划分的概念

6-1、组件

App工程上所说的 组件,应该翻译为“Component”,意思是组件、部件、元件。在电脑的构造中,CPU这类部件就是电脑构成的基本元素,而且CPU坏了,更换一个新CPU即可,绝不会影响到硬盘等其它部件,具有高内聚,松耦合的特性。在App工程上,组件是构成业务或者功能模块的基本单位。原则上,组件与组件之间互不依赖。

例如,网络请求功能,应该叫“网络组件”,而不是“网络模块”。因为网络请求数据从功能、业务上,已经不能往下拆了。

网络请求可能使用okhttp,或者retrofit。无论网络组件用okhttp网络库,还是retrofit网络库,都不会影响这个组件的功能,因此组件具有可替换性;同时,网络组件可以被多个业务使用,因此组件具有可复用性,而且组件的开发需要遵守软件设计模式中”单一职责”原则。

同理,日志功能,叫“日志组件”,不叫“日志模块”。因为从编码的角度来看,我们不可能特意去划分订单日志或是抢单日志,应该是统一记录日志即可。

6-2、模块

模块翻译为“Module”。模块由多个组件构成,它可以实现一个独立的功能,甚至业务。

例如,胖猫司机的订单功能,是一个业务,可以叫“订单模块”,产品培训或各部门交涉时习惯上叫“订单业务”。它可以拆分更小的模块:订单搜索、订单调度车辆等,只是目前功能较小,不必过度拆分,否则得不偿失。

6-3、组件和模块的关系

从上面的阐述可以得出,一个工程,由多个模块组成,每个模块由多个组件构成。但很多时候,两者界限还是相当模糊。例如“日志组件”称为“日志模块”,也没有违和感。

1、 组件从业务角度上不能继续拆分,可替换,可复用;

2、 模块的定义比较笼统,可以是一个Business业务,可以是技术架构中一个业务,也可以是几个组件构成的小功能。

通过以上的种种描述来看组件化侧重点是应该基于重用,而模块化侧重点应该是基于解耦。无论是组件化还是模块化,目标都是把臃肿的工程,拆分为更小的部分,解耦各种复杂的逻辑,便于代码管理。

6-4、业务划分

产品经理根据业务部门的需求在规划产品功能,或者做产品原型时,业务已经划分好了。如果业务庞大的App项目,后端牛逼的话,他们也会做业务划分,极有可能根据业务进行负载均衡来提高服务器交互性能。所以,App端做业务划分时,可以咨询后端,正所谓一个优质软件的形成离不开各个环节,前后不分家,也可以咨询产品经理。

例如:我们的胖猫司机App,划分出了抢单、订单、结算等不同的业务。我们可以称之为Business业务。

将来随着我们的业务不断发展,App不断壮大,也有可能会形成一些基础的业务,即公有业务(CommonBusiness)

6-5、组件与业务的关系

上文提过,多个组件构成一个模块,当模块相当于业务时,就是说该业务由多个组件组合而成。

不仅仅订单业务调用,所有业务都会调用这些组件。于是,业务架构应该逐渐向上文提到的架构愿景靠拢

整个项目被清晰的划分为三层,从下往上分别是:

Basic Component Layer: 基础组件层,顾名思义就是一些基础组件,包含了各种开源库以及和业务无关的各种自研工具库;

Business Component Layer: 业务组件层,这一层的所有组件都是业务相关的,例如上文提到的公共业务组件,当然物流项目目前尚未提炼公有业务,可能公有部分过小而不能够形成公有业务组件。但从架构的角度来讲应该预留足够的空间,有朝一日能够排上用场,让架构更加完善;

Business Module Layer: 业务 Module 层,在 Android Studio 中每块业务对应一个单独的 Module。例如物流 App 我们就可以拆分成订单 Module、抢单 Module、结算 Module 等等,每个单独的 Business Module 都必须准遵守目前的MVC架构,以后可能是MVP 架构,也有可能是MVVM架构,还是那句老话一种技术架构无法满足所有的业务项目,更不可能有一种架构方案能够一劳永逸。

根据以上三层架构的描述,物流App最终项目架构愿景应该是这样的:

如上图所示,我们可以把 Basic Component Layer 和 Business Component Layer 放在一起看做是 ZhaoGang SDK,在理想的情况下,新的业务或者项目只需要依赖 ZhaoGang SDK就好。甚至我们可以做得更极致一些,开发一套自己的组件管理平台,业务方可以根据自己的需求选择自己需要的组件,但这是后话了。

组件化、模块化需要注意的一些问题:

1、 对于 Business Component Layer,单一业务组件只能对应某一项具体的业务,对于有个性化需求的对外部提供接口让调用方定制

2、 合理控制各组件和各业务模块的拆分粒度,太小的公有模块不足以构成单独组件或者模块的,我们先放到类似于 CommonBusiness 的组件中,在后期不断的重构迭代中视情况进行进一步的拆分

3、 上层的公有的业务或者功能模块可以逐步下放到下层,合理把握好度就好;

4、 各 Layer 间严禁反向依赖,横向依赖关系各业务开发员商讨决定。

5、 遵守单一职责设计原则:一个类或一个组件尽量保证职责单一。这样做的目的是保证一个类或组件不臃肿,让其达到极致。

6、 遵守依赖倒转设计原则:高层模块不应该依赖底层模块,两个都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。

七、实现方案

有了上面的组件化、模块化理论基础作为根基,实施就显得胸有成竹了。

7-1、聚焦问题,明确思路

组件化不是插件化,插件化是在[运行时],而组件化是在[编译时]。换句话说,插件化是基于多 APK 的,而组件化本质上还是只有一个 APK。

代码实现上核心思路要紧记一句话:开发时是 application,发版时是 library。这样的话可以针对每个业务单独开发测试。

7-2、组件化、模块化需要解决的问题

7-2-1、业务间跳转

各业务模块之间的跳转采用路由框架 Route(com.alibaba:arouter) 来实现,原因是Route不需要任何依赖,而且可以传递参数,还可以对跳转前URL拦截做一些业务规则处理,比如访问某个页面角色权限控制。建议对原始提供的Route sdk进行进一步封装成ZGRoute,方便日后更换Route SDK。

7-2-2、业务数据交互

无论是前端、后端,业务之间数据交互,都是很重要的环节,选用何种合适的方案都能够体现出项目架构的健壮性,复用性、可扩展性、可维护性、灵活性。

可能有人会问,上面提到的Route不是解决了数据传递的问题吗?其实数据交互不仅仅是简单的参数传递,但它也算一种。当业务A需要业务B的业务数据时,这个场景可能要求业务B从网络获取数据,然后再给到业务A,反之亦然。

未做业务拆分时,直接调用其他业务的代码即可:

但业务拆分后,就不能直接调用代码了。两个业务相互独立,代码互不依赖,必须用某种协议(常用json)用数据。

如果其他业务需要获取订单数据,首先订单业务需要注册订单服务到服务中心,其他业务才可以通过协议调用这个服务,其实说白了就是使用接口隔离(业务之间调用数据,通过统一的协议与服务中心交互,不调用业务实际代码),其实提倡的是面相接口编程,在软件开发中能否形成一个好的设计理念是相当重要的。

Android中业务数据交互的方案一般有以下几种:

7-2-2-1、发布-订阅模式

(1) Broadcast

Android四大组件之一。如果运用合理,它就能业务间进行通讯。broadcast仅支持基本数组类型(int、string等)。不选择它的理由是:一方面是如果要传递复杂类型(对象等),需要bean继承serializable,有些时候显得代码不太优雅,而且有些对象也没必要做多一个操作(序列化)。另一方面是broadcast需要注册和反注册来避免内存泄漏,好多初级程序员一般都没有性能意识,容易造成内存泄漏。

(2) Eventbus

EventBus事件总线,比broadcast灵活很多,可以传递任意对象,还能设置哪个线程接收数据。因为EventBus不依赖context,它仅仅以发布-订阅模式工作,所以除了activity、service,任意地方都可以用eventBus通讯。

不选择它的原因:

1、 它是发布订阅模式,不能直接调用获取数据,也不能跨进程。因此,EventBus不太适合跨业务获取数据的需求,但很适合用于不同界面间、不同线程间互通发送事件。

2、 另一方面是eventBus也需要注册和反注册来避免内存泄漏,好多初级程序员一般都没有性能意识,容易造成内存泄漏。

3、 如下图所示, eventBus对代码的侵入性很强,故弃之。

7-2-2-2 直接调用模式

(1) AIDL

通常Android进程间内存相互不能访问,但可以通过把对象转换成操作系统能识别的原语,进程间通讯就成为可能。因此,Android使用AIDL来完成这一任务。但它不适合业务间数据交互。

(2) 私有协议

实质是通过接口解耦,使用URL协议进行数据交互。也是比较好的业务数据交互方式,灵活性也强。但对URL协议依赖性很强,容易写错。

7-2-2-2 socket

socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。适用于长连接场景。

7-2-2-3 Route

目前项目中使用的阿里提供的ARouter组件来实现页面间跳转,它本身也提供了业务数据交互的功能。不选择它的原因是业务间数据交互这么重要的功能,我觉得依赖于第三方库是不太合适的,万一要替换或是ARouter出现问题时,不仅影响了页面跳转的功能,而且影响了模块间数据交互功能。

7-2-2-4 ContentProvider

Android四大组件之一,提供了强大的数据共享功能,不仅适用于同一个进程间数据共享,而且不同进程之间也可以共享数据。

其实我们最想要达到的目的是能够通过某种方式,实现调用方使用被调用方的代码去调用数据,类似的这种效果,我觉得ContentProvider能够胜任。

实现步骤:

1、 在zgservice组件(是一个Module)中定义车库模块对外提供的协议接口

package business.com.zgservice.protocol;

/**
* Created by decheng.yang on 2018/4/16.
* 车库模块协议(即车库服务),让车库模块实现它,
同时需要将它注册到服务中心,让其它模块通过服务中心获取该服务,
以便获取车库模块提供的数据
*/

public interface ProtocolCarport {
String getCar(int id);
}

2、编写车库模块服务具体实现类CarportProvider,存在于车库模块工程(carportbusiness)中

package business.com.carportbusiness.com.carport.service;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;


import business.com.zgservice.protocol.ProtocolCarport;

/**
* Created by decheng.yang on 2018/4/16.
* 车库模块ContentProvider,给其它模块输出车库相关的数据
*/


public class CarportProvider extends ContentProvider
implements ProtocolCarport {
@Override
public boolean onCreate() {
return false;
}


@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable
String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}


@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}


@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}


@Override
public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) {
return 0;
}


@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values,
@Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}


@Override
public String getCar(int id) {
return "我是来自车库服务的数据";
}
}

主App工程的AndroidManifest.xml配置服务

<!--start ydc 配置各个业务协议-->
<provider
android:name="business.com.carportbusiness.com.
carport.service.CarportProvider"
android:authorities="business.com.carportbusiness.com.
carport.service.CarportProvider"></provider>
<!--end ydc 配置各个业务协议-->

3、在zgservice组件中定义服务中心(ZGServiceIoc)

package business.com.zgservice;
import android.content.ContentProvider;
import android.content.ContentProviderClient;
import android.content.Context;


import business.com.zgservice.protocol.ProtocolCarport;
import common.CommonApp;


/**
* Created by decheng.yang on 2018/4/16.
* 使用接口和ContentProvider实现的业务模块数据交互服务中心,
主要目的是注册各个模块的服务,各个模块可以通过它获取服务,
从而获取各个模块的数据。
*/


public class ZGServiceIoc {
public static ProtocolCarport getCarportService (String providerPath) {
return (ProtocolCarport) get(providerPath);
}


/**
* 获取ContentProvider
*
* @param authority AndroidManifest.xml配置provider的authority
* @return
*/
public static ContentProvider get(String authority) {
try {
Context context = CommonApp.getApp().getApplicationContext();
ContentProviderClient client = context.getContentResolver(). acquireContentProviderClient(authority);


if (client != null) {
ContentProvider provider = client.getLocalContentProvider();


return provider;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

}

4、在zgservice组件中定义各个模块的服务地址(ProviderPath),注意它是一个字符串参数。

package business.com.zgservice.constant;
/**
* Created by decheng.yang on 2018/4/16.
* 各个模块相关的ContentProvider地址
*/


public class ProviderPath {
//车库服务地址
public final static String Carport_ProviderPath = "business.com.carportbusiness.com.
carport.service.CarportProvider";
}

5、现在我在订单模块工程中获取车库数据

看到没,订单模块可以轻松获取到车库模块的数据,而且仅仅是传递一个服务地址给服务中心,就能获取到你想要的各个模块的服务,这期间没有任何业务代码的耦合,zgservice起到了一个控制反转的作用,代码显得格外优雅。可见Android世界里,只要合理运用每一个组件,都能够达到意想不到的效果。

我觉得编程是一门技术,更加是一门艺术。需要用心体会啊,一个人的思想比任何他外在的东西都重要。

其实组件与组件之间,也存在相互调用的情况,可参考这种做法。

7-2-3、解决 Application 冲突

由于 module 在开发过程中是以 application 的形式存在的,如果这个 module 调用了类似 ((XXXApplication)getApplication()).xxx()这种代码的话,最终 release 项目时一定会发生类转换异常。因为在 debug 状态下的 module 是一个 application,而在 release 状态下它只是一个 lib。所以也就是在 debug 和 release 时获取到的 Application 不是同一个类的对象。

这个问题还好,我们只要在 application 里面尽量不要写方法实现,不要做强转操作就好。

如果确实要区分,业务模块在 debug 状态和 release 状态有不同的行为,可以通过扩展 BuildConfig 这个类,在代码中通过 boolean 值来执行不同的逻辑。只需要在 gradle 中加入即可。

7-2-4、重复依赖

重复依赖问题其实在开发中经常会遇到,比如你 compile 了一个A,然后在这个库里面又 compile 了一个B,然后你的工程中又 compile 了一个同样的B,就依赖了两次。

7-2-5、资源名冲突

因为分了多个 module,在合并工程的时候总会出现资源引用冲突,比如两个 module 定义了同一个资源名,可加上模块名前缀命名。

7-2-6、模块资源和共用资源处理

应该把各模块隶属的相关资源应该从主工程中分离到各个模块中;共用的部分需要构建一个公共资源存放模块,里面是一些通用的代码,即每个业务模块都会接入的部分,可能需要构建base-res或Base-Module。这里需要遵守一个游戏规则:资源或是业务逻辑边界不能够掌握时,先统统浮到上层(base-lib或是base-Module),日后可逐渐下沉。

7-2-7、各个Module如何持有第三方库问题

各个模块都需要用到第三方库,需要考虑如何统一管理第三方库及其版本。

7-2-8、eventbus和Broadcast

目前项目中使用了eventbus和Broadcast通信,组件化和模块化之后功能是否受影响。 如何处理像eventbus相关的这些类,可能需要构建出CommonModule,让其置放在其中,供各个模块使用。

广播亦如此:

7-2-9、共用类的处理

主工程中有些bean和Adapter(适配器)是业务间通用的,比如车辆bean来说它不仅在车库模块中使用,而且在订单->车辆调度中使用。可能需要构建出CommonBusiness或BaseModule,让其置放在其中,供各个模块使用。

7-3-0、全局协议的处理

主工程中定义了一些全局的协议。由于主App Module不能反向依赖于各个业务Module可能需要构建出CommonBusiness或BaseModule,让其置放在其中,供各个模块使用。

7-3-1、工具类的处理

目前有些工具类存放在主项目中,由于主App Module不能反向依赖于各个业务Module,所以可能需要梳理浮到ZGCommonUtils供各个模块使用。

7-3-2、DataRepository(整个项目的数据提供中心)完善

SharedPreferencesHelper目前置于主工程中,模块化使得各个业务模块需要共享SharedPreferencesHelper中的数据,需要下沉到DataRepository中

7-3-3、Hybird 开发是怎么支持的?

7-3-4、注意事项

鉴于Route和Data Repository刚刚建设完毕,尚未通过测试部门验证,所以需要重新开设一个分支来进行组件化、模块化建设,建议迭代版本时,分批验证;

由于业务分离难度系数颇高,所以我们进行这项工作时,需要一步一个脚印,每一步操作务必保证现有功能不受影响;

在建设过程中难免会碰到技术难题,希望团队成员能够第一时间能够积极抛出问题,进行讨论。

嘱托:组件化、模块化之路漫长,需要我们多点耐心。只要我们能把设计思路说的如此清晰,那就肯定可以实现,能把问题表达明白那离解决问题就不远了。我们能把组件化、模块化的工作做好,对于个人的项目架构能力会有前所未有的提升。

 

 
   
4056 次浏览       16
相关文章

整洁架构在前端的设计思想与应用实践
细说 Vue 响应式原理的 10 个细节!
前端性能精进之浏览器——呈现
从输入URL到Web页面呈现的全过程
相关文档

浅谈 Vue 3 响应式与组合式 API
35分钟掌握Angular核心概念
java-web-使用分层实现业务处理
HTML5的canvas绘图
相关课程

HTML 5 + CSS3 原理与开发应用
Web前端高级工程师必备技能实战
使用Vue.jS构建web应用实战
Vue大型项目开发实战

最新活动计划
MBSE(基于模型的系统工程)4-18[北京]
自然语言处理(NLP) 4-25[北京]
基于 UML 和EA进行分析设计 4-29[北京]
以用户为中心的软件界面设计 5-16[北京]
DoDAF规范、模型与实例 5-23[北京]
信息架构建模(基于UML+EA)5-29[北京]
 
 
最新文章
整洁架构在前端的设计思想与应用实践
细说 Vue 响应式原理的 10 个细节!
前端性能精进之浏览器——呈现
VUE.JS组件化开发实践
深入理解JSCore
最新课程
HTML 5 + CSS3 原理与开发应用
Web前端高级工程师必备技能实战
Vue大型项目开发实战
React原理与实践
Vue.js进阶与案例实践
成功案例
中交集团 构建Web自动化测试框架
某著名电信公司 Vue.js进阶与案例
国电通网络技术 HTML5+CSS3 +web前端框
移动通信 移动互联网应用开发原理
某电力行 android开发平台最佳