1.背景介绍

  在移动端项目功能不断完善和丰富的过程中我们一直在寻找一种可以高效开发且复用率高的开发模式,特别是多应用同步开发、管理。

  在开发过程中你是否遇到需要发布影子工程?新建项目是否需要耗费大量时间将原有基类、工具类、第三方通用类库逐个 copy 进新项目中?在项目不断迭代后是否发现编译时间不断增加?

  组件化开发可以很好的解决上述问题,它实际是将一个完整的项目划分为若干个模块,过程类似搭积木,一个一个拼接,你可以单独使用其中任意一个,也可以用多个拼接出各种形状,达到高效开发,最大复用。

在了解组件化开发之前我们需要先了解组件和模块这两个概念

  • 组件:指的是单一的功能组件,如地图组件(MapSDK)、支付组件(AnjukePay)、路由组件(Router)等等;

  • 模块:指的是独立的业务模块,如基类模块(common-module)、商城模块(mall-module)、会员模块(member-module)等等;模块相对于组件来说粒度更大。

2.系统架构图

项目由主项目 app,商城模块 mall-module,会员模块 member-module,公共模块 common-module 各个基类(BaseActivity/BaseApplication/BaseAdapter 等)、工具类、网络、图片等一些公共常用的第三方 sdk:

  • app:作为项目的主程序入口,生产环境时可以引入其他模块如本项目的的 common-module、mall-modulem、member-module;开发调试不涉及到跨模块调用业务时可以单独引入 common-module 依赖运行。

  • mall-module:商城模块,编写完整的商城业务逻辑,生产环境作为 library 引入 app 主项目,开发阶段可以 application 方式单独运行,亦可作为商城应用独立发布。

  • member-module:会员模块,编写完整的会员业务逻辑,生产环境作为 library 引入 app 主项目,开发阶段可以 application 方式单独运行,亦可作为会员独立发布。

  • common-module:作为整个项目的基石,所有基类增加修改,均可以在引入项目中生效,达到最大复用率,提高开发效率。

3.实践

mall-module、member-module、common-module 作为 library 被引入 app 中运行效果图:

mall-module 作为应用单独运行效果图:

主项目 app 创建 项目 build.gradle 配置文件:

apply plugin: 'com.alibaba.arouter'

buildscript {

   repositories {

       google()

       jcenter()

   }

   dependencies {

       classpath 'com.android.tools.build:gradle:2.3.3'

       classpath "com.alibaba:arouter-register:1.0.2"

   }

}

allprojects {

   repositories {

       google()

       jcenter { url "http://jcenter.bintray.com/" }

   }

}

task clean(type: Delete) {

   delete rootProject.buildDir

}

项目 gradle.properties 全局配置文件:

#IS_MAIN_APP true主项目为应用 mall-module为library false mall-module为应用可单独启动

IS_MAIN_APP=true

#AS 编译配置

COMPILE_SDK_VERSION=28

BUILDTOOLS_VERSION=28.0.3

MIN_SDK_VERSION=15

TARGET_SDK_VERSION=25

#版本号

APP_VERSION=1

APP_VERSION_CODE=1.0.1

app build.gradle 配置文件:apply plugin 根据 ISMAINAPP 值设置为 application 或 library

if (IS_MAIN_APP.toBoolean()) {

   apply plugin: 'com.android.application'

} else {

   apply plugin: 'com.android.library'

}

android {

   compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)

   buildToolsVersion BUILDTOOLS_VERSION

   defaultConfig {

       if (IS_MAIN_APP.toBoolean()) {

           applicationId "com.fenlibao.arouter"

       }

       minSdkVersion Integer.parseInt(MIN_SDK_VERSION)

       targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)

       versionCode Integer.parseInt(APP_VERSION)

       versionName APP_VERSION_CODE

       testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

       javaCompileOptions {

           annotationProcessorOptions {

               arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]

           }

       }

   }

   buildTypes {

       release {

           minifyEnabled false

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

       }

   }

   sourceSets {

       main {

           if (IS_MAIN_APP.toBoolean()) {

               manifest.srcFile 'src/main/release/AndroidManifest.xml'

           } else {

               manifest.srcFile 'src/main/debug/AndroidManifest.xml'

           }

       }

   }

}

dependencies {

   compile fileTree(dir: 'libs', include: ['*.jar'])

   annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'

   compile project(':common-module')//公共方法 基类 放在该项目中

   if (IS_MAIN_APP.toBoolean()) {

       compile project(':mall-module')//商城模块

       compile project(':member-module')//会员模块

   }

}

创建 mall-module: mall-module build.gradle 配置文件 apply plugin 根据 ISMALLAPP 值设置为 application 或 library

if (IS_MALL_APP.toBoolean()) {

   apply plugin: 'com.android.application'

} else {

   apply plugin: 'com.android.library'

}

android {

   compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)

   buildToolsVersion BUILDTOOLS_VERSION

   defaultConfig {

       if (IS_MALL_APP.toBoolean()) {

           applicationId "com.fenlibao.mall"

       }

       minSdkVersion Integer.parseInt(MIN_SDK_VERSION)

       targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)

       versionCode Integer.parseInt(APP_VERSION)

       versionName APP_VERSION_CODE

       testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

       resourcePrefix "mall_"

       javaCompileOptions {

           annotationProcessorOptions {

               arguments = [AROUTER_MODULE_NAME: project.getName()]

           }

       }

   }

   buildTypes {

       release {

           minifyEnabled false

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

       }

   }

   sourceSets {

       main {

           if (IS_MALL_APP.toBoolean()) {

               manifest.srcFile 'src/main/release/AndroidManifest.xml'

           } else {

               manifest.srcFile 'src/main/debug/AndroidManifest.xml'

           }

       }

   }

}

dependencies {

   compile project(':common-module')

   compile 'com.android.support.constraint:constraint-layout:1.1.3'

   annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'

}

common-module 创建: common-module build.gradle 配置文件 apply plugin 固定为'com.android.library'作为库被其他项目引用

apply plugin: 'com.android.library'

android {

   compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)

   buildToolsVersion BUILDTOOLS_VERSION

   defaultConfig {

       minSdkVersion Integer.parseInt(MIN_SDK_VERSION)

       targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)

       versionCode Integer.parseInt(APP_VERSION)

       versionName APP_VERSION_CODE

       testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

       javaCompileOptions {

           annotationProcessorOptions {

               arguments = [ AROUTER_MODULE_NAME : project.getName() ]

           }

       }

   }

   buildTypes {

       release {

           minifyEnabled false

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

       }

   }

}

dependencies {

   compile fileTree(dir: 'libs', include: ['*.jar'])

   compile 'com.android.support:appcompat-v7:28.0.0'

   compile 'com.android.support:design:28.0.0'

   compile 'com.android.support.constraint:constraint-layout:1.1.3'

   testCompile 'junit:junit:4.12'

   androidTestCompile 'com.android.support.test:runner:1.0.2'

   androidTestCompile('com.android.support.test.espresso:espresso-core:3.0.2', {

       exclude group: 'com.android.support', module: 'support-annotations'

   })

   compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'

//该项目中suppourt包为25.2.0版本比较低 顾做排除处理

   compile('com.alibaba:arouter-api:1.4.1') {

       exclude group: 'com.android.support'

   }

   //Rxjava2

   compile 'io.reactivex.rxjava2:rxjava:2.2.6'

   compile 'io.reactivex.rxjava2:rxandroid:2.1.0'

   //Retrofit2

   compile 'com.squareup.retrofit2:retrofit:2.4.0'

   compile 'com.squareup.retrofit2:converter-gson:2.4.0'

   compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'

   //Okhttp-interceptor

   compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'

}

4.注意事项

多个 module 间 Activity 如何跳转?

由于模块与模块之间是相互独立存在,因而不能使用项目内方式实现 Activity 跳转,下面介绍两种 Activity 跳转方式,日常开发中推荐使用方式二,方式二不管如何修改类名包名都能保持与字符串常量映射关系,维护和使用更方便,更多 ARouter 高级用法请查阅相关资料。

通过反射方式:

try {

   Class clazz = Class.forName("com.fenlibao.member.MainActivity");

   Intent intent = new Intent(_activity, clazz);

   startActivity(intent);

} catch (ClassNotFoundException e) {

   e.printStackTrace();

}

使用 ARouter 注解方式,实现映射关系自动注册:

/**

* ARouter在MyApplication中初始化

*/

public class MyApplication extends BaseApplication {

   @Override

   public void onCreate() {

       super.onCreate();

       init();

   }

   private void init() {

       if (isDebug()) {

           ARouter.openLog();

           ARouter.openDebug();

       }

       ARouter.init(this);

   }

   public boolean isDebug() {

       return getApplicationInfo() != null && (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;

   }

   @Override

   public void onTerminate() {

       super.onTerminate();

       ARouter.getInstance().destroy();

   }

}

/**

* 在常量类中定义静态字符串常量

*/

public static final String MEMBER_1_URL_MAIN = "/member/MainActivity";

/**

* 在对应module Activity中使用@Route标签进行绑定

*/

@Route(path = RoutePath.MALL_1_URL_MAIN)

public class MainActivity extends BaseActivity {

}

/**

* 在需要跳转至该页面build方法中传入对应Activity字符串常量,即可实现跳转

*/

ARouter.getInstance().build(RoutePath.MEMBER_1_URL_MAIN).navigation();

在 module 中如何获取 Application 上下文对象?

在 common-module 中定义 BaseApplication 继承 Application。

**

* 项目Application基类主项Application需集成此类

*/

public class BaseApplication extends Application {

   /**

    * 系统上下文

    */

   private static BaseApplication mAppContext;

   @Override

   public void onCreate() {

       super.onCreate();

       mAppContext = this;

   }

   /**

    * 获取系统上下文单例

    */

   public static BaseApplication getInstance() {

       return mAppContext;

   }

}

在 app 项目中 MyApplication(上部分代码示例中可以查看)继承 BaseApplication,同时设置到 AndroidManifest.xml application 标签下 name 属性中。

在 app 及 module 中都可通过 BaseApplication.getInstance();方法即可获取应用上下文对象,如下:

BaseApplication application = BaseApplication.getInstance();

如何将 module 作为应用单独运行?

在项目 gradle.properties 全局配置文件中找到 ISMAINAPP 配置变量,设置为 ture 时为主项目启动方式,设置为 false,脱离其他 mall-module 作为应用单独启动,设置完成之后重新编译项目即可,开发测试阶段推荐使用该方式,可缩短项目编译时间,项目越大,缩短时间越明显。

##IS_MAIN_APP true主项目为应用,mall-module为library;false mall-module为应用可单独启动

IS_MAIN_APP=true

应用主入口只能有一个,如何切换应用主入口?

根据 ISMAINAPP 设置值加载不同目录下的 AndroidManifest.xml 文件,实现根据参数加载配置文件,下面是 app mall-module 的 build.gradle 配置。

#app build.gradle

android {

   sourceSets {

           main {

               if (IS_MAIN_APP.toBoolean()) {

                   manifest.srcFile 'src/main/release/AndroidManifest.xml'

               } else {

                   manifest.srcFile 'src/main/debug/AndroidManifest.xml'

               }

           }

       }

}

#mall-module build.gradle

android {

   sourceSets {

           main {

               if (!IS_MAIN_APP.toBoolean()) {

                   manifest.srcFile 'src/main/release/AndroidManifest.xml'

               } else {

                   manifest.srcFile 'src/main/debug/AndroidManifest.xml'

               }

           }

       }

}

如何避免资源文件冲突覆盖?

在组件化开发过程中经常会出现资源文件冲突覆盖问题,主项目会覆盖 module 项目资源文件。

在 module 项目中 build.gradle 配置文件中设置:

resourcePrefix "mall_"

  1. 通过给模块设置不同的前缀,避免资源文件重名覆盖问题。

  2. 良好的命名(资源文件/类文件)习惯,需以 module 前缀开头,例如 mall-module 布局资源文件,采用 mallactivity_home,方式避免资源文件重名覆盖问题,如果资源文件不按 resourcePrefix "mall" 定义前缀命名会有错误提示,例如以如下方式命名布局资源文件 activitymallhome.xml:

组件化开发如何利用 Git 管理代码,实现协同开发?

项目中引用的 module 可能是其他组员负责开发,我们只负责调用传递数据,当 module 发生改变时,项目中需要可以获取到最新的代码,本项目中采用 git submodule 方式管理代码,如下图:

1. 增加 git module 项目引用 采用 git submodule add [url][module-name] 命令增加 git 项目引用 url:git 项目完整路径; module-name:为本地项目 module 名称

2. 导入完整 git submodule 项目 第一步初始化:git submodule init;第二步更新项目:git submodule update 引用至本地,如果遇到提示项目目录已存在,将原有项目目录删除再执行更新命令即可。

5.总结

  以上就是组件化开发的详细步骤和相关注意事项,如果你面前是一个庞大的工程,建议你使用该方案,以最小的代价尽快开始实施组件化。如果你现在负责的是一个开发初期的项目,代码量还不大,那么也建议尽快进行组件化的规划,不要给未来的自己增加徒劳的工作量。 项目示例 Github 地址:https://github.com/guixia/Android-Submodule.git。

- END -

更多推荐内容

《kubernetes 单master集群的搭建》

捷易拍sdk开发指南.doc_每个Android开发都必须知道的利器相关推荐

  1. asp.net中有关文件HTTP上传服务器保存的问题(捷易拍sdk开发方面)

    我们都知道,一般客户端文件上传服务器有两种方法:HTTP上传和FTP上传两种.两种上传方式以捷易拍SDK为例: 1.HTTP上传: /********************************* ...

  2. 捷易拍与springMVC系统结合

    1. 捷易拍高拍仪在jsp页面的调用 使用ActiveX插件的方式处理解决此问题,捷易拍公司提供了支持IE8以上的32位浏览器的插件,安装插件后,我们可以使用Object标签,使用高拍仪 注意: 1. ...

  3. 用捷易拍文件拍摄仪搭建数字图书馆

    一. 什么是数字图书馆 数字图书馆就是一个不需要阅览室的图书馆,只要有网络存在的地方,任何人都可以随时随地查阅资料.获取信息. 数字图书馆系统是现代计算机及网络技术与传统图书馆信息检索技术相融合的结晶 ...

  4. 安卓开发指南!分享Android资深架构师的成长之路,面试心得体会

    前言 最近有不少人问我这样一个问题:「我刚接触编程,准备学习下Android开发,但是担心现在市场饱和了,Android开发的前景怎么样?」 想着可能有很多人都有这样的担心,于是就赶紧写篇文章,来跟你 ...

  5. HoloLens开发指南(4)--- 开发第一个HoloLens应用

    经过了前面的环境开发准备,终于我们要使用Unity来开发第一个HoloLens应用. 以下内容由公众号:AIRX社区(国内领先的AI.AR.VR技术学习与交流平台) 整理 Step 1:创建Unity ...

  6. 区块链开发指南_区块链开发权威指南

    区块链开发指南 by Haseeb Qureshi 由Haseeb Qureshi 区块链开发权威指南 (The authoritative guide to blockchain developme ...

  7. 程序员开发指南!半路出家Android程序员看我轻松逆袭!实战篇

    前言 不清楚你是不是知道,咱们中国有相当大的一部分软件公司,他们的软件开发团队都小的可怜,甚至只有1-3个人,连一个项目小组都算不上,而这样的团队却要承担一个软件公司所有的软件开发任务,在软件上线和开 ...

  8. 最新《Android车载系统应用开发指南 》,Android工程师的新赛道

    2019年,中国首个外商独资的整车制造项目,"上海特斯拉超级工厂"开工了.作为世界上最大汽车生产和销售国,特斯拉的热销立马就引发了一场鲶鱼效应,国内外的汽车制造商纷纷开始布局智能化 ...

  9. ESP8266在Alios-Things上的入门开发指南 (一)开发环境搭建及HelloWorld固件

    一.    开发环境搭建 目前国内大多数开发者使用的都是WinXP/Win7/Windows XX做MCU的开发.习惯Windows环境开发的朋友,如果一旦一接触到MCU需要是Linux环境来进行开发 ...

最新文章

  1. 2021新款 iPad,包邮送一个!10月25日截止
  2. 使用netty实现一个类似于微信的聊天功能
  3. cleaning selected projects has encountered a problem errors occurred during build
  4. TCP三次握手,握的是啥?
  5. c+ +三角函数_C ++中的三角函数
  6. React面试题总结,含爱奇艺,小米,腾讯,阿里
  7. 在Brackets中使用jsHint遇到的问题
  8. linux+mysql+导出备份_Linux系统MySQL备份的导入导出的具体分析
  9. luogu1082 [NOIp2012]同余方程 (扩展欧几里得)
  10. AttributeError: module ‘tensorflow‘ has no attribute ‘constant‘
  11. 高可用的分布式Hadoop大数据平台搭建,超详细,附代码。
  12. AD9371开发总结(一)
  13. android ant下载安装,ANT+ Plugins
  14. 关于净推荐值(NPS)的理解
  15. Enable Geolocation in a WebView (Android)
  16. Java入门篇——安装Java SE14
  17. python程序实验教程_20192428 实验一《Python程序设计》实验报告
  18. 商品评价实体情感识别项目
  19. CMA大段设备内存分配
  20. dropzone java实例_Java实现拖拽文件上传dropzone.js的简单使用示例代码

热门文章

  1. pc817光耦参数_光耦在电子电路中有什么作用?关键参数有哪些?一起了解一下...
  2. 10、 HAVING:过滤分组
  3. 1.2.3 OSI参考模型(2)
  4. python设置ini文件中的值_PyCharm设置python文件模板,自动读取文件信息。
  5. python装饰器功能是冒泡排序怎么做_传说中Python最难理解的点|看这完篇就够了(装饰器)...
  6. neo4j 知识图谱_知识图谱里的知识存储:neo4j的介绍和使用
  7. virtio驱动_0020 virtio-blk简易驱动
  8. python opencv 界面按钮_PyAutoGUI:自动化键鼠操作的Python类库
  9. python:dataframe groupby后agg、apply、transfrom用法
  10. 一个项目搞定支付宝,微信支付!