Android MVVM组件化架构方案

MVVMHabitComponent

关于Android的组件化,相信你们并不陌生,网上谈论组件化的文章,多如过江之鲫,然而一篇基于MVVM模式的组件化方案却不多。结合自身的调研和探索,在此分享一篇基于MVVMHabit框架的一套Android-Databinding组件化开发方案。文章写的比较简单基础,没有大篇幅单向技术的讨论点,目的是让学习了此方案的开发人员均可以快速上手搭建MVVM组件化项目。html

MVVMHabit-Family

原文地址: github.com/goldze/MVVM…前端

总体架构

一、浅谈

1.一、MVVM的优点

想必熟悉前端的朋友对MVVM很是了解,这种模式在目前web前端火的一塌糊涂,好比vue、angular、react都是采用MVVM设计模式实现的前端框架。而在Android开发中,MVVM并非惟一的架构模式,最经常使用的多是MVC模式(一般不是理想的实现)。比较流行的是MVP,它在某种程度上与MVVM模式很是类似。不过MVVM在MVP的基础上更进一步的提升了开发效率,拥有了数据绑定的能力。说到Android MVVM,很天然的联想到谷歌出的Databinding,它提供了xml与java的完美绑定,就像html与js的绑定同样。虽然Android端的MVVM还不是很火,但我相信它是一种趋势。趁着web前端MVVM的热度,移动前端也应该崛起了。vue

1.二、组件化开发

代码是死的,产品是活的。在平常开发中,各类各样频繁变更的需求,给开发上带来了不小的麻烦。为了尽可能把代码写“活”,因此出现了设计模式。但光有设计模式,仍是很难知足产品BT的需求。java

对于简单的小项目,大多都采用的是单一工程,独立开发。因为项目不大,编译速度及维护成本这些也在接受范围以内。而对于作好一个App产品,这种多人合做、单一工程的App架构势必会影响开发效率,增长项目的维护成本。每一个开发者都要熟悉如此之多的代码,将很难进行多人协做开发,并且Android项目在编译代码的时候电脑会很是卡,又由于单一工程下代码耦合严重,每修改一处代码后都要从新编译打包测试,致使很是耗时,最重要的是这样的代码想要作单元测试根本无从下手,因此必需要有更灵活的架构代替过去单一的工程架构。react

使用组件化方案架构,高内聚,低耦合,代码边界清晰,每个组件均可以拆分出来独立运行。全部组件寄托于宿主App,加载分离的各个组件,各自编译本身的模块,有利于多人团队协做开发。android

1.三、MVVM模式 + 组件化

光说理论没用,来点实际的东西,这里要提两个重要的框架。git

  • MVVMHabit:基于谷歌最新AAC架构,MVVM设计模式的一套快速开发库,整合Okhttp+RxJava+Retrofit+Glide等主流模块,知足平常开发需求。使用该框架能够快速开发一个高质量、易维护的Android应用。
  • ARouter:阿里出的一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通讯、解耦。

MVVMHabit + ARouter:MVVM模式 + 组件化方案,前者是设计模式,后者是方案架构,二者并用,相得益彰。有这两个框架做支撑,事半功倍,可快速开发组件化应用。github

二、项目搭建

2.一、建立项目

先把工程中最基本的架子建立好,再一步步将其关联起来web

2.1.一、建立宿主

搭建组件化项目与单一工程项目同样,先经过Android Studio建立一个常规项目。windows

File->New->New Project...

建立的这个项目将其定义为“ 宿主 ”(大多数人都是这种叫法),也能够叫空壳项目。它没有layout,没有activity,它的职责是将分工开发的组件合而为一,打包成一个可用的Apk。

在宿主工程中,主要包含两个东西,一个是AndroidManifest.xml:配置application、启动页面等;另外一个是build.gradle:负责配置构建编译/打包参数,依赖子模块。

2.1.二、建立组件

所谓的组件,其实也就是一个Module,不过这个Module有点特殊,在合并打包的时候它是一个library:apply plugin: ‘com.android.library’,在独立编译运行的时候,它是一个application:apply plugin: ‘com.android.application’

File->New->New Module->Android Library...

通常能够取名为module-xxx(组件名)

2.1.三、建立Library

除了业务组件以外,还须要建立两个基础Library,library-baselibrary-res

  • library-base:存放一些公共方法、公共常量、组件通讯的契约类等。上层被全部组件依赖,下层依赖公共资源库、图片选择库、路由库等通用库,经过它,避免了组件直接依赖各类通用库,承上启下,做为整个组件化的核心库。

  • library-res:为了缓解base库的压力,专门分离出一个公共资源库,被base库所依赖,主要存放与res相关的公共数据,好比图片、style、anim、color等。

2.1.四、第三方框架准备

还须要准备两个第三方的框架,即前面说的 MVVMHabitARouter,可以使用远程依赖。

MVVMHabit

allprojects {
    repositories {
		...
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}
复制代码
dependencies {
    ...
    implementation 'com.github.goldze:MVVMHabit:?'
}
复制代码

ARouter

defaultConfig {
    ...
    javaCompileOptions {
        annotationProcessorOptions {
            arguments = [AROUTER_MODULE_NAME: project.getName()]
        }
    }
}
复制代码
dependencies {
    api 'com.alibaba:arouter-api:?'
    annotationProcessor 'com.alibaba:arouter-compiler:?'
}
复制代码

2.二、组件分离

组件化实际上是一个 分离--组合 的过程,分离是分离产品原型,组合是组合代码模块。拿到需求后,必定不要急着开干,首先将产品原型分离成一个个子原型,分工开发后,将编写完成的子业务模块又打包组合成一个完整的Apk。

最多见的应属这种底部几个tab的设计。

经过组件化,能够按照业务大体将项目拆分为:首页模块工做模块消息模块用户模块,固然还能够再分细一点,好比用户模块再分离一个身份验证模块出来。拆分的越细,复用起来就越方便。

那么在上面2.1.2节建立组件时,则建立如下几个组件Module:module-homemodule-workmodule-msgmodule-usermodule-sign

2.三、组件配置

gradle是组件化的基石,想搭建好组件化项目,gradle知识必定要扎实(Android已经留下了gradle的烙印)。

2.3.一、依赖关系

项目建立好后,须要将他们串联起来,组合在一块儿。依赖关系以下图所示:

宿主依赖业务组件

dependencies {
    //主业务模块
    implementation project(':module-main')
    //身份验证模块
    implementation project(':module-sign')
    //首页模块
    implementation project(':module-home')
    //工做模块
    implementation project(':module-work')
    //消息模块
    implementation project(':module-msg')
    //用户模块
    implementation project(':module-user')
}
复制代码

业务组件依赖library-base

dependencies {
    //组件依赖基础库
    api project(':library-base')
	//按需依赖第三方组件
}
复制代码

library-base依赖公共库

dependencies {
    //support相关库
    api rootProject.ext.support["design"]
    api rootProject.ext.support["appcompat-v7"]
    //library-res
    api project(':library-res')
    //MVVMHabit框架
    api rootProject.ext.dependencies.MVVMHabit
    //ARouter框架
    api rootProject.ext.dependencies["arouter-api"]
    //其余公共库,例如图片选择、分享、推送等
}
复制代码

2.3.二、开启dataBinding

Android MVVM模式离不开DataBinding,每一个组件中都须要开启,包括宿主App

android {
    //开启DataBinding
    dataBinding {
        enabled true
    }
}
复制代码

2.3.三、模式开关

须要一个全局变量来控制当前运行的工程是隔离状态仍是合并状态。在gradle.properties中定义:

isBuildModule=false
复制代码

isBuildModule 为 true 时可使每一个组件独立运行,false 则能够将全部组件集成到宿主 App 中。

2.3.四、debug切换

在组件的build.gradle中动态切换library与application

if (isBuildModule.toBoolean()) {
    //做为独立App应用运行
    apply plugin: 'com.android.application'
} else {
    //做为组件运行
    apply plugin: 'com.android.library'
}
复制代码

当 isBuildModule 为 true 时,它是一个application,拥有本身的包名

android {
    defaultConfig {
        //若是是独立模块,则使用当前组件的包名
        if (isBuildModule.toBoolean()) {
            applicationId 组件的包名
        }
    }
}
复制代码

2.3.五、manifest配置

组件在本身的AndroidManifest.xml各自配置,application标签无需添加属性,也不须要指定activity的intent-filter。当合并打包时,gradle会将每一个组件的AndroidManifest合并到宿主App中。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.goldze.main">
    <application>
        ...
    </application>
</manifest>
复制代码

组件独立运行时,就须要单独的一个AndroidManifest.xml做为调试用。能够在src/main文件夹下建立一个alone/AndroidManifest.xml。配置application标签属性,并指定启动的activity。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.goldze.main">
    <application ... >
        <activity ... >
            <intent-filter>
                ...
            </intent-filter>
        </activity>
    </application>
</manifest>
复制代码

并在build.gradle中配置

android {
    sourceSets {
        main {
            ...
            if (isBuildModule.toBoolean()) {
                //独立运行
                manifest.srcFile 'src/main/alone/AndroidManifest.xml'
            } else {
                //合并到宿主
                manifest.srcFile 'src/main/AndroidManifest.xml'
                resources {
                    //正式版本时,排除alone文件夹下全部调试文件
                    exclude 'src/main/alone/*'
                }
            }
        }
    }
}
复制代码

2.3.六、统一资源

在组件的build.gradle配置统一资源前缀

android {
    //统一资源前缀,规范资源引用
    resourcePrefix "组件名_"
}
复制代码

2.3.七、配置抽取

能够将每一个组件的build.gradle公共部分抽取出一个module.build.gradle

if (isBuildModule.toBoolean()) {
    //做为独立App应用运行
    apply plugin: 'com.android.application'
} else {
    //做为组件运行
    apply plugin: 'com.android.library'
}
android {
    ...
    defaultConfig {
        ...
        //阿里路由框架配置
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
    sourceSets {
        main {
            if (isBuildModule.toBoolean()) {
                //独立运行
                manifest.srcFile 'src/main/alone/AndroidManifest.xml'
            } else {
                //合并到宿主
                manifest.srcFile 'src/main/AndroidManifest.xml'
                resources {
                    //正式版本时,排除alone文件夹下全部调试文件
                    exclude 'src/main/alone/*'
                }
            }
        }
    }
    buildTypes {
        ...
    }
    dataBinding {
        enabled true
    }
}
复制代码

组件中引入module.build.gradle便可

apply from: "../module.build.gradle"
android {
    defaultConfig {
        //若是是独立模块,则使用当前组件的包名
        if (isBuildModule.toBoolean()) {
            applicationId 组件的包名
        }
    }
    //统一资源前缀,规范资源引用
    resourcePrefix "组件名_"
}
dependencies {
    ...
}
复制代码

2.四、完成

运行效果以下:

到此为止,一个最基本的组件化工程搭建完毕。

三、可行性方案

3.一、组件初始化

组件在独立运行时,也就是debug期,有单独的manifest,固然也就能够指定Application类进行初始化。那么当组件进行合并的时,Application只能有一个,而且存在宿主App中,组件该如何进行初始化?

3.1.一、反射

反射是一种解决组件初始化的方法。

在library-base下定义一个 ModuleLifecycleConfig 单例类,主要包含两个公共方法:initModuleAhead(先初始化)、initModuleLow(后初始化)。

为什么这里要定义两个初始化方法?

组件多了,一定会涉及到初始化的前后顺序问题,组件中依赖的第三方库,有些库须要尽早初始化,有些能够稍晚一些。好比ARouter的init方法,官方要求尽量早,那么就能够写在library-base初始化类的onInitAhead中,优先初始化。

@Override
public boolean onInitAhead(Application application) {
    KLog.init(true);
    //初始化阿里路由框架
    if (BuildConfig.DEBUG) {
        ARouter.openLog();     // 打印日志
        ARouter.openDebug();   // 开启调试模式(若是在InstantRun模式下运行,必须开启调试模式!线上版本须要关闭,不然有安全风险)
    }
    ARouter.init(application); // 尽量早,推荐在Application中初始化
    return false;
}
复制代码

再定义一个组件生命周期管理类 ModuleLifecycleReflexs ,在这里注册组件初始化的类名全路径,经过反射动态调用各个组件的初始化方法。

注意:组件中初始化的Module类不能被混淆

3.1.二、初始化接口

定义一个 IModuleInit 接口,动态配置Application,须要初始化的组件实现该接口,统一在宿主app的Application中初始化

public interface IModuleInit {
    //初始化优先的
    boolean onInitAhead(Application application);

    //初始化靠后的
    boolean onInitLow(Application application);
}
复制代码

3.1.三、初始化实现

反射类和接口都有了,那么在各自的组件中建立一个初始化类,实现IModuleInit接口。最后在宿主的Application中调用初始化方法

@Override
public void onCreate() {
    super.onCreate();
    //初始化组件(靠前)
    ModuleLifecycleConfig.getInstance().initModuleAhead(this);
    //....
    //初始化组件(靠后)
    ModuleLifecycleConfig.getInstance().initModuleLow(this);
}
复制代码

最后即实现组件的初始化效果

小优化: 当组件独立运行时,宿主App不会执行onCreate方法,可是组件业务又须要初始化单独调试。常规作法是组件中单独定义Application,但这样每一个组件都须要建立一个Application,比较繁琐。咱们有了上述的初始化方法,能够在 library-base 中定义一个 DebugApplication ,debug包下的代码不参与编译,仅做为独立模块运行时初始化数据。最后记得在组件的调试版alone/AndroidManifest下指定为base中的 DebugApplication

3.二、组件间通讯

组件间是彻底无耦合的存在,可是在实际开发中确定会存在业务交叉的状况,该如何实现无联系的组件间通讯呢?

3.2.一、ARouter

ARouter 之因此做为整个组件化的核心,是由于它拥有强大的路由机制。ARouter在library-base中依赖,全部组件又依赖于library-base,因此它能够看做为组件间通讯的桥梁。

在组件A中跳转到组件B页面:

ARouter.getInstance()
    .build(router_url)
    .withString(key, value)
    .navigation();
复制代码

在组件B页面中接收传过来的参数:

@Autowired(name = key)
String value;
复制代码

更多ARouter用法:github.com/alibaba/ARo…

3.2.二、事件总线(RxBus)

MVVMHabit 中提供了RxBus,可做为全局事件的通讯工具。

当组件B页面须要回传数据给组件A时,能够调用:

_Login _login = new _Login();
RxBus.getDefault().post(_login);
复制代码

在组件A中注册接收(注册在调用以前完成):

subscribe = RxBus.getDefault().toObservable(_Login.class)
    .subscribe(new Consumer<_Login>() {
        @Override
        public void accept(_Login l) throws Exception {
            //登陆成功后从新刷新数据
            initData();
            //解除注册
            RxSubscriptions.remove(subscribe);
        }
    });
RxSubscriptions.add(subscribe);
复制代码

3.三、base规范

library-base 有两个主要做用:一是依赖通用基础jar或第三方框架,二是存放一些公共的静态属性和方法。下面列举一些基础通用类的约定规范。

3.3.一、config

在base的config包下面,统一存放全局的配置文件,好比组件生命周期初始化类:ModuleLifecycleConfig、ModuleLifecycleReflexs,网络ROOT_URL,SD卡文件读写目录等。

3.3.二、contract

RxBus组件通讯,须要通过base层,统一规范。那么能够在contract包下面定义RxBus的契约类,写好注释,便于其余组件开发人员使用。

3.3.三、global

主要存放全局的Key,好比 IntentKeyGlobal: 存放组件间页面跳转传参的Key名称; SPKeyGlobal: 全局SharedPreferences Key 统一存放在这里。单个组件中内部的key能够另外在单独组件中定义。

3.3.四、router

ARouter 路由@Route注解中Path能够单独抽取一个或者两个RouterPath类出来,好比定义一个RouterActivityPath:

public class RouterActivityPath {
    /** * 主业务组件 */
    public static class Main {
        private static final String MAIN = "/main";
        /*主业务界面*/
        public static final String PAGER_MAIN = MAIN +"/Main";
    }
复制代码

Activity的路由路径统一在此类中定义,并使用静态内部类分块定义各个组件中的路径路由。

四、总结

仍是得总结一下。

项目组件化,就比如制造业,生活中的绝大多数工业产品。好比汽车,由发动机、轮子、引擎等各个重要零件拼装而成。一样,咱们的app也是由各个组件并联起来,造成一个完整可执行的软件。它的精髓就是这么3点:独立、完整、自由组合。 并且组件化甚至都不算是人类的发明。即便放在天然界,这也是早已存在的模式。想一想咱们人体多么复杂,绝对不亚于windows操做系统。但除去几个很是重要的数器官以外,大多部分损坏或缺失,咱们都能活下来。这不得不说是组件化的奇迹。

写软件必定要注重架构,不要等到代码越写越烂,越烂越写,最后连本身都看不下去了才想到去重构。组件化是一个很好隔离每一个业务模块的方案,即便其中一个组件出了问题,也不用像单一工程那样总体地去调试。配合MVVM设计模式,使得咱们工做中的具体项目变得更轻、好组装、编译构建更快,不只提升工做效率,同时自我对移动应用开发认知有进一步的提高。组件化框架具备通用性,特别适用于业务模块迭代多,量大的大中型项目,是一个很好的解决方案。

Android架构的演进,由 模块化组件化 再到 插件化。咱们在组件化开发的道路上,尽量的完善组件开发规范,丰富组件功能库,有一些粒度大的业务组件能够进一步的细化,对组件功能进行更单一的内聚,同时基于现有的组件化框架,便于过分在将来打造插件化框架。

QQ交流群:84692105

若是以为这个方案不错的话,麻烦点个 star,你的支持则是我前进的动力!

源码: github.com/goldze/MVVM…

相关文章
相关标签/搜索