此文属于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抽离的,一个模块可以依赖多个组件。组件则不能再依赖其他组件。
如上面模块化之后的示例项目结构图片,我们来分析一下:
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这个框架解决了模块化的两大难题,模块跳转和模块间服务调用,笔者也是使用这个库有大半年的时间,并没有发现有什么问题。
因为路由跳转是子模块都需要用到的,所以我们在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
本文已授权开发者技术前线 独家发布 侵权必究