项目架构,从最初的单module代码级分包到如今的基于module的模块化演变;而单module内部,从mvc(不算是)到mvp再到结合google新出的带有生命周期的viewmodel、livedata等构建的mvvm演变。谈一谈自己的安卓项目架构经历。
从事安卓一开始便入得小公司,项目全靠自己搭建,最初一个app模块包含所有业务功能代码。app模块内部结构,分三大块,网络、ui、dao,分包予以区分。网络模块由asynchttpclient完成,统一单文件配置所有接口。ui层,分activity、view,其他基础包如utils、bean等。dao包括数据库创建,增删改查等。基本整体架构如此。后来人数达3-5人,每版功能变动极大,很多大模块功能同时开发。为了便于开发和管理,进行功能级别的分包,每个功能模块内部分ui、dao、网络三部分。
随着rxjava与retrofit网络框架的流行,网络模块代码逐渐向之移植。rxjava处理异步回调,着实顺手;rxbus进行页面间事件的通知也很方便。早先网络异步回调,成功与否更新页面Ui,很容易造成内存泄露,以及当页面关闭时,进行页面相关dialog显示易造成崩溃(dialog找不到依附的页面,因为已调用finish方法)等问题。内存泄露及崩溃,是通过静态类以及弱引用加以处理,写起来很不方便。尤其异步回调嵌套,更是使得代码难以阅读,写起来也费劲,通过rxjava很好的避免这一点。在页面onDestory生命周期中,取消订阅,防止内存泄露;同时基于事件流的异步回调处理,简单方便易读。
mvp(加dagger2),渐渐的流行起来。对项目进行改造,严格来说所谓的mvp就是传统意义上的mvc,只是安卓界把model(网络数据处理、数据库数据处理等)、activity(ui显示加数据加工、逻辑控制)当做mvc,v跟c并未分层。不管什么架构,目的就是为了便于开发和维护(写的多了,会发现好的代码形式、项目结构,确实是大大大大的方便,没有前后对比没有伤害),也不用过于较真概念。mvp,三层,每层抽象出来对外功能接口。view层,model层,presenter层,每层内部有自己的实现类。在代码构建过程中,会逼迫你先把页面功能点过一遍,把对外的api与数据结构提前定义好,层与层之间直接交互而无需关心具体内部实现。不需涉及每个环节的细节点,已经搭建出整体流程。为了解耦和,通过dagger2配置接口与实现类。代码结构一目了然,层层之间互不影响,开发过程流畅而清晰,后期修改维护也很方便,不会造成过多的代码伤害。为防止内存泄露,presenter建议定义成静态类,在初始化时持有view层引用,在view的onDestory生命周期中置引用为空。
做的时间长了,会发现公司会有做多个项目的需求。一些项目,根本就是主项目功能的摘取;还有主项目的部分功能,会由于各种原因进行推翻重做。比较糟糕的事情来了,比如经手的题库模块,好几个项目都有这个功能。如何摘取组合,成了难题。再经历过几次艰辛痛苦的提取后,着手寻找好的解决办法,然后进行了现有项目的模块化改造。新项目需要什么功能组合,可轻松配置出,不用再劳心劳肝,痛不欲生了。
模块化、组件化,一堆的概念。个人感觉,模块化,偏向业务方面的解耦;组件化,偏向功能模块的复用。大体架构,每个library可配置单独运行的工程。基础library提供基础的util等功能,通过不同library的组合依赖配置,最终生成app,如此灵活便于协同开发与管理。安卓module通过配置,可以很方便的进行library与application的转化
1.见下
if (rootProject.ext.xxx) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
2.library与application的清单文件有所不同
application清单文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.xxx">
<application
android:name="com.commlib.application.BaseApplication"
android:allowBackup="true"
android:icon="@mipmap/comlib_ic_launcher"
android:label="@string/ygexam__app_name"
android:largeHeap="true"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:label">
<activity
android:name=".ui.examMain.ExamMainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
library清单文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xxx">
<application>
<activity
android:name=".ui.examMain.ExamMainActivity"
android:screenOrientation="portrait" />
3.library与主工程的资源文件如果有命名重复的情况,主工程会对library进行资源覆盖,为防止出现重复情况,需要进行别名设置。可以在build.gradle文件里配置 :resourcePrefix “xxx_”
4.模块间通信,如接口调用,页面跳转等,借助阿里的arouter实现,github上可见详细使用说明
5.不同模块会有各自的application初始化需求,基础library提供接口抽象,各个模块实现该接口,进行各自的初始化操作,并注册到基础Library中。统一在基础library中,进行调用初始化
open class BaseApplication : Application() {
private var applicationListeners: MutableList<ApplicationListener> = mutableListOf()
override fun onCreate() {
super.onCreate()
Ext.with(this)
Fresco.initialize(Ext.ctx)
//友盟统计
val analyticsConfig = MobclickAgent.UMAnalyticsConfig(this, UMENGKEY, ChannelUtils.getChannel(this))
MobclickAgent.startWithConfigure(analyticsConfig)
if (DEVELOP_MODE) {
ARouter.openDebug()
ARouter.openLog()
}
ARouter.init(Ext.ctx)
ARouter.getInstance().build(xxx_application).navigation()?.let {
applicationListeners.add(it as ApplicationListener)
}
for (index in applicationListeners.indices) {
applicationListeners[index].onCreate()
}
}
```
基础接口
```
interface ApplicationListener : IProvider {
fun onCreate()
}
模块实现类
@Route(path = xxx_application)
class ApplicationDelegate : BaseApplicationListener() {
override fun init(context: Context?) {
}
override fun onCreate() {
}
}
6.跨模块间接口调用,借助arouter,同上类似,基础library中声明模块对外接口,模块内部实现该接口,通过path创建实现类并调用该接口
7.模块化构建中,可与组件化结合,进行代码复用。基础library可进行再细化构造,本人项目目前结构见下图
AroutePath文件按模块进行文件划分,各个文件中有arouter路径的详细配置
moduleService文件是每个模块对外暴露的接口
moduleEvent文件是跨模块通知事件
随着kotlin语言影响的扩大,包括google表态对它的支持,逐渐使用kotlin进行项目的开发。kotlin的扩展函数很强大,当然它的对空处理,lambda等其他特点也很好,扩展函数使用起来尤其方便。比如下面这块代码,如果使用rxjava想必不陌生,这样提取后,简直不要太爽,一个Scheduler()函数搞定一大堆代码。其他好处不一而足。
fun <T> Observable<T>.Scheduler(): Observable<T> =
this.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
随着viewmodel、livedata、room一批新组件的出现,对项目进行再次改造。room是用来数据持久化的工具,方便将对象写入数据库,可以用如greendao等框架替代。viewmodel是跟随页面生命周期的控件,livedata是可观察的数据对象。通过这几个组件,很方便的构建自动管理生命周期的“mvp“架构。整体分dataRepository、viewmodel、activity三层。其中dataRepository,分local、remote两块,local本地数据处理,包括本地文件以及数据库(dao);remote为网络数据请求。viewmodel对dataRepository数据进行调用处理,提供给activity层显示所需格式字段。livedata桥接viewmodel与activity,viewmodel对livedata进行带状态标识的数据赋值,activity根据livedata状态变化与携带数据进行相关显示。viewmodel不要直接持有activity的引用,二者通过livedata交互。下见图
viewmodel层对从repository而来的数据进行统一转换处理,保证viewmodel层与activity层之间的数据格式一致。这很重要,你会发现当来源数据格式极多且差距极大时(比如不同做题模式,服务器返回数据格式极不一致,异步数据可借助rxjava的map、flatmap等操作符),处理成一致字段后,activity层无需任何修改即可适用各种情况。各层内部互不通明,互不影响;层与层间数据格式一致,带来极大的方便