内容简介:当一个项目经过N手人开发,N个产品经理的蹂躏,N长时间的维护,此时一定存在大量代码冗余、业务耦合、项目臃肿,资源文件大把重复等等,不堪重负。当需要增加新功能或者修改之前某个功能的时候,我相信很多同仁都说只敢增加,不敢随意的去删除、修改原有的代码,因为不知道哪些有用,哪些没有用。不但增加了维护成本,也在无形中增加了APK的体积,浪费了资源。 在此背景下,就衍生除了模块化、组件化的概念。目前也已经有很多优秀的案例,我就踩在巨人的肩膀上搭建了符合组件业务的组件化框架。其基本理念就是,把常用的功能、控件、基础类、
当一个项目经过N手人开发,N个产品经理的蹂躏,N长时间的维护,此时一定存在大量代码冗余、业务耦合、项目臃肿,资源文件大把重复等等,不堪重负。当需要增加新功能或者修改之前某个功能的时候,我相信很多同仁都说只敢增加,不敢随意的去删除、修改原有的代码,因为不知道哪些有用,哪些没有用。不但增加了维护成本,也在无形中增加了APK的体积,浪费了资源。 在此背景下,就衍生除了模块化、组件化的概念。目前也已经有很多优秀的案例,我就踩在巨人的肩膀上搭建了符合组件业务的组件化框架。
一.浅谈模块
其基本理念就是,把常用的功能、控件、基础类、第三方库、权限等公共部分抽离封装,把业务拆分成N个模块进行独立(module)的管理,而所有的业务组件都依赖于封装的基础库,业务组件之间不做依赖,这样的目的是为了让每个业务模块能单独运行。而在APP层对整个项目的模块进行组装,拼凑成一个完整的APP。借助路由(Arouter)来对各个业务组件之间的跳转,通过消息(eventbus)来做各个业务模块之间的通信。 模块化的好处:
- 1.解耦 只要封装做得好,实际开发中会省去大量的重复代码的coding。
- 2.结构清晰、层次明显,对后面的维护也是极其容易。
- 3.每个业务模块可独立运行,单独提测,节省开发时间。
二.基础搭建
先来一张整个项目构思图
根据项目构思图搭建的项目结构图
下面逐一介绍每个模块的功:
-
app模块
app壳没有任何功能主要就是集成每个业务组件,最终打包成一个完整的APK app壳的gradle
做如下配置,根据配置文件中的isModule
字段来依赖不同的业务组件
... dependencise{ //公用依赖包 implementation project(':common_base') if (!Boolean.valueOf(rootProject.ext.isModule)) { //main模块 implementation project(':module_main') implementation project(':module_market') implementation project(':module_wan_android') } } ... 复制代码
-
common_base模块
功能组件主要负责封装公共部分,如第三方库加载、网络请求、数据存储、自定义控件、各种 工具 类等。为了防止重复依赖问题,所有的第三方库都放在该模块加载,业务模块不在做任何的第三方库依赖,只做common_base库的依赖即可。
common模块无论在什么情况下都是以library
的形式存在,所有的业务组件都必须依赖于common 其结构如下: 在commong的gradle
中引入项目中使用的所有第三方库,业务组件就不用再去逐一引入
apply plugin: 'com.android.library' apply plugin: 'com.jakewharton.butterknife' ... dependencies { // 在项目中的libs中的所有的.jar结尾的文件,都是依赖 implementation fileTree(dir: 'libs', include: ['*.jar']) //把implementation 用api代替,它是对外部公开的, 所有其他的module就不需要添加该依赖 api rootProject.ext.dependencies["appcompat_v7"] api rootProject.ext.dependencies["constraint_layout"] api rootProject.ext.dependencies["cardview-v7"] api rootProject.ext.dependencies["recyclerview-v7"] api rootProject.ext.dependencies["support-v4"] api rootProject.ext.dependencies["design"] api rootProject.ext.dependencies["support_annotations"] //MultiDex分包方法 api rootProject.ext.dependencies["multidex"] //黄油刀 annotationProcessor rootProject.ext.dependencies["butterknife_compiler"] api rootProject.ext.dependencies["butterknife"] //Arouter路由 annotationProcessor rootProject.ext.dependencies["arouter_compiler"] api rootProject.ext.dependencies["arouter_api"] api rootProject.ext.dependencies["arouter_annotation"] //eventbus 发布/订阅事件总线 api rootProject.ext.dependencies["eventbus"] //网络 api rootProject.ext.dependencies["novate"] //日志 api rootProject.ext.dependencies["logger"] //fastJson api rootProject.ext.dependencies["fastjson"] //沉浸栏 api rootProject.ext.dependencies["barlibrary"] //banner api rootProject.ext.dependencies["banner"] //图片加载 api rootProject.ext.dependencies["picasso"] //lombok api rootProject.ext.dependencies["lombok"] api rootProject.ext.dependencies["lombokJavax"] } 复制代码
- 业务组件,在集成模式下它以
library
的形式存在。在组件开发模式下它以application
的形式存在,可以单独独立运行。 业务组件完整的gradle
如下:
if (Boolean.valueOf(rootProject.ext.isModule)) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' } apply plugin: 'com.jakewharton.butterknife' ... dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) //公用依赖包 implementation project(':common_base') //Arouter路由 annotationProcessor rootProject.ext.dependencies["arouter_compiler"] //黄油刀 annotationProcessor rootProject.ext.dependencies["butterknife_compiler"] } 复制代码
- 配置文件,对项目中的第三库、app的版本等配置
/** * 全局统一配置文件 */ ext { //true 每个业务Module可以单独开发 //false 每个业务Module以lib的方式运行 //修改之后需要Sync方可生效 isModule = false //版本号 versions = [ applicationId : "com.wss.amd", //应用ID versionCode : 1, //版本号 versionName : "1.0.0", //版本名称 compileSdkVersion : 27, buildToolsVersion : "27.0.3", minSdkVersion : 17, targetSdkVersion : 23, androidSupportSdkVersion: "27.1.1", constraintLayoutVersion : "1.1.1", runnerVersion : "1.0.1", espressoVersion : "3.0.1", junitVersion : "4.12", annotationsVersion : "24.0.0", multidexVersion : "1.0.2", butterknifeVersion : "8.4.0", arouterApiVersion : "1.4.0", arouterCompilerVersion : "1.2.1", arouterannotationVersion: "1.0.4", eventbusVersion : "3.0.0", novateVersion : "1.5.5", loggerVersion : "2.2.0", fastjsonVersion : "1.1.54", barlibraryVersion : "2.3.0", picassoVersion : "2.71828", bannerVersion : "1.4.10", javaxVersion : "1.2", lombokVersion : "1.16.6", greendaoVersion : "3.2.2", ] dependencies = ["appcompat_v7" : "com.android.support:appcompat-v7:${versions["androidSupportSdkVersion"]}", "constraint_layout" : "com.android.support.constraint:constraint-layout:${versions["constraintLayoutVersion"]}", "runner" : "com.android.support.test:runner:${versions["runnerVersion"]}", "espresso_core" : "com.android.support.test.espresso:espresso-core:${versions["espressoVersion"]}", "junit" : "junit:junit:${versions["junitVersion"]}", "support_annotations" : "com.android.support:support-annotations:${versions["annotationsVersion"]}", "design" : "com.android.support:design:${versions["androidSupportSdkVersion"]}", "support-v4" : "com.android.support:support-v4:${versions["androidSupportSdkVersion"]}", "cardview-v7" : "com.android.support:cardview-v7:${versions["androidSupportSdkVersion"]}", "recyclerview-v7" : "com.android.support:recyclerview-v7:${versions["androidSupportSdkVersion"]}", //方法数超过65535解决方法64K MultiDex分包方法 "multidex" : "com.android.support:multidex:${versions["multidexVersion"]}", //路由 "arouter_api" : "com.alibaba:arouter-api:${versions["arouterApiVersion"]}", "arouter_compiler" : "com.alibaba:arouter-compiler:${versions["arouterCompilerVersion"]}", "arouter_annotation" : "com.alibaba:arouter-annotation:${versions["arouterannotationVersion"]}", //黄油刀 "butterknife_compiler": "com.jakewharton:butterknife-compiler:${versions["butterknifeVersion"]}", "butterknife" : "com.jakewharton:butterknife:${versions["butterknifeVersion"]}", //事件订阅 "eventbus" : "org.greenrobot:eventbus:${versions["eventbusVersion"]}", //网络 "novate" : "com.tamic.novate:novate:${versions["novateVersion"]}", //日志 "logger" : "com.orhanobut:logger:${versions["loggerVersion"]}", //fastJson "fastjson" : "com.alibaba:fastjson:${versions["fastjsonVersion"]}.android", //沉浸式状态栏 "barlibrary" : "com.gyf.barlibrary:barlibrary:${versions["barlibraryVersion"]}", //banner "banner" : "com.youth.banner:banner:${versions["bannerVersion"]}", //图片加载 "picasso" : "com.squareup.picasso:picasso:${versions["picassoVersion"]}", //lombok "lombokJavax" : "javax.annotation:javax.annotation-api:${versions["javaxVersion"]}", "lombok" : "org.projectlombok:lombok:${versions["lombokVersion"]}", //数据库 "greenDao" : "org.greenrobot:greendao:${versions["greendaoVersion"]}", ] } 复制代码
最后别忘记在工程的中 build.gradle
引入该配置文件
apply from: "config.gradle" 复制代码
修改isModule字段之后 需要Sysn才会生效
三.搭建过程中遇到的问题
1. Application
、全局 Context
、 Activity
管理问题
- 在功能组件即Demo中的
common_base
封装BaseApplication
,在BaseApplication
对第三方库初始化、全局Context
的获取等操作。在BaseActivity
中对Activity
进行添加和移除的管理
//BaseApplicion public class BaseApplication extends Application { ... //全局唯一的context private static BaseApplication application; //Activity管理器 private ActivityManage activityManage; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); application = this; //MultiDex分包方法 必须最先初始化 MultiDex.install(this); } public void onCreate() { super.onCreate(); activityManage = new ActivityManage(); initARouter(); initLogger(); } /** * 获取全局唯一上下文 * * @return BaseApplication */ public static BaseApplication getApplication() { return application; } } //BaseActivity public abstract class BaseActivity extends Activity { ... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //加入Activity管理器 BaseApplication.getApplication().getActivityManage().addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); //将Activity从管理器移除 BaseApplication.getApplication().getActivityManage().removeActivityty(this); } } 复制代码
2. AndroidManifest
的管理
我们知道APP在打包的时候最后会把所有的 AndroidManifest
进行合并,所以每个业务组件的 Activity
只需要在各自模块的 AndroidManifest
中注册即可。如果业务组件需要独立运行,则需要单独配置一份 AndroidManifest
,在 gradle
的 sourceSets
根据不同的模式加载不同的 AndroidManifest
文件。
gradle
配置
... android { ... sourceSets { main { if (Boolean.valueOf(rootProject.ext.isModule)) { manifest.srcFile 'src/main/module/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' java { //排除java/debug文件夹下的所有文件 exclude '*module' } } } } } ... 复制代码
注意:在配置Gradle的时候 manifest.srcFile... manifest 是小写的
其中集成模式加载的 Manifest
中不能设置 Application
和程序入口:
//集成模式下Manifest <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.wss.module.wan"> <application> <activity android:name=".main.WanMainActivity" /> </application> </manifest> //组件模式下Manifest <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.wss.module.wan"> <application android:name=".common.WanApplication" android:allowBackup="true" android:label="@string/app_name" android:theme="@style/AdmTheme"> <activity android:name=".main.WanMainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 复制代码
需要注意的是如果在组件开发模式下,组件的 Applicaion
必须继承自 BaseApplicaion
3.不同组件之间的跳转
业务组件之间没有依赖,不能通过常规的 Intent
显示的进行跳转,这个时候就需要引入路由的概念
利用阿里的 ARouter 对需要跳转的页面做配置 gradle
配置
android { ... defaultConfig { ... //Arouter路由配置 javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] includeCompileClasspath = true } } } } dependencies{ ... //Arouter路由 annotationProcessor rootProject.ext.dependencies["arouter_compiler"] } 复制代码
目标页面配置
@Route(path = "/wan/WanMainActivity") public class WanMainActivity extends ActionBarActivity<WanMainPresenter> implements IWanMainView, OnRcyItemClickListener { ... } 复制代码
跳转
... ARouter.getInstance() .build("/wan/WanMainActivity") .navigation(); ... 复制代码
4.不同组件之间通信
可以利用第三方 如 EventBus 对消息进行管理。在 common_base
组件中的 Base
类做了对消息的简单封装,子类只需要重写 regEvent()
返回 true
即可对事件的注册,重写 onEventBus(Object)
即可对事件的接收。
public abstract class BaseActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... if (regEvent()) { EventBus.getDefault().register(this); } } @Override protected void onDestroy() { super.onDestroy(); if (regEvent()) { EventBus.getDefault().unregister(this); } } /** * 子类接收事件 重写该方法 */ @Subscribe(threadMode = ThreadMode.MAIN) public void onEventBus(Object event) { } /** * 需要接收事件 重写该方法 并返回true */ protected boolean regEvent() { return false; } 复制代码
5. butterknife
的问题
在 library
中使用 butterknife
会存在找不到的问题。 推荐使用 8.4.0
版本,用 R2
代替 R
, onClick
中使用 if else
不要使用 switch case
即可解决问题 。
public class HomeFragment extends BaseMvpFragment<HomePresenter> implements IHomeView, OnRcyItemClickListener { @BindView(R2.id.banner) Banner banner; @BindView(R2.id.recycle_view) RecyclerView recyclerView; ... @OnClick({R2.id.tv_title, R2.id.btn_open}) public void onClick(View v) { if (v.getId() == R.id.tv_title) { //do something } else if (v.getId() == R.id.btn_open) { //do something } } } 复制代码
6.资源文件冲突问题
目前没有比较好的约束方式,只能通过设置资源的前缀来防止资源文件冲突,然后在提交代码的时候对代码进行检查是否规范来控制。
最后放上 Demo地址 ,共同学习,有什么不好的地方,欢迎大家指出!
参考文献
移动架构这么多,如何一次搞定所有 戏说移动江湖开发历程 模块化,组件化傻傻分不清?附带组件化福利 寄Android开发Gradle你需要知道的知识 解决组件化开发butterknife 在 library中使用的坑 `
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据结构C++语言描述
William Ford,William Topp / 刘卫东 沈官林 / 清华大学出版社 / 1999-09-01 / 58.00
一起来看看 《数据结构C++语言描述》 这本书的介绍吧!