二.组件化配合ARouter框架使用

1.什么是组件化,为什么需要组件化

1.1 适用场景

大型项目、多人协作开发,此时就可以采用组件化,减少编译时间,减少多人协作代码提交导致的冲突.
小型项目、独立完成,这种情况下就没有必要采用组件化来进行过度设计.

1.2 所有代码都在app目录下,通过包名进行管理各个模块,存在哪些弊端?

低内聚问题、高耦合、无重用、层次混乱

  • 低内聚问题:内部间聚集、关联的程度,那么低内聚就是指内部间聚集和关联程度很低.
  • 高耦合:是对模块间关联程度的度量。耦合的强弱取决与模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。高耦合是指模块间存在紧密的关联关系.
  • 无重用:由于没有将组件或者公共基础库单独封装成一个独立的包,所以别的项目无法重用这些组件,需要重新将代码拷贝过去才能使用.
  • 层次混乱:比如早期的项目,所有的代码都在app模块下面,采用包名来进行模块管理,迭代过多或者代码不规范就会导致层次混乱的问题.

1.3 组件化的意义

不相互依赖、可以相互交互、任意组合、高度解耦、自由拆卸、自由组装、重复利用、分层独立化

解释说明:

  • 不相互依赖:业务组件之间可以做到不相互依赖;公共组件库之间可以做到不相互依赖
  • 可以相互交互:app与业务组件、组件之间都可以配合ARouter进行页面跳转
  • 任意组合:app工程可以添加和移除依赖的任意业务组件,从而可以达到快速新增和删除某个模块业务.
  • 高度解耦:业务组件之间相互独立,互不依赖;公共组件库之间相互独立,互不依赖
  • 自由拆卸:可以将单个组件打包出apk,单独进行这个模块的测试 (gradle编码控制)
  • 自由组装:可以将组件集成到app工程中,打包出正式环境apk (gradle编码控制)
  • 重复利用:公共基础库可以重复利用在各个组件之中,甚至还可以在公司开发的其他项目中使用;组件与组件之间没有直接关系,可以单独拿出来放在别的项目中使用.
  • 分层独立化:app工程、业务组件和公共基础库之间分工明确

组件化的层次结构:

  • app工程
  • 业务组件化
    • 微信举例:微信、通讯录、发现、我.
      还可以把某个功能/项目作为一个组件,比如:QQ里的QQ空间,淘宝中的天猫超市.
  • 公共基础库
    • Base、Utils、Http、ARouter等等

2.集成环境/组件环境自动部署服务

2.1 通过Gradle来控制正式环境和测试环境的部署

gradle文件访问顺序:

  1. settings.gradle
  2. project的build.gradle
    • 我们可以通过在project目录下创建一个global.gradle(标记的名字可以随便取)文件,这个文件将app壳工程和模块中一些依赖的库给抽离出来,达到版本统一控制的目的.
      然后在app工程和模块就引用这个global.gradle文件中定义的变量.
  3. app工程的build.gradle
  4. module的build.gradle

2.2 自定义全局gradle使用

这里我们拿上面创建的global.gradle文件使用来举例.

  1. 在project目录下创建全局控制的global.gradle文件,global.gradle文件代码如下:

    ext {
          
          
        isApplication = false //false:作为module(不能单独运行),true:作为application(可以作为app单独运行)
    
        //对app工程及所有组件版本号进行统一管理
        version = [
                compileSdkVersion: 30,
                minSdkVersion    : 19,
                targetSdkVersion : 30,
                buildToolVersion : '28.0.3',
                versionCode      : 100,
                versionName      : "1.0.0",
        ]
    
        //依赖库版本号管理
        libsVersion = [
                gson = "2.8.5",
                glide = "4.6.1",
                rxlifecycle = "2.2.2",
        ]
        //依赖库管理
        libsDependency = [
                gson                         : "com.google.code.gson:gson:$rootProject.gson",
                glide                        : "com.github.bumptech.glide:glide:$rootProject.glide",
                rxlifecycle                  : "com.trello.rxlifecycle2:rxlifecycle:$rootProject.rxlifecycle",
        ]
    }
    
  2. 在app工程和组件的build.gradle文件中使用,拿组件目录下的build.gradle文件中代码举例:

    apply plugin: 'com.android.library'
    //配置版本号
    android {
          
          
      compileSdkVersion rootProject.ext.version.compileSdkVersion
    
      defaultConfig {
          
          
          minSdkVersion rootProject.ext.version.minSdkVersion
          targetSdkVersion rootProject.ext.version.targetSdkVersion
          buildToolsVersion rootProject.ext.version.buildToolVersion
          versionCode rootProject.ext.version.versionCode
          versionName rootProject.ext.version.versionName
      }
    }
    
    //依赖的包
    dependencies {
          
          
        implementation(rootProject.ext.libsDependency.gson)
        implementation(rootProject.ext.libsDependency.glide)
        implementation(rootProject.ext.libsDependency.rxlifecycle)
    }
    

3.组件化中子模块交互方式

3.1 如果不使用ARouter,可以通过哪些方案实现组件化中各个子模块之间的通信?

  • EventBus
    • 缺点:EventBean对象维护成本太高,不好去管理
  • 广播(Broadcast)
    • 缺点:不好管理,都统一发送出去了
  • 隐式意图
    • 缺点:在AndroidManifest.xml里面配置,每个需要跨模块跳转的页面都需要配置intent-filter的action和category属性.
    • 使用方式代码如下:
      <!--清单文件中需要添加intent-filter过滤器,然后设置action和category-->
      <activity android:name="com.tangkun.action.ActionImplActivity">
          <intent-filter>
              <action android:name="com.tangkun.myaction1" />
              <category android:name="android.intent.category.DEFAULT" />
          </intent-filter>
      </activity>
      
      //在Activity中通过Intent传入Action名称来实现页面跳转
      Intent intent = new Intent();
      //设置的action名称需要和清单文件中通过action配置名称相同;还需要指定category,否则会报错
      intent.setAction("com.tangkun.myaction1");
      startActivity(intent);
      
  • 类加载方式
    • 缺点:容易写错包名或类名. 可以尝试通过这种方式
    • 使用方式代码如下:
      Class classLoad = Class.forName("com.tangkun.action.ActionImplActivity");
      Intent intent = new Intent(this, classLoad);
      startActivity(intent);
      
  • 使用全局Map的方式
    • 缺点:要注册很多的对象. 可以尝试通过这种方式
    • 操作步骤:
    1. 在App工程中自定义Application中将所有的Activity都注册到base库中的一个集合中去,通过名字(如:MyInfoActivity)和Activity.class(比如:MyInfoActivity.class)
    2. 在组件中通过依赖的base库里的集合对象中,通过名称取出他的Class对象,然后通过Intent启动这个页面即可.
      问题: 如果要移除某个组件,岂不是要将这个组件下所有添加到集合Activity从自定义Application给删除掉.

3.2 组件化、插件化和模块化的区别?

  • 组件化: 组件之间可以自由组装与拆卸,以及重用,因为组件与组件之间没有直接关系
  • 插件化: 当前app通过加载从服务器下载的apk文件,然后解析出来在当前app中使用.
    • 比如:支付宝app中包含了很多插件(饿了么、菜鸟、哈啰这些都属于下载下来的插件apk)
    • 存在问题:版本兼容,需要去适配目前各个系统版本存在的兼容性问题,还要担心Android系统升级带来的兼容性问题.
      因为需要启动未注册的Activity,所以会用到Hook技术,在AMS检测启动页面时,将启动页面替换成一个已经注册的代理ProxyActivity,AMS检测后在执行生命周期之前替换成未注册的启动页面即可.
      为了规避插件化带来的兼容性问题,这些app都替换成加载网页的形式,来实现展示别的app功能.
  • 模块化: 组件化是为了重用而拆分模块,而模块化是为了业务的分离而拆分模块.

4.APT技术

注解处理器(Annotation Processing Tool)

概念:
是一种处理注释的工具,他对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,如果想要注解处理器能够运行,必须要通过APT工具(继承他AbstractProcessor重写process()方法,然后在这个方法中进行处理)来进行处理.
也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解处理器才能执行.
通俗来讲:根据规则,帮我们生成代码、生成类文件.

运行期和编译其采用过APT技术的一些框架举例和比较:

  • 运行期采用了APT技术的框架举例:
    XUtils
    特点: 需要在用户使用的时候才会去做解析工作,给用户带来了性能负担.因此,惨遭淘汰.

  • 编译期采用类APT技术的框架举例:
    Butterknife、Dagger2、Room、DataBinding、ARouter
    特点: 开发者通过开发工具在编译代码的时候,就会生成相应的代码.因此,不会影响到用户的体验.

5.高级用法JavaPoet

Poet /ˈpoʊət/ 诗人

  • EventBus采用传统的方式一行一行生成代码.
    • 我的理解:读取.java/.kt文件中的每一行代码进行解析,看是否使用到了EventBus的注解相关代码,然后再去解析注解内容,进行处理;应该就是观察者设计模式,被观察者发送消息然后观察者去处理消息
  • ButterKnife和ARouter则采用JavePoet生成代码.

JavaPoet是什么?
JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件,这个框架的功能非常实用,也是我们习惯的Java面向对象OOP语法,可以很方便的使用他根据注解生成相应代码.

(EventBus实用)传统方式与JavaPoet区别?
传统方式:先导包,然后再写类,最后写方法,总之就是把我们的java文件中的代码一行一行的通过BufferWriter写进去,比较符合正常使用逻辑;可读性强
JavaPoet先写方法,然后再写类,最后导包,是OOP面向对象思想,在复杂的场景下性能不及传统的方式.看起来费劲

JavaPoet使用方式:

  1. 通过自定义注解处理器继承自AbstractProcessor,然后重写process()方法,当我们在编译的时候,会调用这个方法,该方法中会对使用过带有注解(比如:过滤Arouter.class)的类进行解析;
  2. 在解析过程中,JavaPoet会根据方法、类、包、文件的顺序生成对应的.class文件,生成文件的目录是:
    build/generated/sources/...

6.组件化项目部署,ARouter原理

从层次结构上分类:

  • app壳工程
  • 业务组件
    • 在业务组件中的类中添加注解,比如:Activity添加ARouter注解
    • 当编译的时候,就会将这个类的信息注册到ARouter路由表中
    • 从而可以让一个和当前组件没有直接关系的组件,可以通过注册到路由表中的class信息,实现跨组件页面跳转.
  • 公共基础库
  • ARouter
    • ARouter-annotatation 注解类所在lib
    • ARouter-complier 用到了auto-service,然后继承自AbstractProcessor重写process()方法.当我们在编译的时候,就会根据注解生成相应的.class文件

7.Path和Group的映射关系

采用分组Group的形式,来对这个组下面的Path进行管理.
比如:我们的业务组件,在使用ARouter的时候,在对Activity/Fragment进行注解时,需要采用模块名+类名的形式.如:/user/PersonDetailActivity
当要通过ARouter路径跳转某个页面时,会根据路径解析出组名,对该组的页面路径进行遍历,找出跳转的页面.
分组的好处:不需要遍历所有的组下的路径对应的class,只需要遍历当前组下的路径列表,提高查询效率.

8.Path生成管理和Group生成管理

Path和Group生成步骤:

  1. 定义Path仓库和Group仓库,为后续存储做准备
  2. 准备工作:路由对象封装,校验作用域为Activity等
  3. 校验路由对象
  4. 第一大步:Path
  5. 第二大步:Group

总结:
在自定义AbstractProcessor类重写的process方法中,通过过滤出使用过@ARouter注解的ActivityFragment,然后通过JavaPoet给他们生成.java文件,
(JavaPoet采用OOP思想生成Java代码并存放在文件里,生成规则是按这个顺序:方法、类、包、文件)
当我们在编译的时候,就会生成.class文件,存放在如下目录:
build/generated/sources/...
由于ARouter框架是由公共基础库所依赖,所以我们依赖了基础组件库的组件就可以通过组名(Group)和路径名(Path)找到启动页面的Class类,然后我们可以通过Intent启动该Class类实现跨组件页面跳转.

9.待续:ARouter懒加载navigation、组件与组件之间通信传输

猜你喜欢

转载自blog.csdn.net/tangkunTKTK/article/details/130834184