1.多模块开发的场景
1).多模块的使用场景。
1.APP多模块的开发以及多模块的代码入侵性的改变。新项目不依赖其他项目,因此不能将其他项目的类名引用进需要跳转的模块,那么怎么解决呢,可以用路由代替最好。手动版路由写个全局配置文件对应key value。新模块写配置进去,然后跳转时查找跳转。自动版定义注解接口在编译时自动将写了注解的模块编译到跳转模块中,跳转时查找跳转表进行跳转。
2.减少开发build时间,其他使用到的功能模块都是已经打包好的arr文件,通过编译隔离大型项目只编译入口和项目组正在开发的模块,可以使项目打包运行时间加快。
3.app多人开发的协同的好处,不会改动其他人的代码但是会强约束别人写文档。
4.快捷服务替换,因为移动APP每个功能模块都会需要一些文件资源(图片,文字,指定大小,颜色,或者第三方包,等等)这些资源如果放在单独的功能module里面,如果主项目现在不想用这些模块也可以在build.gradle解除配置。
5.可以解耦首页,减少冷启动的时间。
2).多模块开发的缺点:
1.功能arr模块的维护需要编写打包上传gradle的脚本,比直接拉代码下来编译麻烦。
2.功能arr模块的代码也还是需要用git submodule 代码管理工具进行管理,增加了复杂度。但是在多部门多项目开发时才能体现出优势。
3.需要对maven 组件仓库进行维护。
4.APP内全局消息同步,以及可能有跨进程跨线程的消息同步处理。比如登录或者退出的全局信息同步,以及其他信息等同步问题。
2.多模块App结构
1.app结构设计
功能模块是我们需要解耦,并且可以将其打包成arr上传到maven组件库的。也是我们可以好好解耦设计的。
3.使用maven构建私有仓库
1.maven简介
maven是Apache下的开源项目,maven主要服务于基于Java平台的项目构建,依赖管理,项目信息管理。
1.项目对象模型(Project Object Model):pom对象模型,每个maven工程中都有一个pom.xml文件,定义工程所依赖的jar包,本工程的坐标,打包运行方式
2.依赖管理系统(基础核心):maven通过坐标对项目工程所依赖的jar包统一规范管理。(坐标,依赖资源的地址)
3.maven定义一套项目生命周期:清理,初始化,编译,测试,报告,打包,部署,站点生成
4.一组标准集合:maven工程有自己的工程目录结构,定义坐标有标准。
5.maven管理项目生命周期过程都是基于插件完成的。
项目开发过程主要包含的内容:
1.版本:maven有自己的版本定义和规则
2.构建:maven支持许多种类的应用类型,对于每一种支持的应用程序类型定义好了一组构建规则和工具集
3.输出物管理:
4.依赖关系管理:
5.文档和构建结果:
6.项目关系:
7.移植性管理:
maven仓库:
仓库类型:
hosted :类型的仓库,内部项目的发布仓库
releases :内部的模块中release模块的发布仓库
snapshots :发布内部的SNAPSHOT模块的仓库
3rd party: 第三方依赖的仓库,这个数据通常是由内部人员自行下载之后发布上去
proxy: 类型的仓库,从远程中央仓库中寻找数据的仓库
group: 类型的仓库,组仓库用来为了方便我们开发人员而进行设置的仓库
nexus下载:https://www.sonatype.com/download-oss-sonatype。nexus中托管了maven管理工具。
2.Mac上配置nexus的过程
1.下载后使用解压命令 tar -zxvf nexus.xx.xx.xx
2.用vim .bash_profile 打开配置文件。将nexus的PATH=$PATH:"nexus的bin路径" 写到配置文件中
3.运行source .bash_profile 使配置生效
4.运行chmod -R 700 nexus.xx.xx的解压缩目录 或者看看nexus.xx.xx目录内是否有两个文件夹. nexus.xx.xx 和sonatype-work 两个目录. 因为nexus的默认端口是8081 我们需要查看端口是否被占用,如果被占用可以去nexus的etc 或者config文件中找默认端口配置文件将端口改了.
5.使用命令nexus start 。nexus status 查看是否启动。nexus stop 停止服务。nexus restart强制重启
6.使用http://localhost:8081/nexus 访问.于是我们可以见到一下界面.
7.nexus的登录的默认账号和密码是:admin admin123。登录后我们可以看到如下界面.点击上面的设置样式的图标(server administration and configuration)按钮
8.点击repositories 我们可以看到如下界面:在这里我们可以添加自己的repositories。
需要更深入研究的还有nexus中的不同仓库的使用,nexus中的用户新建以及权限赋予,nexus中建立组件管理任务等等丰富的功能.
4.多模块的重要模块路由模块设计
1.涉及技术:
1.编译时注解的使用自动生成module,action,intercepter,
编译时注解的项目:https://blog.csdn.net/lmj623565791/article/details/51931859
2.线程,线程池,线程同步异步,handler
3.责任链模式,享元模式,策略模式,模版模式
2.路由设计
1.使用编译时注解,将功能模块添加上注解标志,在编译时将这些标志编译解析成代码。编译注解比运行时通过反射实现功能不会改变系统运行性能。依赖注解还可以实现将XML控件和view相绑定,初始数据和view绑定等。初始化路由表等。
2.在项目中新建Android module 提供对Android接口调用的接口,新建Java module实现对注解标志的定义,新建Java module实现Java文件编译时将注解实现成所需功能。Java module不能依赖Android module所以新建三个module包。
3.路由使用
1.对将要跳转的模块写上编译注解的标注,还可以配置是否需要开启新的进程,activity栈等。
2.在需要跳转的地方,通过路由把跳转协议写上进行跳转。
协议的组成:
4.ARouter 的实现分析
1.通过源码我们发现ARouter提供了两个主要SDK,一个是Api SDK 是用在运行时路由跳转等作用的。另一个是Compliler SDK 是用于编译器生成相关类文件的.
2.Compliler SDK由三部分组成,RouterProcessor路由路径处理器,InterceptorProcessor 拦截器处理器,AutowireProcessor自动装配处理器.Compliler SDK 只是处理根据扫描到的注解生成响应的映射(Java)文件。最后一步通过固定包名加载映射文件是由API SDK 来处理的。
下面是阿里ArouterProcessor Java module的代码文件。为啥是Java module呢因为这是处理编译时的工具包。
下面我们来讲解一下RouterProcessor.
@AutoService(Processor.class)
@SupportedOptions({KEY_MODULE_NAME, KEY_GENERATE_DOC_NAME})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends AbstractProcessor {
需要实现里面的 init() 和 process() 两个继承的函数.
/**
* Initializes the processor with the processing environment by
* setting the {@code processingEnv} field to the value of the
* {@code processingEnv} argument. An {@code
* IllegalStateException} will be thrown if this method is called
* more than once on the same object.
*
* @param processingEnv environment to access facilities the tool framework
* provides to the processor
* @throws IllegalStateException if this method is called more than once.
*/
// 初始化处理器
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 文件管理器
mFiler = processingEnv.getFiler(); // Generate class.
// 获取类型处理工具类
types = processingEnv.getTypeUtils(); // Get type utils.
// 获取日志信息工具类
elements = processingEnv.getElementUtils(); // Get class meta.
typeUtils = new TypeUtils(types, elements);
// 封装日志信息类
logger = new Logger(processingEnv.getMessager()); // Package the log utils.
// 获取用户配置的[moduleName]
// Attempt to get user configuration [moduleName]
Map<String, String> options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
moduleName = options.get(KEY_MODULE_NAME);
generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
}
if (StringUtils.isNotEmpty(moduleName)) {
// 格式化
moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
logger.info("The user has configuration the module name, it was [" + moduleName + "]");
} else {
// 如果没有在build.gradle中配置moduleName,则会抛出异常。
logger.error("These no module name, at 'build.gradle', like :\n" +
"android {\n" +
" defaultConfig {\n" +
" ...\n" +
" javaCompileOptions {\n" +
" annotationProcessorOptions {\n" +
" arguments = [AROUTER_MODULE_NAME: project.getName()]\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n");
throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
}
if (generateDoc) {
try {
docWriter = mFiler.createResource(
StandardLocation.SOURCE_OUTPUT,
PACKAGE_OF_GENERATE_DOCS,
"arouter-map-of-" + moduleName + ".json"
).openWriter();
} catch (IOException e) {
logger.error("Create doc writer failed, because " + e.getMessage());
}
}
// RouterProcessor 初始化完毕
iProvider = elements.getTypeElement(Consts.IPROVIDER).asType();
logger.info(">>> RouteProcessor init. <<<");
}
2.process()函数
/**
* {@inheritDoc}
*
* @param annotations
* @param roundEnv
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isNotEmpty(annotations)) {
// 获取所有添加Route注解的元素
Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
try {
logger.info(">>> Found routes, start... <<<");
// 调用arseRoute()函数进行处理获取的注解元素集合
this.parseRoutes(routeElements);
} catch (Exception e) {
logger.error(e);
}
// 如果有Route元素的注解,并且处理过程中无异常则返回true
return true;
}
// 否则返回false
return false;
}
parseRoutes()
1.
5.使用gradle配置实现多模块配置构建
一.使用场景:
1.android 的第三方依赖:
Android对第三方的依赖的配置一般有两种情况第一种直接拷贝到lib目录下然后gradle build的配置文件中引用该库,第二种引用jcenter 中的第三方类库等在gradle中配置。使用拷贝lib文件每次更新需要重新下载lib文件更新同时项目文件过大,使用第二种方式由于不是公司内网所以同步速度不是很有保证。
2.多人多任务的配合
在公司局域网内搭建服务器使用maven仓库。可以将之前的lib文件放入仓库中,Android studio 配置gradle 项目依赖引用maven自动下载依赖。之前每个人都需要从外网上下载第三方依赖的现在统一从公司局域网下载速度有保障。
3.任务的迭代更替
公司的项目一般都是功能变迁迭代的也是多人一起开发,我们可以将项目划分成
lib-router-module
lib-core-module
lib-base-module
app-home-module,app-login-module,app-gesture-module,app-function-module,....等
这样比如登录模块换了一个新的登录模块,通过gradle依赖配置将旧的替换换成新的登录样式。
二.项目实现gradle的配置
1.app-xx 功能模块中编写打包arr和上传maven的gradle脚本。
脚本编写:
在gradle.properties 中配置全局信息
#Maven仓库的URL
NEXUS_MAVEN_URL=http://localhost:8081/repository/maven-releases/
SNAPSHOT_REPOSITORY_URL=http://localhost:8081/repository/maven-releases/
#对应maven的GroupId的值
GROUP = com.dawn.login
#登录nexus ossde的用户名
NEXUS_USERNAME=admin
#登录nexus oss的密码
NEXUS_PASSWORD=admin123
# groupid
GROUP_ID = com.dawn.login
# type
TYPE = aar
# description
DESCRIPTION = app.lib
#版本号
VERSION_NAME=1.0.0
在app-xx,功能Android-module的build.gradle 中编写
apply plugin: 'maven'
uploadArchives {
configuration = configurations.archives
repositories {
mavenDeployer {
repository(url: NEXUS_MAVEN_URL) {
authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
}
snapshotRepository(url: SNAPSHOT_REPOSITORY_URL) {
authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
}
//'groupId:artifactId:version' 或 'groudId:artifactId:version@aar' 形式
pom.project {
name NEXUS_USERNAME
version VERSION_NAME
artifactId DESCRIPTION
groupId GROUP_ID
packaging GROUP_ID
description DESCRIPTION
}
}
}
}
// 生成sources.jar 写 artifacts {} 之前
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
artifacts {
//编译的源码类型
archives androidSourcesJar
//archives androidJavadocsJar
}
点击Android studio 的右侧的gradle图标于是出现。双击点击运行。
结果如下:
2.app-main APP主项目引用maven组件包的gradle的脚本编写。
脚本编写:
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
....
implementation 'com.dawn.login:app.lib:1.0.0@aar'
}
结果如下:
3.app-mian中使用ARouter 实现路由跳转功能
初始化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 开启 debug
DRouter.openDebug();
// 初始化且只能初始化一次,参数必须是 Application
DRouter.getInstance().init(this);
}
}
使用
DRouter.getInstance()
.action("login/action")
.context(this)
.param("key", "value")
.invokeAction();