组件化:就是将一个app分成多个module,每个module都是一个组件(也可以是一个基础库供组件进行依赖),开发中可以单独调试组件,组件间不需要相互依赖,但可以相互调用,最终发布时所有的组件以lib的形式被主工程依赖并打包成apk
业务隔离,方便开发和调试
插件化:将整个app拆分成多个模块,分宿主和多个插件,每个模块都是一个apk(组件化,每个模块都是一个lib),最终打包的时候将宿主apk和插件apk(或其他格式)分开或者联合打包,
组件化是在编译器分模块,插件化实在运行期。插件化一般用于热修复,和动态更新模块。
组件化开发让每个module都跨源独立运行,开发期间都设置为application。发布时再合并。
重点:
遇到的问题:
阿布他们的项目大量的用了 databinding 和 dagger,然而我们项目并没有用这些,用了这两个库的可以看看他是怎么爬坑的:
魔都三帅
当你采用了组件化开发的时候,一定会遇到这几个问题,这几个问题除了第三个都只能规避,没有好的处理办法:
1、module 中 Application 调用的问题
2、跨 module 的 Activity 或 Fragment 跳转问题
3、AAR 或 library project 重复依赖
4、资源名冲突
1,applcation冲突,因为再debug状态下module是个application,再release状态下它是一个lib,所以获取到的application对象不是同一个类。
所以再application里面尽量不要写方法实现和强转操作,用变量区分业务逻辑
2,module跳转
1,
Scheme方式建立映射表,集中处理activity,可以传递一定的数据。
2,github上的一个
url Router
,同时支持http和程序内Activity跳转,而且通过注解的方式进行,使用非常方便,于是引入到了项目中。项目地址
ActivityRouter
。
ActivityRouter
的readme中已经有比较详细的wiki,
3,
类名方式跳转不支持传递数据
首先创建一个所有界面类名的列表
public class RList { public static final String ACTIVITY_MEMORYBOX_MAIN = "com.kymjs.app.memory.module.main.MainActivity"; public static final String FRAGMENT_MEMORYBOX_MAIN = "com.kymjs.app.memory.module.list.MainFragment";}
在获取 Fragment 的时候就可以根据列表中的类名来读取指定的 Fragment 了。
public class FragmentRouter { public static Fragment getFragment(String name) { Fragment fragment; try { Class fragmentClass = Class.forName(name); fragment = (Fragment) fragmentClass.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } return fragment; }}
同理,Activity 其实也可以用这种方法来跳转:
public static void startActivityForName(Context context, String name) { try { Class clazz = Class.forName(name); startActivity(context, clazz); } catch (ClassNotFoundException e) { throw new RuntimeException(e); }}
最后,对于这个
RList
类,我们还可以通过 Gradle 脚本来生成,就像 R 文件一样,这样子开发就要方便很多了。
3.重复依赖
如果是 aar 依赖,gradle 会自动帮我们找出新版本的库而抛弃旧版本的重复依赖。但是如果你使用的是 project 依赖,gradle 并不会去去重,最后打包就会出现代码中有重复的类了。
一种是 将 compile 改为 provided,只在最终的项目中 compile 对应的代码,但是这种办法只能用于没有资源的纯代码工程或者jar包;
可以将所有的依赖写在 shell 层的 module,这个 shell 并不做事情,他只用来将所有的依赖统一成一个入口交给上层的 app 去引入,而项目所有的依赖都可以写在 shell module 里面。
4, resourcePrefix 可以给xml文件加前缀,但所有的图片资源仍需要手动修改资源。
组件化架构:
app
是最终工程的目录
explorer
和
memory-box
是两个功能模块,他们在开发阶段是以独立的 application,在 release 时才会作为 library 引入工程。
router
有两个功能,一个是作为路由,用于提供界面跳转功能。另一个功能是前面讲的 shell ,作为依赖集合,让各业务 module 接入。
base-res
是一些通用的代码,即每个业务模块都会接入的部分,它会在 router 中被引入。
组件化路由架构图
通过上图可以看到,我们在最基础的Common库中,创建了一个路由
Router
,中间有n个模块
Module
,这个
Module
实际上就是Android Studio中的module,这些
Module
都是
Android Library Module
,最上面的Module Main是
可运行的Android Application Module
。
这几个
Module
都引用了Common库,同时Main Module还引用了A、B、N这几个
Module
,经过这样的处理之后,
所有的
Module
之间的相互调用就都消失了,耦合性降低,所有的通信统一都交给Router来处理分发,而注册工作则交由Main Module去进行初始化
。这个架构思想其实和Binder的思想很类似,采用C/S模式,模块之间隔离,数据通过共享区域进行传递。模块与模块之间只暴露对外开放的Action,所以也具备
面向接口编程思想
。
图中的红色矩形代表的是行动
Action
,
Action
是具体的执行类,其内部的
invoke方法是具体执行的代码逻辑
。如果涉及到
并发操作
的话,可以在
invoke方法内加入锁,或者直接在invoke方法上加上synchronized描述
。
图中的黄色矩形代表的是供应商
Provider
,每个
Provider
中包含1个或多个
Action
,其内部的数据结构以
HashMap
来存储Action。
首先HashMap查询的时间复杂度是O(1),符合我们对调用速度上的要求,其次,由于我们是统一进行注册,所以在写入时并不存在并发线程并发问题,在读取时,并发问题则交由Action的invoke去具体处理。
在每一个
Module
内都会有1个或多个供应商
Provider
(如果不包含
Provider
,那么这个
Module
将无法为其他
Module
提供服务)。
途中蓝色矩形代表的是路由
Router
,每个
Router
中包含多个
Provider
,其内部的数据结构也是以
HashMap
来存储
Provider
,原理也和
Provider
是一样的。之所以用了两次HashMap,有两点原因,一个是因为这样做,
不容易导致
Action
的重名
,另一个是因为在注册的时候,只注册
Provider
会
减少注册代码,更易读
。并且由于HashMap的查询时间复杂度是O(1),所以两次查找不会浪费太多时间。当查找不到对应
Action
的时候,Router会生成一个
ErrorAction
,会告之调用者没有找到对应的
Action
,由调用者来决定接下来如何处理。
一次请求流程
通过Router调用的具体流程是这样的:
Router时序图
- 任意代码创建一个RouterRequest,包含Provider和Action信息,向Router进行请求。
- Router接到请求,通过RouterRequest的Provider信息,在内部的HashMap中查找对应的Provider。
- Provider接到请求,在内部的HashMap中查找到对应的Action信息。
- Action调用invoke方法。
- 返回invoke方法生成的ActionResult。
- 将Result封装成RouterResponse,返回给调用者。
耦合降低
所有的
Module
之间的相互依赖没有了,我们可以在主app中,取消任意的
Module
引用而不影响整体App的编译及运行。
取消对Module N的依赖
如图所示,我们取消了对
Module N
的依赖,整体应用依然可以稳定运行,遇到调用
Module N
的地方,会返回Not Found提示,实际开发中可以根据需求做具体的处理。
Router是JVM级别的单例模式,并不支持跨进程访问
开源库ActivityRouter支持给Activity定义 URL,这样就可以通过 URL 跳转到Activity,并且支持从浏览器以及 APP 中跳入我们的Activity,而且还支持通过 url 调用方法。
接下来我们将声明项目中的业务组件,声明方法如下:
@Module("girls")
public class Girls {
}
在每一个业务组件的java文件的根目录下创建一个类,用 注解@Module 声明这个业务组件;
然后在“app壳工程”的 应用Application 中使用 注解@Modules 管理我们声明的所有业务组件,方法如下:
@Modules({"main", "girls", "news"})
public class MyApplication extends BaseApplication {
}
项目中的任何一个地方通过 URL地址 : module://girls, 调用 GirlsActivity,方法如下:
Routers.open(MainActivity.
this
,
"module://girls"
);
APT(Annotation Processing Tool)是一种处理注解的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。
组件间通信可以使用Android广播来解决,也可以使用第三方的事件总线来实现,比如EventBus。
URL Scheme应用场景:
客户端应用可以向操作系统注册一个 URL scheme,该 scheme 用于从浏览器或其他应用中启动本应用。通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如商品详情页、活动详情页等等。也可以执行某些指定动作,如完成支付等。也可以在应用内通过 html 页来直接调用显示 app 内的某个页面。综上URL Scheme使用场景大致分以下几种:
- 服务器下发跳转路径,客户端根据服务器下发跳转路径跳转相应的页面
- H5页面点击锚点,根据锚点具体跳转路径APP端跳转具体的页面
- APP端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面
- APP根据URL跳转到另外一个APP指定页面