-
2021-12-27 22:21:11
1. 总体结构
1. 由于模块之间不能互相依赖引用,所以模块之间的依赖关系图要提前定好
2. APP => ((产品模块 => 产品模块接口),(会员模块 => 会员模块接口), ...) => 基础模块
3. APP是个空壳,*各模块* 生成自己的 *模块接口* , *各模块* 可以调用任何 *模块接口* , *各模块接口* 只能调用 *基础模块*
3. 实际上为了简便,产品模块接口 => 会员模块接口
4. 使用了arouter加载页面Activity或fragment
5. 自动加载接口实现使用了arouter框架,接口基于arouter IProvider类扩展
1. 部分自动加载接口实现失败, 使用了name则加载成功,如$helloService4 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation();$
2. 资源冲突问题通过设置*resourcePrefix "module_product_"* 来解决
3. 在项目的build.gradle中要定义好第三方模块的版本号,在项目的gradle.properties中定义好模块是单独运行还是作为library处理
1. 各模块独立运行时,添加src/main/debug/AndroidManifest.xml文件以及src/main/java/debug 作为运行时的默认参数
4. APP 模块中有Application类,各个模块也有自己的Application类(包含init(Application app)函数),在App Application.onCreate函数中要依次调用各模块的Application.init(this)方法
5. viewmodel放在当前模块的接口层,repository实现放在当前模块层,repository接口放在各模块接口层
6. 基础模块一定要定义好BaseFragment 方便各模块之间通过arouter互相调用对方的fragment ,也要定义好BaseActivity
更多相关内容 -
Android模块化开发项目式教程.ppt
2020-02-26 16:10:23模块8 课表查询 参照本模块的课表查询项目完成课表查询手机客户端的程序设计 项目训练课表查询设计 Android模块 化项目式教程 综合实训-校园生活小助手 模块9 模块9 综合实训-校园生活小助手 学习目标 了解校园生活... -
微信Android模块化架构重构实践.docx
2021-10-13 19:47:01微信Android模块化架构重构实践.docx -
Android模块化编译速度解决方案模块化完整方案.docx
2021-10-26 07:21:14Android模块化编译速度解决方案模块化完整方案.docx -
我所理解的Android模块化(一)——模块化概念和路由
2017-10-25 12:11:08笔者在公司的项目中使用模块化的方式开发APP已经快一年的时间,其中经历过以模块化的方式来重构项目中一些相对来说...遇到了一些问题,也积累了一些经验,所以想谈一谈我对Android模块化的理解,也希望能帮助到大家。此文属于finddreams的原创博客,转载请注明出处:http://blog.csdn.net/finddreams/article/details/78339809
《我所理解的Android模块化(一)——概念和路由》
《我所理解的Android模块化(二)——模块通信和模块间服务调用》
《我所理解的Android模块化(三)——模块可插拔单独编译运行》
《我所理解的Android模块化(四)——常见问题和注意事项》笔者在公司的项目中使用模块化的方式开发APP已经快一年的时间,其中经历过以模块化的方式来重构项目中一些相对来说业务比较独立的模块。遇到了一些问题,也积累了一些经验,所以想谈一谈我对Android模块化的理解,也希望能帮助到大家。
我们都知道APP的体积越大编译一次所花的时间就会越长,这样每次修改编译想看效果要等很久才能看到,这样既没有效率,同时也影响了开发者的心情。而模块化的方式可以把不同模块分离开来,分别编译运行,不仅分工更加明确,耦合性降低,同时降低了编译时间,提高了开发效率。
我们公司APP最后打包发布的时候,apk的体积是40M+,编译出来在Windows系统上需要10分钟左右,在Linux上则需要3分钟。如果以传统的项目结构方式来运行的话,肯定是效率很低的,后来通过插件化方式改造,我们把一些相对独立的其他业务和第三方业务抽离成模块化,在平时的开发过程中,如果没有涉及到这些模块化业务的开发,我们是不会把他们集成进来,只有在最后打包发布的时候才一起集成,这样的做法大大的降低了编译时间(Windows上需要1分钟,Linux 上只需30秒即可),平时开发的时候只集成必要的base模块和正在开发的业务模块,这样开发中的apk的体积就减少了(减少到15M),运行速度也就提升了,然后发布上线的时候就把所有的模块集成进来一起打包发布,明显的提高了开发效率。
说完了笔者使用模块化的背景,再来谈谈我对Android模块化的一些理解,如果理解的很浅显或者狭隘的话,还请各位多多指教;
下面说几个概念:
模块化:指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程。
插件化:和模块化差不多,只是它是可以把模块打包成独立的apk,可以动态的加载,删除,独立的插件apk可以在线下载安装,有利于减少apk的体积和实现模块的热修复。目前热门的插件化方案有:阿里的atlas,360公司的RePlugin,滴滴的VirtualAPK等等;
组件化:组件是指通用的功能或者UI库可以做成一个组件,比如说分享,支付可以当成组件,下拉刷新可以做成组件,模块化项目是根据业务抽离的,而组件化项目则是根据功能UI抽离的,一个模块可以依赖多个组件。组件则不能再依赖其他组件。
模块化之前的项目 模块化之后的项目
模块化的示例项目GIF动图模块化的要解决的问题
- 模块间页面跳转(路由);
- 模块间事件通信;
- 模块间服务调用;
- 模块的独立运行;
- 其他注意事项;
模块的划分
如上面模块化之后的示例项目结构图片,我们来分析一下:
app : 是要最终把所有模块集成在一起打包发布出去的项目:
lib_pay,lib_share : 则是组件库,分别是支付,和分享库;
module_base :base模块,是所有子模块的父类,有各子模块所通用的业务和资源图片,各子模块直接的跳转和方法调用也写在这里;
module_find : 发现模块;
module_gooddetail : 商品详情模块;
module_home : 首页模块;
module_shoppingcart : 购物车模块;
module_user : 用户模块;下面的思维导图,说明了各模块的关系:
这里只是简单的给大家一个如何模块化的参考,模块划分的粒度还是要根据项目的具体情况来定,根据业务的粒度来划分;比如根据上面的示例项目来解说,咱们通常的商城APP包含首页,发现,购物,用户中心等,我们可以把这些按业务划分为独立的模块,分别交给不同模块的负责人开发,这样每个模块负责人只和自己所对应的模块打交道,对这个模块负责。同时又可以做到每个模块是可以独立编译运行的,提高了开发效率。
对于不同的模块的通用部分,我们需要抽离一个 module_base,每个子模块都依赖module_base共享和规范公用的类和资源,防止公用的功能在不同的模块中有多个实现方式,防止通用的资源在不同的模块中有不同的拷贝,降低维护的难度。
路由
项目模块化之后,页面的跳转就不能直接startActivity 调用具体的activity了,因为这个Activity已经在另外一个模块中,直接用.的方式是提示不出来的,这时需要通过借助路由库来实现页面的跳转,当然通过反射的方式也是可以跳转到对应页面的。通过这样的路由跳转,而不是显示引用,就达到了模块之间解耦的目的,在不需要的时候,可以不需要引入这个模块编译,提高开发速度,发布的时候又能很方便的集成进来,功能不受影响,这就实现了模块化的第一步。
路由跳转方式:
1.反射方式:通过反射可以调用集成在同一个APP中的另一个模块中的类或者方法,属性。因为反射是写死的类路径和类名,如果另一个模块中的类名变更的话,会导致反射找不到相对应的类,从而无法实现页面的跳转。2.使用路由框架:这里推荐阿里的ARouter,ARouter这个框架解决了模块化的两大难题,模块跳转和模块间服务调用,笔者也是使用这个库有大半年的时间,并没有发现有什么问题。
ARouter的使用
因为路由跳转是子模块都需要用到的,所以我们在module_base base模块中引入
compile "com.alibaba:arouter-api:$rootProject.arouterVersion"
然后在各子模块的build文件中导入:
annotationProcessor "com.alibaba:arouter-compiler:rootProject.arouterProcessorVersion"
来在编译期间生成路径映射。同时也需要在各子模块的build中加入
defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [moduleName: project.getName()] } } }
需要在主的APP中的Application中初始化ARouter:
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); initRouter(this); } public static void initRouter(Application application) { if (BuildConfig.DEBUG) { ARouter.openLog(); // 打印日志 ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) } ARouter.init(application); } }
ARouter跳转Activity,是先在这个Activity上加入注解:
@Route(path = RouteUtils.User_Activity_Login) public class LoginActivity extends BaseActivity implements View.OnClickListener {
然后跳转逻辑是
public static void startLoginActivity() { ARouter.getInstance().build(User_Activity_Login).navigation(); }
实现这个Activity跳转的路径是
public static final String User_Activity_Login = "/user/login";
这样子的做法的好处就是,只要是path路径不变,任你包名或者类名的变化,都可以成功的跳转到对应的页面,另外还有一个功能就是还能实现web网页跳转到对应的Activity,这样看来比反射还是要容易维护一些。这里需要注意的是路由的path “/user/login”,user代表的是模块名,login则是代表具体的登录页面,路由的path不能重复。
ARouter不止可以跳转Activity还能跳转fragment,所以非常的方便,
@Route(path = RouteUtils.User_Fragment_Main) public class UserMainFragment extends BaseFragment
比如上面演示的项目GIF图片,首页,发现,购物车,用户中心都是Fragment,使用ARouter来获取到这个Fragment,实现四个tab页面的左右切换
public static final String Home_Fragment_Main = "/home/main"; public static final String Find_Fragment_Main = "/find/main"; public static final String User_Fragment_Main = "/user/main"; public static final String ShoppingCart_Fragment_Main = "/shoppingcart/main"; public static Fragment getFindFragment() { Fragment fragment = (Fragment) ARouter.getInstance().build(Find_Fragment_Main).navigation(); return fragment; } public static Fragment getUserFragment() { Fragment fragment = (Fragment) ARouter.getInstance().build(User_Fragment_Main).navigation(); return fragment; } public static Fragment getHomeFragment() { Fragment fragment = (Fragment) ARouter.getInstance().build(Home_Fragment_Main).navigation(); return fragment; } public static Fragment getShoppingCartFragment() { Fragment fragment = (Fragment) ARouter.getInstance().build(ShoppingCart_Fragment_Main).navigation(); return fragment; }
private void switchTab(int checkedId) { FragmentTransaction ft = supportFragmentManager.beginTransaction(); hideAllFragment(ft); if (checkedId == R.id.rb_home) { mHomeFragment = supportFragmentManager.findFragmentByTag(TAG_FRAGMENT_HOME); if (mHomeFragment == null) { mHomeFragment = RouteUtils.getHomeFragment(); if (mHomeFragment != null) { ft.add(R.id.ll_main, mHomeFragment, TAG_FRAGMENT_HOME); } } curFragment = mHomeFragment; } else if (checkedId == R.id.rb_find) { findFragment = supportFragmentManager.findFragmentByTag(TAG_FRAGMENT_FIND); if (findFragment == null) { findFragment = RouteUtils.getFindFragment(); if (findFragment != null) { ft.add(R.id.ll_main, findFragment, TAG_FRAGMENT_FIND); } } curFragment = findFragment; } else if (checkedId == R.id.rb_shoppingcart) { shoppingcartFragment = supportFragmentManager.findFragmentByTag(TAG_FRAGMENT_CART); if (shoppingcartFragment == null) { shoppingcartFragment = RouteUtils.getShoppingCartFragment(); if (shoppingcartFragment != null) { ft.add(R.id.ll_main, shoppingcartFragment, TAG_FRAGMENT_CART); } } curFragment = shoppingcartFragment; } else if (checkedId == R.id.rb_user) { userFragment = supportFragmentManager.findFragmentByTag(TAG_FRAGMENT_USER); if (userFragment == null) { userFragment = RouteUtils.getUserFragment(); if (userFragment != null) { ft.add(R.id.ll_main, userFragment, TAG_FRAGMENT_USER); } } curFragment = userFragment; } if (curFragment != null) { ft.show(curFragment).commit(); } }
因为篇幅的原因,路由的基本用法就讲这些,关于ARouter的更多用法,还请大家去对应的github https://github.com/alibaba/ARouter网站看教程,其他的高级用法包含支持跳转动画,同时也支持startActivityForResult的方式等等。
下一篇中将会给大家讲一讲模块间的通信和跨模块的服务调用。了解更多,请直接看示例代码,示例项目已上传到github,路径:https://github.com/finddreams/AndModulePractice
本文已授权开发者技术前线 独家发布 侵权必究
-
android模块化app开发笔记环境搭建.doc
2020-08-18 22:32:15android模块化app开发笔记环境搭建 PAGE PAGE 1 作者 日期 PAGE / NUMPAGES 由于项目做的越来越大业务上就产生了要将app模块化的需求所谓模块化就是将一个app分成不同功能的小模块插件,当安装程序的时候并不需要将... -
Android 模块化完整方案实现
2017-12-16 22:48:06因为公司业务太多,代码越来越臃肿,越来越难维护,为了提升开发效率,减低代码的维护成本,所以采取了模块化开发方案。 既然是模块化开发,必然要考虑到各个module的开发,调试,迭代拓展及维护,module之间不仅...转载请标明原文地址: http://blog.csdn.net/yalinfendou/article/details/78822749
Android 模块化完整方案实现
Github:Android-Router1 模块化实现(module+router)
本套模块化方案实现来源于公司的业务需求,因为公司业务太多,代码越来越臃肿,越来越难维护,为了提升开发效率,减低代码的维护成本,所以采取了模块化开发方案。
既然是模块化开发,必然要考虑到各个module的开发,调试,迭代拓展及维护,module之间不仅需要做到业务代码隔离,还需要方便的跳转(路由引导模块),方便的传递数据(包括大容量的数据),能够独立编译调。最后,各个module,完成之后,一起打包到主APP即可。2 本套模块化方案实现特点
- 支持module单独作为Application编译调试
- 支持module在debug和release状态下对Application的调用方法完全一致,
- 支持动态注入路由
- 支持注解方式注入路由
- 支持module之间传大容量的数据
- 路由引导模块:自动生成module之间的跳转调用方法
- moduleEventBus:实现module之间通信
3 项目代码主体架构设计
-
app: 一个空壳,本身不实现任何业务逻辑,最终打包成完整的release APK
-
moduleshop:实现shop相关的业务逻辑,可单独编译成APK
-
moduleuser:实现user相关的业务逻辑,可单独编译成APK,和其它module通过router通信
-
routerguidercore:为各个module生成自动调用的方法
-
moduleEventBus:实现module之间通信
4 代码实现方案
4.1 module的apllication实现
我们希望实现以下功能
- module能单独作为Application编译
- module有自己的Apllication,并在里面初始化一些第三方SDK服务
- module在debug和release状态下,业务层代码对application方法调用完全一样
- module在release状态下,能够调用主App的application
4.1.1 首先gradle配置如下配置
def isDebug = rootProject.ext.isDebugType if (isDebug) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' }
这样,在开发时,是一个application,在发布时,是一个library。处于debug状态时,通过 ./gradlew :moduleuser:assemble(mac)命令即可编译打包。
4.1.2 在modulebase中创建ApplicationService
public interface ApplicationService { void loadModuleApplicationService(); //初始化一些第三方SDK服务 Application getApplication(); //获取主APP的Application或者module在debug时自己的application }
ApplicationService放在moduleBase里面, 无论是主App的Application还是module的application,都要实现ApplicationService接口。
4.1.3 在moduleshop中创建ShopDebugApplication,ShopReleaseApplication和ShopApplication,如图所示:
public class ShopDebugApplication extends Application implements ApplicationService { ... } public class ShopReleaseApplication implements ApplicationService { ... }
为什么需要创建三个Application? 其实代码真正调用的是 ShopApplication,在ShopApplication里面,根据debug或者release状态,去调用ShopDebugApplication或者ShopReleaseApplication的方法,这样就能保证业务层代码对application方法调用完全一致。代码如下:
@Override public void loadModuleApplicationService() { if (BuildConfig.IS_DEBUG_TYPE) { ShopDebugApplication.getInstance().loadModuleApplicationService(); } else { ShopReleaseApplication.getInstance().loadModuleApplicationService(); } } @Override public Application getApplication() { if (BuildConfig.IS_DEBUG_TYPE) { return ShopDebugApplication.getInstance().getApplication(); } else { return ShopReleaseApplication.getInstance().getApplication(); } }
ShopDebugApplication是debug调试状态下的Application,ShopReleaseApplication是发布状态的Application。在loadModuleApplicationService方法中,可以初始化一些第三方SDK。 ShopDebugApplication的getApplication() 返回自身实例。 ShopReleaseApplication的getApplication()则通过反射拿到主App的Applicattion。
4.1.4 因为Application需要在library和application之间切换,所以需要配置两套AndroidManifest.xml
gradle配置如下:
if (isDebug) { manifest.srcFile 'src/main/debug/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' }
4.2 module之间跳转
4.2.1 router实现
通常来讲,module之间可以URL Scheme隐式intent实现跳转,不过这种方式扩展性太差,网上也有很多开源的router框架,比如ARouter,考虑到ARouter相对来说比较重,并且项目需要添加一些更加灵活的功能,比如路由引导模块,大容量数据传递之类的功能,所以就自己实现了一个router模块。
router实现原理主要是通过拿到需要启动Activity的信息,组装intent再跳转。这里有两种实现方式,编译时注解自动生成路由表和动态注册路由表实现。
1. 动态态注册路由表实现
主要是通过注册activity路由表,启动时,通过路由表拿到待启动的activity的class,组装一个intent,实现跳转
A. 首先在Application或者启动的activity中注册
Router.registerRouters(new Router.RouterTable() { @Override public List<Rule> buildRuleList() { List<Rule> ruleList = new ArrayList<>(); ruleList.add(new Rule("user", "main", com.joybar.moduleuser.MainActivity .class)); ruleList.add(new Rule("shop", "main", com.joybar.moduleshop.MainActivity .class)); } });
B. 简单的启动如下
Router.create() .buildRule(new Rule("shop", "main")) .navigate(context);
2. 编译时注解路由实现
- 首先自定义编译时注解:@RegisterRouter
-
定义注解解释器:编译器会在编译时检查AbstractProcessor的子类,通过创建RouterProcessor并继承AbstractProcessor,在process方法中,主要做两件事:
- 1.得到@RegisterRouter注解,并解析注解并获取需要的module和path
- 2.使用JavaFileObject类生成java代码
主要代码如下:
String module =typeElement.getAnnotation(RegisterRouter.class).module(); String path = typeElement.getAnnotation(RegisterRouter.class).path(); String fullName = typeElement.getQualifiedName().toString(); MethodSpec addRouter = computeAddRouter(Config.ROUTER_MANAGER_METHOD_NAME,module, path, typeElement.getQualifiedName().toString()); TypeSpec routerManger = TypeSpec.classBuilder(fullName.replace(".", "_") + Config.ROUTER_MANAGER_CLASS_NAME_SUFFIX) .addModifiers(Modifier.PUBLIC) .addMethod(main) .addMethod(addRouter) .build(); JavaFile javaFile = JavaFile.builder(Config.ROUTER_MANAGER_PKN, routerManger) .build(); javaFile.writeTo(mFiler);
3. 路由引导模块实现
这是一个需求性很强烈的功能,主要是方便module之间的调用。比如说同事A定义了一个module A,里面又定义了很多Activity,每个Activty启动时传入的参数又不一样,同事B调用module A时的某个Activity时,在缺乏完整详细文档的情况下,完全无从下手,为了方便module之间的跳转调用,所以实现了一个路由引导模块,能够自动生成module A所有的对其它module公开的调用方法,使用举例如下:
在moduleshop的ReceiveParamActivity中定义了以下两个带有不同参数的启动方法,并声明注解 @RegisterLaunch
@RegisterLaunch public static void launch(Context context,String address){ ... }
@RegisterLaunch public static void launch(Context context,String name,int id){ ... context.startActivity(intent); }
声明注解 @RegisterLaunch后,在Builder#main中,执行
public class Builder { public static void main(String[] args) { CodeMaker.autoGenerateModuleMethodName("moduleshop"); } }
在指定的路径下会自动生成以下RouterTable$$Moduleshop类,这个类中,有moduleshop所有的调用方法
public final class RouterTable$$Moduleshop { public static RouterGuider launchReceiveParam(String address) { // This class was generated automatically 2017-12-11 20:06:21 // module=shop,path=receive_param RouterGuider routerGuider = new RouterGuider("shop", "receive_param"); routerGuider.withString("address", address); return routerGuider; } public static RouterGuider launchReceiveParam(String name, Integer id) { // This class was generated automatically 2017-12-11 20:06:21 // module=shop,path=receive_param RouterGuider routerGuider = new RouterGuider("shop", "receive_param"); routerGuider.withString("name", name); routerGuider.withInt("id", id); return routerGuider; } }
这样,其它module的开发同事就能直接调用RouterTable$$Moduleshop.launchReceiveParam("obo", 25).navigate(context)或者launchReceiveParam("杭州"),就能启动moduleshop的ReceiveParamActivity并传入对应参数了。
自动生成路由引导模块的原理也很简单,根据注解@RegisterRouter拿到Activity 类相应的信息,通过 @RegisterLaunch拿到相关的启动参数信息,再通过javapoet,在指定路径下生成相关的类和方法,具体实现这里就不赘述了,详见源码4. module之间大容量数据传递
Activity之间传递数据通常是使用Bundle,但是Bundle传递数据时是有大小限制的。主要是通过注解@DataParam,通过解析注解将大容量对象保存到一个静态map集合中,然后通过反射把大容量对象传入其它module
5. module之间通讯
在同一进程间通信,EventBus无意非常流行,因为嫌弃EventBus太重,所以就实现了一个简单的moduleEventbus,其实现原理完全照搬EventBus,其使用方法和EventBus也完全一致,分为 定义事件类型,订阅,发布事件,订阅事件处理,解除订阅几个步骤,不过核心代码精简到100多行,具体实现详见代码
4.3 资源名冲突
通过设置resourcePrefix即可解决
if (!isDebug) { resourcePrefix 'user_' }
最终的代码结构图:
各个module完成之后,正常打包即可发布。值得一提的是,module之间的拆分颗粒度不可过细或者过粗。过细会导致module太多,过粗则违背了模块化开发的初衷。所以,在一开始拆分module时,一定要先理清项目的业务,对于一些公用的业务模块,可以放到业务基础组件中,module拆分也不可能一蹴而就,需要随着业务需求不断的优化调整。源码地址:
-
Android模块化和组件化开发
2019-07-24 08:59:301.1:模块化简介 1.2:模块化和组件化的区别 1.3:模块化的优点 1.4:模块化的层级介绍 二.如何实现组件化 2.1:实现模块化需要解决的问题 2.2:各个问题的解决方法 一:模块化介绍 (1)对于简单的小项目...目录
一.模块化介绍
1.1:模块化简介
1.2:模块化和组件化的区别
1.3:模块化的优点
1.4:模块化的层级介绍
二.如何实现组件化
2.1:实现模块化需要解决的问题
2.2:各个问题的解决方法
一:模块化介绍
(1)对于简单的小项目,大多都采用的是单一工程,独立开发。由于项目不大,编译速度及维护成本这些也在接受范围之内。而对于一个大型App产品,多人合作、单一工程的App架构势必会影响开发效率,增加项目的维护成本。每个开发者都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时。所以必须要有更灵活的架构代替过去单一的工程架构。
(2)模块和组件化的区别:
模块指的是独立的业务模块。
组件指的是单一的功能组件。如视频组件、支付组件。每个组件都可以以一个单独的module开发,并且可以单独抽取出来作为SDK对外发布使用。
模块和组件最明显的区别是模块相对组件来说粒度更大。一个模块中可能包含多个组件。在划分的时候,模块化是业务导向,组件化是功能导向。
模块化和组件化,我的拙见,并不需要严格意义上的区分。只要适合自己的就好。所以本文中,只是介绍思想,并不是严格意义上的模块化或组件化开发,抛砖引玉。
(3)模块化的优点:
使用模块化架构,业务模块分离,高内聚,低耦合,代码边界清晰,每一个模块业务都可以拆分出来独立运行。所有模块寄托于宿主App,加载分离的各个组件,各自编译自己的模块,有利于多人团队协作开发。我们只需要关注如何做好隔离,如何更好地计,以及提高开发效率与产品体验,还有代码质量。
(4)模块化层级介绍:
基础层BaseModule:基础层包含的是一些常用的基础库以及对基础库的封装和工具类的封装,比如常用的图片加载、网络请求、数据库、数据存储等操作。其他模块或组件都可以引用同一套基础库。
业务层ProviderModule:基础层往上封装了一层跟项目相关的业务功能层,一些与项目有关的常量类、以项目为基础的工具类。
应用层:业务功能层往上就是应用层,应用层可以是多个业务模块,最后将多个业务模块统筹输出为APP。
二:如何实现模块化:
2.1:模块化开发需要解决的问题
1.每一个组件或模块都是一个完整的整体,所以模块化开发过程中要满足单独运行及调试的要求。这样可以提升开发过程中项目的编译速度,以此来提高开发效率。
2.模块之间数据的传递和方法的相互调用。
3.模块之间的界面跳转:在模块化开发过程中如何在不相互依赖的情况下实现互相跳转。
4.模块化开发完成后相互之间的集成调试如何实现呢?
2.2:question one:组件的单独调试和集成调试
1.动态配置组件的工程类型
目的是:让我们的组件既可以单独调试也可以被其他模块依赖。
我们在 gradle.properties,添加一个Boolen变量, isUserModule=false。
if (isUserModule.toBoolean()) { apply plugin: 'com.android.library'//配置一个library工程,构建后输出aar包。 } else { apply plugin: 'com.android.application'//配置一个Android App工程,项目构建后输出一个APK的安装包 }
2.动态配置组件的 ApplicationId 和 AndroidManifest 文件
一个APP应该只有一个ApplicationId 和AndroidManifest 文件。我们在build.gradle文件中添加代码进行动态配置。
//动态设置ApplicationId if (!isUserModule.toBoolean()) {//如果是模块,则设置包名 applicationId "com.example.user" } //动态设置清单文件,需要建立两个清单文件。一个是单独运行的,另外一个是作为依赖时候使用的 sourceSets { main { if (isUserModule.toBoolean()) { manifest.srcFile 'src/main/AndroidManifest.xml' } else { manifest.srcFile 'src/main/debug/AndroidManifest.xml' } } }
2.3:question two:组件间的数据传递和方法相互调用
因为组件和组件之间是没有相互依赖的,所以不可以直接使用类的方式进行相互调用来实现数据传递和方法调用。
数据传递我们可以使用:EventBus、广播、数据持久化、接口+实现等方式来解决。比如判断登录状态,获取登录的用户token等。
本文咱们说一下接口+实现的方式:我们以获取登录状态为例
(1):我们在业务模块ProviderModule创建service包,里面放置我们定义的接口和实现类。
(2):创建一个用户模块的接口,IAccountService。里面定义获取登录状态和用户id的方法。
(3):定义一个ServiceFactory,对外提供设置和获取IAccountService的具体实现类。
(4):在用户模块创建AccountService实现IAccountService接口,然后在Application中初始化ServiceFactory中的service。
(5):在其他模块就可以通过ServiceFactory获取实现类,调用实现类的方法了。
一: /** * author : Naruto * date : 2019-09-07 * desc : 用户模块的对外提供的方法 * version: */ interface IAccountService { /** * 是否登录 */ fun isLogin(): Boolean /** * 获取用户的AccountId */ fun getAccountId(): String /** * 获取用户中心Fragment */ fun getUserFragment(activity: Activity): BaseFragment? } 二:在用户模块,实现IAccountService。 /** * author : Naruto * date : 2019-09-07 * desc : IAccountService实现类。暴露用户模块对外提供的方法。解耦,外部只需要调用就可以了,不需要知道内部实现的细节。 * version: */ class AccountService : IAccountService { override fun isLogin(): Boolean { return true } override fun getAccountId(): String { return "这是来自usercenter的召唤" } override fun getUserFragment(activity: Activity): BaseFragment? { return UserFragment() } } 三:ServiceFactory /** * author : Naruto * date : 2019-09-07 * desc : ServiceFactory,对外提供设置和获取IAccountService的具体实现类 * version: */ class ServiceFactory private constructor() { companion object { val instance: ServiceFactory by lazy { ServiceFactory() } } private var accountService: IAccountService? = null public fun setAccountService(service: IAccountService) { this.accountService = service } public fun getAccountService(): IAccountService { if (accountService == null) { accountService = EmptyAccountService() } return this.accountService!! } } 四: /** * author : Naruto * date : 2019-09-07 * desc : * version: */ class UserCenterApplication : BaseModuleApplication() { private val TAG = "UserCenterApplication" override fun initModuleApplication() { ServiceFactory.instance.setAccountService(AccountService()) Log.e(TAG, "UserCenterApplication-initModuleApplication") } override fun initModuleData() { Log.e(TAG, "UserCenterApplication-initModuleData") } } 五:使用 toast(ServiceFactory.instance.getAccountService().getAccountId())
这样引申出一个问题:Application的动态配置
当用户模块单独调试的时候,UserApplication需要单独初始化。当模块集中调试的时候,APP的的Application就需要对所有的模块application进行初始化。what should i do?
我们可以采用反射的方式来实现模块的Application初始化。
(1):在Base模块,定义BaseModuleApplication,里面定义了两个方法,initModeApp 是初始化当前模块时需要调用的方法,initModuleData 是所有模块的都初始化后再调用的方法。 /** * author : Naruto * date : 2019-09-07 * desc : * version: */ abstract class BaseModuleApplication : BaseApplication() { override fun onCreate() { super.onCreate() initModuleApplication() initModuleData() } /** * 初始化模块的Application */ public abstract fun initModuleApplication() /** * 所有组件的都初始化后再调用的方法 */ public abstract fun initModuleData() } (2):所有的Application都继承BaseModuleApplication,然后实现方法。 /** * author : Naruto * date : 2019-09-07 * desc : * version: */ class UserCenterApplication : BaseModuleApplication() { private val TAG = "UserCenterApplication" override fun initModuleApplication() { ServiceFactory.instance.setAccountService(AccountService()) Log.e(TAG, "UserCenterApplication-initModuleApplication") } override fun initModuleData() { Log.e(TAG, "UserCenterApplication-initModuleData") } } (3):在Base模块中,定义ApplicationConfig,定义一个数组,我们把需要初始化的模块application的完整类名放到数组中。 class ApplicationConfig { companion object { private const val UserCenterApplication: String = "com.zx.user.UserCenterApplication" private const val MsgApplication: String = "com.message.MsgApplication" val moduleApps = listOf(UserCenterApplication,MsgApplication) } } (4):主Application的initModuleApplication,遍历数组,通过反射的方式初始化各个application。 /** * author : Naruto * date : 2019-09-07 * desc : * version: */ class MainApplication : BaseModuleApplication() { override fun initModuleApplication() { try { val moduleApps = ApplicationConfig.moduleApps for (moduleApp in moduleApps) { val clazz = Class.forName(moduleApp) val instance = clazz.newInstance() as BaseModuleApplication instance.initModuleApplication() } } catch (e: Exception) { e.printStackTrace() } } override fun initModuleData() { try { val moduleApps = ApplicationConfig.moduleApps for (moduleApp in moduleApps) { val clazz = Class.forName(moduleApp) val instance = clazz.newInstance() as BaseModuleApplication instance.initModuleData() } } catch (e: Exception) { e.printStackTrace() } } }
2.4:question three:组件之间的页面跳转
这里采用阿里开源的ARouter。ARouter是阿里巴巴开源路由框架,主要解决组件间、模块间的界面跳转问题。
https://blog.csdn.net/weixin_37292229/article/details/93375669
2.5:集成调试
在主模块的build.gradle中配置简短的代码就ok了。
if (isUserModule.toBoolean()) {
implementation project(':UserCenter')
}
本文中的模块化开发,告一段落。
Finally, I think a meaningful life is to insist on doing something embarrassing.
Come on (ง • ̀ _ •) ง Naruto
-
Android 模块化开发
2020-01-03 15:00:15如通用的模块,自动更新的模块,反馈模块,推送模块都可以单独以模块来开发,最后进行集成。我们可以通过一个壳来包含很多个模块。 好处 可以单独升级模块。耦合度低。同时,也很好地解决了“牵一发而动全身”的... -
Android 模块化之 模块间通信 完整解决之道
2019-06-12 08:12:37作者:潘辰星来源:Android那些事一、背景Android 开发,从最初的一个人团队,我的地盘我做主,随着团队和业务逐渐变大,单App开发慢慢跟不上业务发展步伐。代码复... -
我所理解的Android模块化(二)——模块通信和模块间服务调用
2017-10-26 09:23:48上一篇《我所理解的Android模块化(一)》笔者讲到了Android模块化的基本知识和模块化跳转路由的基本用法,解决了模块化中跳转的问题,下面就来讲讲如何实现模块化之间的通信和跨模块方法调用。 有这样一个场景,... -
关于Android模块化开发介绍及使用
2019-02-26 11:27:03模块化浅谈: 1、安卓模块化开发介绍: 模块化开发思路就是:单独开发每个模块,用集成的方式把他们组合起来,就能拼出一个app。app可以理解成很多功能模块的组合,而且有些功能模块是通用的,必备的,像自动更新... -
我所理解的Android模块化(四)——常见问题和注意事项
2017-10-26 17:00:33虽然模块化没有热更新那么多兼容性问题,那么多坑,因为模块化完全是利用AndroidStudio自带的gradle的方式编译来实现多模块,所以不会存在或者很少存在兼容性问题。但是项目的模块化过程也并不轻松,特别是传统的... -
Android 模块化项目不同模块防止资源重复解决方法
2020-07-31 09:09:33由于项目模块化后,每个模块之间是相互独立的,在合并打包生成apk过程中,如果资源名称相同,会造成相互覆盖,导致造成资源引用出现错误; 例: 模块A中有一张ic_photo,模块B中由于业务关系也有一张ic_photo。 ... -
Android模块化与组件化--多模块区分编译
2016-11-26 18:00:23模块化与组件化–多模块区分编译 示例地址:https://github.com/JackyAndroid/Android-Architecture-Fairy/tree/master/multi-variants-library Android-Architecture-Fairy开源项目重点分析主流技术与架构设计,... -
Android模块化,组件化和插件化区别
2021-10-29 20:05:43(1)小项目:模块化 模块化是将功能拆分,分成相互独立的模块,以便于每个模块只包含与其自身功能相关的内容。 从代码逻辑的角度进行划分,方便代码分层开发,保证每个功能模块的职能单一。 模块相应于业务逻辑模块... -
美团猫眼android模块化实战-可能是最详细的模块化实战
2017-09-12 18:51:23写这篇博客的初衷首先一句话概括:我想把这几个月做的事情记录下来,并且希望尽量详细,希望读者读了这篇文章能够知道模块化具体每个步骤都做什么,而不是大致的了解。 现在很多人都在谈模块化,网上有一大堆的... -
安卓 项目 mvp模块化搭建,架构
2018-12-28 14:20:50首页模块化搭建,架构,网络访问封装,base类封装 mvp可用可不用,rxjava封装已好 BaseMvpActivity/BaseFragment BaseActivity 路由之间模块跳转(组件件跳转及数据传递) 日夜间切换 -
Android模块化架构下,子模块自加载方案
2021-12-15 18:35:28Android模块化架构下,子模块自加载方案! 背景 在 Android 模块化架构中后,子Module 间相互解耦,作为独立的模块运行。如果 子Module 也需要进初始化的操作,那么该如何做呢?可能你会说,直接在 壳App ... -
Android-模块化通信-简单实用的android spi机制
2022-03-29 20:01:38为了实现Android-模块化通信-需要一套简单实用的android spi机制。 一、spi是什么? SPI即 Service Provider Interface,它是为接口提供寻找实现服务类,类似IOC的思想。 二、ServiceLoader java中的spi, ServiceLo -
Android模块化组件化方案分享(1)
2019-01-27 22:28:33文章目录为什么模块化如何模块化1:整体项目大致结构2:如何设置模块模式,集成模式调试和运行gradle.properties壳App的build.gradle和module中build.gradle配置3:如何解决模块之间依赖冲突,资源冲突依赖冲突资源... -
Android模块化开发
2017-08-18 20:21:02前言项目越做越大,每次编译的时间越来越长,体验特别不好,在加上协同开发的时候,项目管理问题,因此开始了我的Android项目的模块化开发之旅。1.模块化开发的优势 单独模块开发,编译迅速,调试方便 模块之间相互... -
Android模块化项目模块间数据交互解决方案
2018-04-18 11:12:34之前呢,也做过一个关于模块化业务分离的架构方案,这篇帖子想分享一下关于模块间的数据交互的方案。模块化架构,基础的可以通过创建多个module来把业务进行区分和代码的解耦。为了解耦,让module可拆卸,可移植,... -
关于Android模块化我有一些话不知当讲不当讲
2017-05-18 11:56:58关于Android模块化我有一些话不知当讲不当讲最近公司一个项目使用了模块化设计,本人参与其中的一个小模块开发,但是整体的设计并不是我架构设计的,开发半年有余,在此记录下来我的想法。关于Android模块化我有一些... -
【Android 组件化】从模块化到组件化
2021-05-14 12:22:50一、从模块化到组件化、 二、build.gradle 构建脚本分析、 -
Android:项目模块化/组件化的架构之路(一)
2018-11-13 18:22:59对于组件化,有的人也喜欢称之为模块化开发,我也比较喜欢称之为模块化开发。使用模块化开发也已经有一段时间了,特此总结一下模块化开发的心得,防止以后忘记。 什么是模块化开发 对于模块化开发的概念,有的人... -
Android模块化(三)——模块可插拔单独编译运行
2017-12-21 18:19:28转自: 此文属于finddreams的... ...下面主要来讲一下单一模块的独立编译运行和插拔式的集成。... 模块化的好处之一就是单一模块可以独立的开发编译运行安装到用户的手机上,这样就方便了对某一模块的单独开发调试 -
我所理解的Android模块化(三)——模块可插拔单独编译运行
2017-10-26 15:16:19前面已经写了两篇Android模块的博客,主要讲到了模块化的跳转路由,模块间通信和模块间服务调用。 模块化的好处之一就是单一模块可以独立的开发编译运行安装到用户的手机上,这样就方便了对某一模块的单独开发调试... -
Android模块化开发遇到的问题: 资源名冲突的问题
2018-08-23 10:45:05android { ... buildTypes { ... } resourcePrefix 'my_prefix_' } 还有个注意事项: 我们知道主工程中,不同的xml中可以将控件命名为相同的id,因为是唯一的final public static final int seekbar... -
android模块化思想之application
2015-08-05 20:29:41只是初始化模块,千万不要做其他。 在目录com.xxx.integration下 目前顺序和内容: 版本管理初始化 初始化地图 初始化第三方组件.imageloader,xutils,volley 所有产品的模块初始化 全局异常管理