Android 组件化基础

当 App 项目复杂一定的程度,将项目组件化是必不可少的,组件化可以更好的进行功能的划分,提到组件化有人可能会想到模块化,其实组件化和模块化的本质是一样的,都是为了代码重用的业务解耦,模块化主要按照业务划分,而组件化主要按照功能划分,从组件化最基础的几个方面打开组件化的大门。

  1. 组件之间的跳转
  2. 动态创建
  3. 资源冲突
  4. 静态常量

组件之间的跳转

组件化中两个功能模块时不直接依赖的,其依赖规则是通过 Base module 间接依赖,当组件之间的 Activity 进行界面跳转时,由于没有相互依赖的关系,往往会无法引用另一个 module 中的 Activity。

隐式跳转

隐式跳转是通过 Android 原生 Intent 匹配机制来实现相应跳转,就是使用 Action 来跳转到对应的 Activity,这样使用隐式跳转的方式就可以跨 module 实现 Activity 之间的跳转了,注意一点,如果移出 Activity 所在的 module 而不移出相应的跳转,如果继续跳转会出现异常,使用隐式 Intent 跳转需要验证是否会接收该 Intent,需要对该 Intent 对象调用 resolveActivity() 方法来判断至少有一个应用能够处理该 Intent,通过隐式跳转的方式还可以设置 exported 为 false 来确保只有自己的 App 才能够启动对应的组件。

ARouter 跳转

在 Android 开发中可将 module 看成不同的网络,而对应的 Router 就是连接各个 module 的中转站,这个中转站可以对页面跳转的参数等进行统一处理,ARouter 是阿里开源出来的一个页面跳转路由,使用 ARouter 可以替代隐式跳转来完成不同 module、不同组件之间的跳转以及跳转过程的监听、参数的传递等,ARouter 支持路径跳转和 URL 跳转两种方式,使用也非常灵活,ARouter 的具体使用这里不做介绍,其具体使用会在单独一篇文章中详解,ARouter 与 Android 传统跳转方式的对比如下:

  1. 显示跳转需要依赖于类,而路由跳转通过指定的路径跳转;
  2. 隐式跳转通过 AndroidManifest 集中管理,导致协作开发困难;
  3. 原生使用 AndroidManifest 来注册,而路由使用注解注册
  4. 原生 startActivity 之后跳转过程交由 Android 系统控制,而路由跳转采用的是 AOP 切面编程可对跳转过程进行拦截和过滤。

动态创建

组件化开发中最重要的一点就是各个模块、各个组件之间要尽可能解耦,这样很容易就会想到使用 Java 中的反射机制,使用反射可在运行状态下获取某个类的所有信息,然后就可以动态操作这个类的属性和方法了。如果 Fragment 单独作为一个组件来使用时,当这个 Fragment 组件不需要被移出后,如果是常规的 Fragment 则会因为索引不到该 Fragment 而使得 App 崩溃,想一下如果使用反射创建 Fragment 的方式则至少不会引起 App 崩溃,这里可以捕捉异常完成相关逻辑,这样是不是降低了耦合呢。可见,虽然反射有一定的性能问题,但使用反射确实能在一定程度上降低耦合,学习组件化 Java 反射机制应该是必须的一部分。

组件化开发中要求每个组件都能独立运行,一般情况下每个组件都有一定的初始化步骤,最好的一种情况是项目需要的几个组件的初始化基本相同,那就可将初始化放在 BaseModule 中进行统一初始化,但是这种情况毕竟比较理想,一般情况是每个组件的初始化都不一样,可能你会想到在各自的 Application 初始化,如果在各自的 Application 中初始化,当在最终编译由于 Application 的合并难免会出一些问题,这种方式也不可取,到这里又想到了反射,在各组件中创建初始化文件,然后在最终的 Application 中通过反射完成各个组件的初始化操作,这里通过 Java 的反射机制完成了组件化开发中 Application 的动态配置。

资源冲突

组件化开发过程中,如果 ModuleA 的 AmdroidManifest 文件中使用 android:name 属性指定了相应的 Application,而主 App Module 的 AndroidManifest 文件中也使用 android:name 属性指定了相对应的 Application,此时就必须在 主 App Module 的 AndroidManifest 文件中使用 tools:replace=“android:name” 来解决冲突,使用 replace 属性表示该属性也就是在 标签下的 android:name 属性可在编译过程中被替换,这样根据 AndroidManifest 文件替换规则最终指定的 Application 应该是 App Module 中的指定的 Application。

举一个例子,我在项目中的某个功能 Module 中使用 SMSSDK 来完成短信验证的功能,因为其他地方不用,所以只引入到了要使用的功能 Module 中,如果其他 Module 会使用应该将 SMSSDK 引入到 BaseModule 中,使用 SMSSDK 如果不指定该 Module 的 Application,MobSDK 会将 com.mob.MobApplication 指定为该 Module 的 Application,此时在整体编译打包时就会出现 AndroidManifest 文件的 android:name 属性冲突,当然了解决方法就是使用 replace 属性了。 AndroidManifest 文件合并后的主要冲突也就是这个问题了,当然 下的其他属性有冲突,也是使用 replace 属性。在实际的开发中多验证会更有收获喔。

组件化开发中另外需要注意的一点是防止资源名称一样导致最终合并的时候,因为冲突造成资源引用错误或者某些资源丢失等,如字符串、颜色值等资源等合并的时候会被后面加载的相同名称的资源所替换,解决的思路是在资源命名上要有一定的规则,可以在 build.gradle 文件中配置 “resourcePrefix “组件名称”” 的方式强制约束开发者确保资源名称唯一,建议 Module 中资源的命名格式为 “Module 名称 _ 功能 _ 其他”。

静态常量

组件化开发中,最终合并时每个组件都是以 Lib Module 的形式存在,而 Lib Module 中 R.java 文件中定义的静态变量没有声明为 final,这就意味着不能在组件 Module 中使用相对应的常量了,如在时候 switch 语句就不能使用了,这就要求在组件中要使用 if 语句来替代 switch 语句,当然在组件独立运行的时候是没有这个问题的。

开发中经常会使用到 Butterknife,Butterknife 可非常方便的对 View 及 View 的事件等进行注解操作,它采用的是编译时注解机制,注解中只能使用常量,所以在 Butterknife 在组件化开发中应该使用 R2 代替 R,R2 实际上是 R 的拷贝, R2 对应声明的变量是 final,所以在组件化开发中如果使用 Butterknife 在相应的注解中要使用 R2 替代 R。