随着移动平台的不断发展,软件慢慢变的愈来愈复杂,业务繁多,体积臃肿;为了下降大型软件复杂性和耦合度,同时也为了适应模块重用、多团队并行开发测试等等需求,Android社区提出了两种解决方案:模块化和插件化。插件化暂且按下不提,本文主要讲述模块化。从基本思路上来说,模块化的实现大致上来说都是差很少的,本文将着重讲述基本思路。此外,在实践的过程当中也有特别的地方:Databinding在模块化中的坑,Dagger2在模块化中的应用,页面统一跳转,模块化通讯方式设计,模块层级架构设计等。这些问题将在本文和后面的系列文章中一一介绍。android
什么是模块化呢?有一种定义是:模块化是一种处理复杂系统分解为更好的可管理模块的方式。因而可知,模块化思路下构成的复杂系统是由各个可管理的子模块构成的,每一个子模块以前相互独立,并经过某种特定的方式进行通讯。 在工业上面,有模块化汽车的概念,也有模块化手机的概念,各个模块根据必定的标准进行生产,生产以后能够直接进行各个模块的组装,某个模块出现问题以后,能够单独对这个模块进行替换。举个例子,一样一款汽车,有各中配置不一样的版本,好比发动机不一样。这些发动机都按照必定的标准生产,可是发送的输出和能耗并不一样。重要的是其接口标准同样。从可替换这一点来说,和软件开发中的可插拔是殊途同归的。git
Android 开发中有两个比较类似的概念:组件化和模块化,这里须要进行区分的。 组件化:指的是单一的功能组件,如地图组件、支付组件、路由组件(Router)等等; 模块化:独立的业务模块,模块相对于组件来说粒度更大。github
模块化的好处是显而易见的。 • 多团队并行开发测试; • 模块间解耦、重用; • 可单独编译打包某一模块,提高开发效率。安全
对于模块化项目,每一个单独的 Business Module 均可以单独编译成 APK。在开发阶段须要单独打包编译,项目发布的时候又须要它做为项目的一个 Module 来总体编译打包。简单的说就是开发时是 Application,发布时是 Library。所以须要在 Business Module 的 build.gradle 中加入以下代码:bash
if(isBuildModule.toBoolean()){
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
复制代码
isBuildModule 在项目根目录的 gradle.properties 中定义:微信
isBuildModule=false网络
一样 Manifest.xml 也须要有两套:架构
sourceSets {
main {
if (isBuildModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
复制代码
debug 模式下的 AndroidManifest.xml :app
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dajiazhongyi.dajia.pedumodule">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application
...
>
<activity
android:name="com.dajiazhongyi.dajia.loginmodule.ui.DaJiaLauncher"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="dajia" />
</intent-filter>
</activity>
<activity
android:name=".ui.MainActivity"
android:screenOrientation="portrait"/>
</application>
</manifest>
复制代码
realease 模式下的 AndroidManifest.xml :框架
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dajiazhongyi.dajia.pedumodule">
<application
android:allowBackup="true"
android:supportsRtl="true">
<activity
android:name="com.dajiazhongyi.dajia.pedumodule.ui.PEducationListActivity"
android:screenOrientation="portrait"/>
<activity
android:name="com.dajiazhongyi.dajia.pedumodule.ui.syslib.SystemEduDetailListActivity"
android:screenOrientation="portrait"/>
<activity
android:name="com.dajiazhongyi.dajia.pedumodule.ui.syslib.SystemEduListActivity"
android:screenOrientation="portrait"/>
</application>
</manifest>
复制代码
合理的模块化分层设计是很是重要的,就像一个房子同样,合理的框架设计是成功的保证。 模块化分层设计须要达到如下几个目标:
根据职责进行分层设计是合理有效的,如下是在项目实践中采用的分层设计。
SDK SDK层包括的内容如图所示,须要强调的是并非全部的第三方Libraries都放到SDK,必须是通用的基础级别的。
组件库 咱们将各个业务模块公用的组件整合到组件库中,组件库并不必定是一个module,它也能够是多个module,实际使用的时候更多的被业务模块依赖。
BaseCore 这是最重要的一个层级,APP核心的部分就是它,BaseCore能够用通用的定义如下几个部分:
CoreAccount: APP帐号管理,帐号登陆、注销、Profile信息获取等; CoreNetwork: 以Retrofit2为例,CoreNetwork并不提供业务模块的API,只是提供基础的网络状态管理、网络错误管理; CoreStorage: 处理SQLite、Preferences; CoreCommunication:模块之间的通讯主要有三种:事件通知、页面跳转(Activity、Service)、接口调用。模块通讯是最重要的层次,后面会重点讲
此外,这个层次是最容易代码越界的层次,随着业务的不断复杂,业务模块中的代码是极有可能下沉到BaseCore的,从而致使Core层代码愈来愈冗余。清晰合理的代码边界规范是重要的。
业务模块 业务模块的拆分粒度须要把控,过小的粒度并非很合理。其中App(Release)是最终发布出去的版本,它是对其余模块1...N 的整合。各个业务模块在debug'阶段,能够独立打包成apk进行调试,在release阶段,则做为APP的module被引用。各个业务模块之间不进行相互调用,它们之间的通讯经过BaseCore层来实现。
合理的代码边界约定能够保证层次的清晰、避免架构变得冗余,虽然无法彻底保证,毕竟按期的重构是没法避免的。
ServiceManager.regist(PluginService.class);
ServiceManager.get(PluginService.class).execute();
复制代码
模块通讯须要解决三大问题:
这里介绍一款页面路由神器:ARouter github.com/alibaba/ARo…
本着能用、够用、好用的原则,这款神器支持如下功能:
其调用方式以下:
1. 添加注解
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
2. 初始化SDK
if (isDebug()) { // 这两行必须写在init以前,不然这些配置在init过程当中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(若是在InstantRun模式下运行,必须开启调试模式!线上版本须要关闭,不然有安全风险)
}
ARouter.init(mApplication); // 尽量早,推荐在Application中初始化
3. 发起路由操做
// 1\. 应用内简单的跳转(经过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2\. 跳转并携带参数
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
复制代码
实际应用中,在BaseCore中实现一个RouterManager,管理路由初始化,跳转等事宜:
public class RouterManager {
/**
* Router Path
*/
public static final String URL_WELCOME = "/loginModule/welcome";
public static final String URL_LOGIN = "/loginModule/login";
public static final String URL_MAIN_LOGIN = "/loginModule/main";
public static final String URL_MAIN_PEDU = "/peduModule/main";
...
/**
* Module application name
*/
public static final String MODULE_LOGIN = "loginmodule";
public static final String MODULE_PEDU = "pedumodule";
public static void initRouter(Application application) {
if (BuildConfig.DEBUG) {
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(若是在InstantRun模式下运行,必须开启调试模式!线上版本须要关闭,不然有安全风险)
}
ARouter.init(application);
}
public static void gotoNewPage(Context context, String pageUrl) {
ARouter.getInstance().build(pageUrl).navigation();
}
public static void goWelcome(Context context) {
ARouter.getInstance().build(URL_WELCOME).navigation();
}
public static void goLogin(Context context) {
ARouter.getInstance().build(URL_LOGIN).navigation();
}
public static void goHome(Context context) {
String packageName = context.getApplicationInfo().packageName;
LogUtils.logD(packageName);
String suffix = packageName.substring(packageName.lastIndexOf(".") + 1);
switch (suffix) {
case MODULE_LOGIN:
ARouter.getInstance().build(URL_MAIN_LOGIN).navigation();
break;
case MODULE_PEDU:
ARouter.getInstance().build(URL_MAIN_PEDU).navigation();
break;
}
}
...
}
复制代码
更多使用方法能够参考github该库的详细介绍
因为篇幅缘由,事件通知、接口调用将在后续文章中介绍!!
对于多个 Bussines Module 中资源名冲突的问题,能够经过在 build.gradle 定义前缀的方式解决:
defaultConfig {
...
resourcePrefix "module_name_"
...
}
复制代码
而对于 Module 中有些资源不想被外部访问的,咱们能够建立 res/values/public.xml,添加到 public.xml 中的 resource 则可被外部访问,未添加的则视为私有:
<resources>
<public name="module1_str" type="string"/>
</resources>
复制代码
更多模块化实践经验,请关注后续文章的推出!!欢迎你们一块儿交流!!
原文地址: http://mp.weixin.qq.com/s/ktbIDjOUzM-gL0uEhowFBw
欢迎一块儿学习和交流
若是你以为此文对您有所帮助,欢迎入群 QQ交流群 :644196190 微信公众号:终端研发部