美文网首页Android开发经验谈
进阶高工必备技能:Android组件化架构全解析!(附视频+电子

进阶高工必备技能:Android组件化架构全解析!(附视频+电子

作者: 木木玩Android | 来源:发表于2020-12-14 15:44 被阅读0次

    前言

    现在大多数的App都会在重构的时候想到组件化或者说模块化,特别是在大厂或者一些有规模的公司显得尤为重要,目的是为了方便App解耦和优化。在我的理解里面组件化即将每个功能相同的模块一个个的装起来,然后以library的形式供我们的主app模块调用,而在主app模块中不会去进行任何的业务逻辑,只管打包好了,而除了主app模块,其他模块各回各家,各找各妈,干自己的业务逻辑去,简单的说组件化就是让library和application之间可以互相切换,libray可以作为单独的App运行也可以作为依赖库给主app调用。这种构建思想能大大的提升开发效率,减低代码的维护成本,减少代码的耦合度,让他人简单易懂。

    整体结构

    • common:基础组件部分,与业务无关,需要所有组件共同依赖的部分,如:网络请求封装、图片加载封装、ui相关基类、工具集合等(当然这些内容可以依据分层原则放在不同的基础module中)
    • router-comp:路由驱动组件,承载整个项目的路由工作
    • comp1:业务组件1,如视频组件,可独立运行
    • comp2:业务组件2,如新闻组件,可独立运行
    • comp3:业务组件3,如视频组件,可独立运行
    • app:壳工程,用于将各个组件组装成一个完成app

    组件化所面临的问题

    • 集成模式与组件模式转换(热插拔)
    • 组件之间页面跳转(路由)
    • 组件之间通信、调用彼此服务
    • 打包混淆

    组件化的实现

    针对上面所说的几个问题,下面我们逐个说明它们的解决方案,当解决完这些问题,你会发现,你已经搭建了一个基于组件化的项目。下图是一个完整的组件化项目结构:common是基础组件module,作为library存在,需要所有组件依赖;comp1、comp2作为组件存在,可配置成library或可独立运行的module;app是个壳,通过组装组件实现其价值。


    集成模式与组件模式转换(热插拔)

    Android工程通过gradle构建,通过配置每个module的gradle,来实现module的不同表现。Android Studio的module有两种属性,分别是:

    • application属性:可独立运行,也就是我们的app
    • library属性:不可独立运行,被app依赖的库

    module属性通过其目录下的gradle文件配置,当module属性为application时,该module作为完整的app存在,可以独自运行,方便编译和调试;当module属性为library时,该module作为一个依赖库,被壳工程依赖并组装成一个app。那么如何让这两种模式可以自动转换呢?如果每次切换模式的时候,都手动去修改每个组件的配置,组件少的情况下还可以接受,组件多了会非常不方便,下面就让我们来聊聊如何实现两种模式的自动转换。1、首先,声明全局配置变量,来标识module的属性(app or library),如在工程目录下的build.gradle文件中声明布尔变量ext.isModule,true代表组件作为可独立运行的app,false代表组件作为被依赖的library,如下所示

    buildscript {
     ext.kotlin_version = '1.3.21'
     ext.isModule = true //true-每个组件都是单独的module,可独立运行 false-组件作为library存在
     repositories {
       google()
       jcenter()
     }
    }
    

    2、配置组件的build.gradle文件

    //1 if (rootProject.ext.isModule) {
    //可独立运行的app
    apply plugin: 'com.android.application'
    } else{
    //被依赖的library
    apply plugin: 'com.android.library'
    }
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    apply plugin: 'kotlin-kapt'
    
    android {
    compileSdkVersion 28
    defaultConfig {
    
    //applicationId "com.study.comp1" //2 如果没有,默认包名为applicationId
    minSdkVersion 19
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
    release {
    minifyEnabled false
    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    }
    compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_7
    targetCompatibility JavaVersion.VERSION_1_7
    }
    //3
    sourceSets {
    main {
    if(rootProject.ext.isModule){
    manifest.srcFile 'src/main/java/module/AndroidManifest.xml'
    } else{
    manifest.srcFile 'src/main/java/library/AndroidManifest.xml'
    java {//移除module包下的代码
    exclude 'module'
    }
    }
    }
    }
    }
    

    上面是截取的组件gradle的部分代码,包含了组件化需要配置的所有内容,每一点都进行了注释

    • 注释1:如上所述,根据isModule的值,来设置module的属性,作为app or library
    • 注释2:当module属性为library时,不能设置applicationId;当为app时,如果未设置applicationId,默认包名为applicationId,所以为了方便,此处不设置applicationId
    • 注释3:Android Studio会为每个module生成对应的AndroidManifest.xml文件,声明自身需要的权限、四大组件、数据等内容;当module属性为app时,其对应的AndroidManifest.xml需要具备完整app所需要的所有配置,尤其是声明Application和launch的Activity;当module属性为library时,如果每个组件都声明自己的Application和launch的Activity,那在合并的时候就会发生冲突,编译也不会通过,所以,就需要为当前module重新定义一个AndroidManifest.xml文件,不声明Application和launch的Activity,然后根据isModule的值指定AndroidManifest.xml的路径,因此就有了注释3处的写法。为了避免集成模式下的命名冲突,每个文件都以自身module名为前缀来命名会是一个很好的方法。下图是该module的目录

    在需要切换module属性的时候改变步骤1处声明的变量值,然后重新编译即可

    组件之间页面跳转(路由)

    在组件化架构中,不同的组件之间是平衡的,不存在相互依赖的关系(可参考文章开头的架构图)。因此,假设在组件A中,想要跳转到组件B中的页面,如果使用Intent显式跳转就行不通了,而且大家都知道,Intent隐式跳转管理起来非常不方便,所以Arouter出现了,并且有强大的技术团队支持,可以放心使用了。那么如何在组件化架构中应用Arouter呢?下面详细来聊一聊

    1、依赖处理

    在common组件中将Arouter依赖进来,并配置编译参数;在业务组件中引入arouter编译器插件,同时配置编译器参数,下面是Common组件gradle文件的部分片段

    //配置arouter编译器参数,每个组件都需配置 kapt {
    arguments {
    arg("AROUTER_MODULE_NAME", project.getName())
    }
    }
    
    dependencies {
    //arouter api,只需在common组件中引入一次
    api('com.alibaba:arouter-api:1.4.1') {
    exclude group: 'com.android.support'
    }
    //arouter编译器插件,每个组件都需引入
    kapt 'com.alibaba:arouter-compiler:1.2.2'
    }
    
    

    2、初始化及编码实现

    在组件架构中,经常会遇到组件需要使用全局Context的情况,当组件属性为app时,可以通过自定义Application实现;当组件属性为library时,由于组件被app依赖,导致无法调用app的Application实例,而且自身不存在Application;所以,这里给出的方案是在common组件中创建一个BaseApplication,然后让集成模式(组件模式)下的Application继承BaseApplication,在BaseApplication中获取全局Context,并做一些初始化的工作,这里需要初始化Arouter,如下是在common组件中声明的BaseApplication。

    abstract class BaseApplication : Application() {
    
    companion object {
    var _context: Application? = null
    //获取全局Context
    fun getContext(): Application {
    return _context!!
    }
    }
    override fun onCreate() {
    super.onCreate()
    _context = this
    //初始化Arouter
    initARouter()
    //初始化其他第三方库
    }
    private fun initARouter() {
    if (BuildConfig.DEBUG) {
    ARouter.openDebug()
    ARouter.openLog()
    }
    ARouter.init(this)
    }
    override fun onTerminate() {
    super.onTerminate()
    //清理Arouter注册表
    ARouter.getInstance().destroy()
    }
    }
    

    根据Arouter的路由特性,初始化之后,就可以通过@Route注解注册页面,然后调用Arouter api实现页面的跳转了(这里所谓的跨组件页面跳转是指在集成模式下,而非组件模式下),无关乎是否在同一个组件下面,假设我们要从组件1页面携带参数跳转到组件2页面,请看下面示例

    /**
    * 在组件2中通过@Route注解注册该页面
    */ @Route(path = "/comp2/msg",name = "我是组件2的MSGActivity")
    class Comp2MsgActivity : BaseActivity() {
    //传递过来的参数
    @Autowired(name = "msg")
    @JvmField
    var msg: String? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //注入传递的参数
    ARouter.getInstance().inject(this)
    setContentView(R.layout.comp2_activity_msg)
    comp2_msg_msg.text = msg!!
    }
    }
    
    //在组件1中发起跳转命令
    ARouter.getInstance()
    .build("/comp2/msg")
    .withString("msg", "hello Im from Comp1")
    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    .navigation()
    

    以上便完成了一次简单的跨越组件的页面跳转,仅仅是Arouter的基本使用而已。解决了组件间页面跳转的问题后,我们来看看组件之间通信、调用彼此服务的实现。组件之间通信、调用彼此服务组件间通信功能和路由功能有着共通的地方,即都是利用Arouter的基础功能实现,在Arouter驱动层定义各个组件对外提供的接口,然后在组件自身模块实现该接口,通过Arouter调用其他组件服务。假设我们在组件2中需要调用组件1中的服务,可以总结为以下3点

    • 定义接口:在common组件中定义组件1对外提供的接口CompServer1,注意该接口类型为Arouter模板类型IProvider
    /**
    * 组件1对外提供的接口
    */
    interface CompServer1 : IProvider {
    fun showMsg(msg: String)
    }
    
    
    • 实现接口:在comm1中实现上面定义的接口,并通过@Route注解注册
    @Route(path = "/comp1/server",name = "comp1对外提供的服务")
    class CompServer : CompServer1 {
    var mContext: Context? = null
    override fun showMsg(msg: String) {
    Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show()
    }
    override fun init(context: Context?) {
    this.mContext = context!!
    }
    }
    
    • 调用服务:在完成组件1接口的定义和实现之后,在组件2中需要的地方调用该接口即可
    val server1 = ARouter.getInstance().build("/comp1/server").navigation() as CompServer1
    server1.showMsg("我从comp2吊起了comp1的接口")
    

    有没有感觉很简单??没错,就是这么简单,赶紧去用吧!哈哈

    打包混淆

    说到混淆,有人可能会疑惑,如果在各个组件中混淆可不可以?不建议这样混淆!!因为组件在集成模式下被gradle构建成了release类型的aar包,如果在组件中进行混淆,一旦代码出现了bug,这个时候就很难根据日志去追踪bug产生的原因,而且不同组件分别进行混淆非常不方便维护和修改,这也是不推荐在业务组件中配置buildType(构建类型)的原因。所以,组件化项目的代码混淆放在集成模式下的app壳工程,各个业务组件不配置混淆。集成模式下在app壳工程.gradle文件的release构建模式下开启混淆,其他buildType配置和普通项目相同,混淆文件放在app壳工程下,各个组件的代码混淆均放在该混淆文件中。

    最后

    以上,我们已经逐一解决了组件化所面对的各个问题,至此,我们已经搭建了一个简单的组件化架构的项目,是不是感觉在不知不觉中就实现了,并不是很难哦!现在,我来总结一下组件化的优势了。

    • 解耦:将业务组件代码90%与工程解耦,只所以是90%而非100%,是因为业务组件需要在common组件中声明对外开放的接口,那有没有什么方式可以做到完全解耦呢?目前还没有发现更好的办法。。。

    • 提高开发效率:依赖解耦这一优势,团队成员可以只专注于自己负责的组件,开发效率更高;而且,组件开发过程中只需编译自身的module,这样大大缩短了编译时长,避免了漫长的等待编译局面。

    • 结构清晰:在业务组件明确拆分的前提下,项目结构变的异常清晰,非常方便全局掌控。

    为了方便大家更加深入学习组件化架构原理及底层源码解读,我这边收集整理了一套视频+电子书+学习笔记的组件化学习资料。视频是由爱奇艺高工Lance老师主讲的《从零开始学组件化》以及组件化学习电子书《Android组件化架构》,和一套《第三方开源框架解析学习笔记》,包含插件化,组件化,热修复等热门第三方框架的解析学习笔记!

    需要以上组件化学习资料的小伙伴,可以在点赞+任意评论后,点击此处免费获取

    组件化学习视频
    组件化电子书
    开源框架解读目录及部分解析内容

    需要以上组件化学习资料的小伙伴,可以在点赞+任意评论后,点击此处免费获取

    相关文章

      网友评论

        本文标题:进阶高工必备技能:Android组件化架构全解析!(附视频+电子

        本文链接:https://www.haomeiwen.com/subject/pnyvgktx.html