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集群的搭建》

android sdk方法隐藏_每个Android开发都必须知道的利器相关推荐

  1. android sdk如何瘦身,给Android应用开发者的十条瘦身建议

    1保持良好的编码习惯 随着iOS和Android智能手机.平板电脑的高速发展,移动设备的运行速率越来越快,对应用软件也有了更高的标准.不过对于Android系统来说,开发者们在创建高性能应用的同时,仍 ...

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

    1.背景介绍   在移动端项目功能不断完善和丰富的过程中我们一直在寻找一种可以高效开发且复用率高的开发模式,特别是多应用同步开发.管理.   在开发过程中你是否遇到需要发布影子工程?新建项目是否需要耗 ...

  3. 编译android sdk方法

    自己编译android sdk满足开发需求 $. ./build/envsetup.sh $make sdk -j4

  4. Android SDK包功能介绍,中文开发API

    Android 包索引 这些是API包. 查看全部 API classes . android 包含平台中包含的应用程序使用的资源类,并定义系统功能的应用程序权限. android.accessibi ...

  5. 基于android的简单网页_成都APP开发:APP原生和网页开发有什么区别?

    现在市面上的APP主要分为原生与网页开发两种模式,下面小编就分别针对APP的原生开发和网页开发两者间的优势和劣势介绍一下两者间的区别,希望帮助大家更好的了解这两种开发模式. 原生优势 1)运行速度比较 ...

  6. android ndk 动态库_百度经验,android ndk 动态库

    android ndk开发经常遇到了动态库的问题,本文主要介绍: ① 动态链接库的生成: ② 在Java和C混合编程的情况下如何调用第三方动态链接库: ③ 通过jar包的方式间接调用第三方动态链接库: ...

  7. python sdklive2d_【Android】用Cubism 2制作自己的Live2D——android sdk样本的下载与Android studio编译!...

    前言- 在浏览Live2d说明书的时候我无意中发现了一个有趣的东西,就是android sdk中居然自带动态壁纸!那就让我们来试试吧,说明书此页的网址连接--中文版||日文版 Android开发所必需 ...

  8. 【Android】用Cubism 2制作自己的Live2D——android sdk样本的下载与Android studio编译!...

    前言- 在浏览Live2d说明书的时候我无意中发现了一个有趣的东西,就是android sdk中居然自带动态壁纸!那就让我们来试试吧,说明书此页的网址连接--中文版||日文版 Android开发所必需 ...

  9. python sdklive2d_用Cubism 2制作自己的Live2D(尝试向)——android sdk样本的下载与Android studio编译!...

    前言- 在浏览Live2d说明书的时候我无意中发现了一个有趣的东西,就是android sdk中居然自带动态壁纸!那就让我们来试试吧,说明书此页的网址连接--中文版||日文版 Android开发所必需 ...

最新文章

  1. 低代码可视化报表开源工具,只要在线拖拽就能做出复杂数据报表
  2. Linux下base64命令工具的使用
  3. 【Spring接MySQL数据库的坑】Could not open JDBC Connection for transaction
  4. 无限踩坑系列(6)-mySQL数据库链接错误
  5. 开元弧焊机器人编程_【数据】2019年中国焊接机器人市场发展现状与趋势分析...
  6. nginx ---- 静态资源部署
  7. excel自动保存_做了4个小时的excel未保存怎么办,用这招,快速恢复未保存的数据...
  8. MYSQL 调优和使用必读
  9. jquery实现app开发闹钟功能_商城app开发价格及功能列表
  10. 抖音云控系统领先品牌
  11. Linux下部署worldPress
  12. 估值指标一把手——市盈率
  13. apache 的配置文件hthp.conf里边都是什么意思?
  14. 搭建校园电视台和录播教室+在线教育实施方案
  15. Ubuntu16.04在4K显示器中,字体太小,进行调整
  16. SpringBoot开发微信公众号_回复文本信息功能
  17. python中max什么意思_python中max()语法的说明
  18. STM32的SWD调试方式
  19. 搭建DNS服务器做域名解析
  20. 消费心理学(01):心理账户

热门文章

  1. python 计量经济 35岁 工作_Python在计量经济与统计学中的应用
  2. 同步轨道进入过程_“收官之星”定点成功!北斗三号卫星全部进入长管模式
  3. awk 内嵌正则 提取字符串_使用awk提取字符串中的数字或字母
  4. python爬取b站排行榜_实时爬取B站排行榜并保存为表格——每周一个爬虫小教程系列...
  5. php for 脚本,php for循环脚本。
  6. c语言中文网 vc++6.0下载量_【新手必看】C语言开发环境,请查收!
  7. 韵乐x5效果器ktv最佳参数_家庭ktv(卡拉ok)家庭影院ktv选购推荐攻略
  8. UC浏览器电脑版怎么恢复被关闭的网页
  9. vue使用class添加动态类
  10. android rxjava2 简书,RXJava2学习