组件化技术,对于经常开发同类型需求的开发团队来说,再提高开发效率和代码维护上是一个十分有利的工具。对于相同的业务流程来说,其高效复用(不改动或少量改动)的前提主要有两个纬度:
这里面基础库的一致性是较为根本和重要的,他可以让我们的组件在启用时,只需要添加组件的依赖就可以引入组件,而不用关心,由于依赖导致的报错。这让我们的精力可以更聚焦于具体的业务。
而编码风格的统一,无论是在开发阶段,还是打包阶段,都能有效帮我们规避掉很多烦人的小问题。
Module作为组件化过程的中基本元素,这里主要根据Module的性质进行命名上的规范,方便快速区分和找到对应业务。
这里在介绍一下组件池这个概念,组件池的意思就是组件资源的一个汇总。一个团队随着组件化开发的深入,组件必然越来越多,大多数项目可能都存在登录组件,此时显然module_login这种命名已经不再合适了,所以这里需要在区分组件的基础上,再区分项目,这就是兼容了组件池的命名。
组件性质 | 建议名称 | 示例 |
---|---|---|
基础组件拆分管理 | lib_project_业务 | lib_hawk_base(自定义的基础类) lib_hawk_router(下沉的接口和数据对象) |
基础组件统一管理 | lib_project_业务 | lib_hawk_base(通用基础依赖的集合体) |
业务组件 | module_project_业务 | module_hawk_main |
对外暴露接口 | interface_project_业务 | interface_hawk_main |
对于基础组件的管理,较为常见的方式为统一管理、分开管理,两种管理模式在依赖后,作为组件化的基础,其对于上层组件是相等的。分开管理中对公用业务下沉和开源依赖的分开,其实主要是更好的体现底层库的构成结构,方便了解底层库的主要职责。
因为使用统一底层的形式对开放的数据对象和服务进行统一管理,那么此处可以使用
组件名称{bean,event,services}的形式进行划分。
组件化对外暴露业务接口,常见的形式一般就是将此部分接口和数据对象做下沉,下沉至lib_base层,那么对于上层组件来说,这些对外服务就都是可见的了,不过这种形式有两个缺点:
所以为了解决以上问题和保证对外暴露业务的独立性和私有性而推出的解决方案。这里我们使用的是Jar Library的形式创建Module这里面主要存放,对外开放的业务接口,和接口涉及的基础数据对象。此部分由纯Java组成,只有接口和数据Bean。此部分Build之后会生成.jar这个就是交付给其他组件的对外暴露接口。
当其他组件需要你的服务的时候,只需要申请.jar就可以了。
如果使用了EventBus,那么Event也属于对外开放的对象。
组件内的包按照Module命名进行命名避免打包时产生冲突。
其命名公式为::com.项目名称.组件名称
包所在为止 | 命名规则 | 示例 |
---|---|---|
基础组件 | com.project.*** | com.project.base、com.project.common |
业务组件 | com.project.*** | com.project.login |
对外暴露接口 | com.project.interfaces.*** | com.project.interfaces.login |
注:interface为关键字不能出现在包名中,所以此处使用interfaces!
组件内,对于对开开放接口的实现类建议进行统一命名放置在service中。
如:package com.telephone.login.service;
组件内类命名在Google推荐的命名方式下,应该尽量体现此类的归属性。
类 | 示例 |
---|---|
Application | LoginApplication |
Activity | LoginAuthCodeActivity |
Fragment | LoginAuthCodeFragment |
Adapter | LoginAuthCodeAdapter |
这里应该尽量避免抽象命名。以LoginAuthCodeActivity举例,一个应用中可能存在不止一个验证码页面,如果直接使用AuthCodeActivity的形式,虽然由于组件间包名不同的原因,在打包时不会报错,但在我们全局查找的时候就会出现多个AuthCodeActivity的类,这样对于判断类所处的组件有需要查看处于包名末尾的包名,这样显然不够直观高效。而具体业务命名可以直接快速的锁定业务类。
组件化中,组件内一般会存在对外暴露接口的实现,以及接入其他组件服务的接口管理类。
这里对于实现类建议命名为:对外接口名称+Impl
如登录的服务接口实现:
public interface LoginService {}
@ServiceProvider
public class LoginServiceImpl implements LoginService
对于接入的接口管理类建议命名:对外接口名称+Manager
如登录组件需要设置组件的功能:
public enum LoginServiceManager {
INSTANCE;
private static final String TAG = "LoginServiceManager";
private Context context;
private MainService mainService;
public void init(Context context) {
this.context = context;
mainService = AppJoint.service(MainService.class);
}
public MainService getMainService() {
return mainService;
}
}
类的开头部分,应尽量保证存在注释,注释应该体现
此处只作为建议。
/**
* Create Time : 2020-5-5 11:11:11
* Author : XXX
* Describe : 登录业务,主要包含登录时登录信息验证和跳转注册页等业务。
*/
由于Android Studio对于上下层依赖Module中的同名资源是不做冲突处理的,所以此处同样建议使用组件名称对资源名称进行归属,避免上下级同名资源被覆盖。
资源类型 | 示例 |
---|---|
layout文件 | login_activity_quicklogin.xml、login_activity_register.xml |
anim文件 | login_slide_in.xml |
mipmap文件 | login_btn_submit.png |
string文件 | < string name=“login_submit”>提交< /string> |
另外对于良好设计的应用UI一般会给出主体颜色,和标准字体大小,以及对应的标准色。此部分建议下沉至lib_base中使用如上的命名方法命名,以此来统一应用整体风格。
另外我们也可能存在,一个组件内部是一个独立的UI显示风格,那么此时建议将独立的UI风格设计移回组件内部。
随着组件化的推进组件的数量会逐渐增减,此时如果使用一个组件一个build.gradle的形式对组件进行管理,显然是低效,且容易出错的。
此处给出通用配置gradle的模版。
文件名称:config.gradle
存放位置:Project的最外层
ext {
//true 每个业务Module可以单独开发
//false 每个业务Module以lib的方式运行
//修改之后需要Sync方可生效
isModule = false
//版本号
versions = [
compileSdkVersion : 29,
buildToolsVersion : "29.0.3",
applicationId : "com.xjl.moudlecriterion", //应用ID
minSdkVersion : 21,
targetSdkVersion : 29,
versionCode : 1, //版本号
versionName : "0.0.1", //版本名称
javaSDKVersion : 1.8,//javaSDK版本
androidxVersion : "1.1.0",
multidexVersion : "1.0.3",
appjointVersion : "1.7",
butterknifeVersion : "10.2.1",
eventbusVersion : "3.2.0",
gsonVersion : "2.8.6",
glideVersion : "4.11.0",
javaxVersion : "1.2",
pickerViewVersion : "4.1.9",
rx3JavaVersion : "3.0.0",
rx3AndroidVersion : "3.0.0",
]
dependencies = [
"appcompat" : "androidx.appcompat:appcompat:${versions["androidxVersion"]}",
//方法数超过65535解决方法64K MultiDex分包方法
"multidex" : "com.android.support:multidex:${versions["multidexVersion"]}",
//组件化框架appjoint
"appjoint" : "io.github.prototypez:app-joint:${versions["appjointVersion"]}",
"appjoint_core" : "io.github.prototypez:app-joint-core:${versions["appjointVersion"]}",
//黄油刀
"butterknife" : "com.jakewharton:butterknife:${versions["butterknifeVersion"]}",
"butterknife_compiler": "com.jakewharton:butterknife-compiler:${versions["butterknifeVersion"]}",
//事件订阅
"eventbus" : "org.greenrobot:eventbus:${versions["eventbusVersion"]}",
"gson" : "com.google.code.gson:gson:${versions["gsonVersion"]}",
//图片加载
"glide" : "com.github.bumptech.glide:glide:${versions["glideVersion"]}",
//时间,地址,条件选择器
"pickerView" : "com.contrarywind:Android-PickerView:${versions["pickerViewVersion"]}",
//RX家族
"rx3_java" : "io.reactivex.rxjava3:rxjava:${versions["rx3JavaVersion"]}",
"rx3_android" : "io.reactivex.rxjava3:rxandroid:${versions["rx3AndroidVersion"]}",
]
}
此模版由Groovy语言编写,主要使用的是List,Map数据结构。
如果感觉读起来吃力可以看这篇帖子:https://blog.csdn.net/u010451990/article/details/105382861
注:新开发的项目不再推荐使用28以下的SDK统一推荐切换到28以上,将依赖的Android库更换为AndroidX!
切换至AndroidX之后的继承
子类 | 父类 |
---|---|
Activity | androidx.fragment.app.FragmentActivity |
Fragment | androidx.fragment.app.Fragment |
Adapter | androidx.recyclerview.widget.RecyclerView.Adapter |
Gradle 3.0开始,使用 implementation, api, runtimeOnly, compileOnly 进行依赖控制。
使用规范:
implementation: 用于组件范围内的依赖,不与其他组件共享。(作用域是组件内,建议)
api: 用于基础层的依赖,要穿透基础层,暴露给组件层。(作用域是所有,不建议)
runtimeOnly: 用于 app 宿主壳的依赖,组件间是绝对隔离的,编译时不可见,但参与打包。(无作用域,建议)
compileOnly: 用于高频依赖,防止 already present 错误。一般是开源库用来依赖 support 库防止与客户的 support 库版本冲突的。
数据库模块建议在各个组件内独立实现,尽量不要做下沉处理。一个组件拥有一个独立的数据库,库内可以创建相关的表。当我们使用注解类型的数据库时,应该有意识的将含有注解的数据对象和原生数据对象区分开,即有注解的对象只做数据库交互操作,而UI刷新,数据回调,组件内外应该统一到无注解的原生数据对象上(不含有注解)。
所在位置 | 示例 |
---|---|
所在包 | com.project.module.db |
实体 | com.project.module.db.bean |
dao | com.project.module.db.dao |
其他规范可以参考:Auligelite 进行开发
应用层使用gradle.properties统一管理版本
初始版本为:0.0.1,不要以1.0.0开始
测试版本格式:0.0.1-SNAPSHOT(SNAPSHOT 为快照版本)
正式版本格式:0.0.1
对于组件自身,无论是开发中业务的多少,还是考虑到后期可能的经常变动,和应用层使用一个版本号都是不合适的,此时需要组件自身维护自身版本。
一个组件相对稳定后,把该组件打包成 aar,发布到 github 或 maven,然后将本地Module依赖更换成远程依赖。
目前部门使用的是Artifactory进行组件的管理。
发布流程如下
注:classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.15.2"
jfrog的版本号和build.gradle的版本号是匹配的,如果升级修改gradle之后发现发布失败,需要对jfrog做升级或者降级处理。
在module的build.gradle中追加如下代码(不需要做插入,直接复制粘贴到之前的module配置下面即可),进行发布。
参数请换成自己的。
//////// 打包发布配置开始 ////////
apply plugin: 'com.jfrog.artifactory'
apply plugin: 'maven-publish'
def MAVEN_LOCAL_PATH ='http://10.110.16.26:8082/artifactory'
def ARTIFACT_ID = 'project_login'
def VERSION_NAME = '0.0.1'
def GROUP_ID = 'com.project.login'
publishing {
publications {
aar(MavenPublication) {
groupId GROUP_ID
version = VERSION_NAME
artifactId ARTIFACT_ID
// Tell maven to prepare the generated "*.aar" file for publishing
artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
}
}
}
artifactory {
contextUrl = MAVEN_LOCAL_PATH
publish {
repository {
repoKey = 'libs-release-local'
username = ""
password = ""
}
defaults {
publications('aar')
publishArtifacts = true
properties = ['qa.level': 'basic', 'dev.team': 'core']
publishPom = true
}
}
}
//////// 打包发布配置结束 ////////
组件的升级应该遵从:开放关闭原则
即我们对外开放的方法有改变时,不建议直接进行修改。而是通过重载或者新建方法的方式来实现。
如:
public void login(String username,String password);
增加参数时:
public void login(String username,String password,String token);
对于废弃的方法应该给出注解并给出取消原因
@Deprecated
public void login(String username,String password);
如果组件间存在先后加载顺序依赖,如组件A加载的基础是组件B先加载,那么此时需要明确组件的优先级。
对于组件的优先级建议使用区间形式定义,如10000、20000、30000
方便优先级变化时,修改一个组件的优先级就可以了。尽量不要简单的使用1、2、3、4这种形式。
另外建议将此部分也写入到统一配置文件当中,方便在调整优先级或划分优先级的时候产生问题。