从结构上来看,APP被划分成5层,每层的功能具体如下:
APP壳工程
这是一个空的项目,其中只包含了一个Application的子类和一个IntentService的子类,主要用来对APP中使用的各种组件进行初始化,IntentService的作用是为了提高APP冷启动的速度,将各种组件的初始化放在后台线程异步执行,这里需要注意的是,对于在Applicaiton或SplashActivity中就会使用的组件,最好直接在Application中进行初始化,否则会抛出未初始化的异常。
业务层
这里的业务层被划分为Main模块和其他模块(至于划分几个模块,根据自己APP的业务,选择合适的粒度进行划分即可)。这里的Main模块主要包括:新用户引导页,启动页,主页。具体业务方面的页面,都放在具体的业务模块中。
公共组件层
公共组件层主要包括APP中使用的第三方组件,这些组件基本都是现在AP
P的通用功能,为上层的业务层提供支持。至于模块划分,虽然这些都是单独的组件,但是每个做为一个Module未免有些繁琐了,所以还是推荐放在一个Module中。在选择第三方的库时,需要做一定的调研,尽量选择大公司,使用用户多的SDK,同时在使用时最好封装一下,这样后面更换时也方便。
基础业务层
基础业务层主要用来统一APP的代码结构,UI风格等,主要包含以下三个方面:
Android组件的二次封装
主要是对Activity/Fragment的封装,提供了不需网络请求的BaseActivity/BaseFragment和需要网络请求的BaseProgressActivity/BaseProgressFragment, 为页面的代码提供统一的结构,页面的样式提供统一的风格。
业务通用UI
主要包含各种样式的Dialog, 自定义View等,根据APP的设计风格提供统一的样式;
图片操作库
图片操作库ImageSet是对图片组件库(包括Fresco, Glide, Universer ImageLoader)的封装,同时提供了调用系统相机/相册选择&裁剪照片,类似微信选择图片的组件,图片上传,图片压缩等功能。这个小模块其实也可放在Common组件层,只是觉得这里面也有一些业务相关的功能,所以就放在了这一层。
当然,基础业务层还还包括APP设计风格中需要用到的各种动画,样式,颜色值,尺寸值等资源。在进行业务开发时,统一使用这些资源,为后续修改整个APP风格提供可能。
Common组件层
这里包含了一些通用的组件,包括各种常用的工具类,通用的UI库,数据源的封装(包括网络,文件,数据库)。这是一个APP的基本架构,里面包含的类基本不需要改动。所以在对工具类和通用UI进行定义时,需要考虑放置的位置是否准确。
以上是整个项目使用组件化/模块化后基本结构的详细介绍,但是在开发过程中还是遇到了很多问题。
遇到的问题
模式切换
从Android工程的结构可以看出,app模块和新建的其他Module的结构基本一致,最大的区别就是build.gradle的结构:
// app模块中build.gralde的结构:
apply plugin: ‘com.android.application’
// 其他Module中build.gralde的结构:
apply plugin: ‘com.android.library’
所以Module是否能够运行,关键就在于plugin的类型。将新建Module的build.gradle中的’com.android.library’ 改成 ‘com.android.application’,同步之后选择相应的模块运行即可。
所以模式的切换只需要根据条件进行判断即可,我们可在gradle.properties中定义一个常量,控制Module的运行模式:
gradle.properties中定义IS_MODULE:
IS_MODULE = false
然后在Module的build.gradle中添加条件判断:
if (IS_MODULE.toBoolean()) {
apply plugin: ‘com.android.library’
} else {
apply plugin: ‘com.android.application’
}
这样在进行模式切换时,只需要修改IS_MODULE的值即可。
AndroidManifest的合并问题
APP在进行打包时,会将所有依赖的Module中的AndroidManifest文件进行合并,具体的合并规则参考合并多个清单文件。合并最基本的原则:只能有一个Application配置了name属性,只能有一个Activity被配置成了主Activity。但是Module中如果不配置Applicaition中name属性,就不能进行相应的初始化,如果不指定主Activity,APP也无法运行。这里可使用两种方案来解决:
- Module依然当做library使用,Module中的AndroidManifest也不需要指定Application的name属性和主Activity,直接加载Main模块(SplashActivity作为主Activity),在SplashActivity中动态修改要加载的模块。
- 在Module中其他路径下新建一个AndroidManifest文件(其中为application标签指定了name属性,同时指定了主Activity),然后在build.gradle中根据IS_MODULE的值动态指定AndroidManifest的路径,这样Module在不同模式下使用不同的AndroidManifest文件就避免了合并出错的问题。但这种方案每个Module需要提供单独的Application类。
module/build.gradle
sourceSets {
main {
if (IS_MODULE.toBoolean()) {
manifest.srcFile ‘…/模块名/src/main/module/AndroidManifest.xml’
} else {
manifest.srcFile ‘…/模块名/src/main/AndroidManifest.xml’
}
}
}
如果选择了双AndroidManifest文件的方式,那么在作为Library的AndroidManifest(默认的)中组件的定义应尽量简单,包括主题都不要定义,这样合并时就很少出现合并错误。
如果在AndroidManifest.xml中定义了元数据,如极光推送:
那么除了在当前Module的build.gradle中定义JPUSH_CHANNEL和JPUSH_APPKEY:
manifestPlaceholders =[
JPUSH_PKGNAME : “com.sxu.library”,
JPUSH_APPKEY : “appkey”,
JPUSH_CHANNEL : “channel”
]
还需要在所有包含此Module的build.gradle中定义这两个常量,否则会出现manifest合并错误。
build.gradle的定义问题
引用Module问题
在引用可切换运行模式的Module时,要根据isModule的值动态添加依赖,如果isModule为true时,引用的模块是以Application的形式存在,此时引入时就会报错。
设置applicationId
applicationId属性在Application模式下才有效,所以在Module中设置时也需要添加isModule的条件判断。
依赖管理
cation的形式存在,此时引入时就会报错。
设置applicationId
applicationId属性在Application模式下才有效,所以在Module中设置时也需要添加isModule的条件判断。