组件化开发

单工程遇到的问题

随着项目逐渐发展,业务越来越多,代码量也越来越多,耦合严重,层次混乱,页面互相之间的跳转有着极强的关联性,所有代码都写在app module中,编译一次都要5-6分钟,为了方便以后项目的开发/测试以及提高编译性能就需要进行组件化了。

组件化的优势

  • 降低耦合度:每个业务模块无不关联,可自由拆卸、自由组装,重复利用
  • 加快编译速度:每个组件可以单独编译运行,发布时也可以合并成一个app。
  • 提高协作效率:团队中每个人负责自己的组件,不会影响其他组件,降低团队成员熟悉项目的成本。

组件如何划分

我们可以分为基础组件、Common组件、业务基础组件、业务组件、注册登录组件、app壳组件。

  • 基础组件:最基础的,一般不会怎么变化的功能,比如网络请求、图片加载、日志、工具类、权限等。
  • Common组件:服务暴露接口、自定义view、部分共用实体类、部分共用资源文件、各种Base基类(BaseActivity/BaseFragment等),依赖基础组件。
  • 业务基础组件:比如分享、推送等功能,依赖基础组件。
  • 业务组件:项目的各个业务模块,我们日常主要围绕着业务组件开发,依赖Common组件。
  • app壳组件:原则上不包含业务代码,依赖Common组件和业务组件。
  • 注册登录组件:依赖Common组件,业务组件、app壳组件按需依赖该组件。

项目结构图

在这里插入图片描述

组件单独调试、gradle配置

统一配置依赖

项目划分了组件需要对每个组件进行统一配置,否则版本不一致会出现各种问题。首先需要在项目根目录创建一个config.gradle文件,然后统一定义版本、依赖等,最后在各个组件中的build.gradle中使用config.gradle定义的版本、依赖。
在这里插入图片描述

动态配置插件

现在项目都是gradle构建的,gradle提供了两种插件用来配置不同的工程

  • com.android.application:app插件
  • com.android.library:library插件

app插件配置android app工程,项目构建后会打包成apk包。library插件用来配置android library工程,项目构建后会打包成aar包。我们需要动态配置这两种插件,当需要发布包时需要配置library插件,当开发单独调试的时候需要配置app插件。

我们需要gradle.properties文件中定义一个isRelease = false变量,当为true的时候不能单独运行,当为false的时候可以独立运行。
在这里插入图片描述

动态配置ApplicationId和AndroidManifest

一个app要想独立运行需要一个ApplicationId的,一个app也只有一个ApplicationId,所以在单独调试和集成调试时组件的 ApplicationId 应该是不同的。一个app需要有一个启动页,集成调试的时候AndroidManifest会合并成一个,因此需要配置两个AndroidManifest,一个有启动页一个没有启动页,根据isRelease来动态切换。
在这里插入图片描述

//单独调试
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jk.order">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.CustomARouter">
        <activity
            android:name=".OrderActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

//集成调试
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jk.order">

    <application>
        <activity android:name=".OrderActivity" />
    </application>

</manifest>

组件之间页面跳转

组件化的核心解耦,组件之间无不关联,相互之间不能有引用,那么怎么实现组件之间的跳转呢?比较著名的有两款框架,分别是阿里的ARouter和美团的WMRouter,他们的原理类似,都是基于apt技术动态生成代码,实现可以根据配置的路由信息进行跳转、数据共享、拦截器等。ARouter功能更加强大,使用人数也更多,因此我们选择ARouter作为路由框架,具体的使用方法参考ARouter文档

组件之间数据共享

由于组件之间不能相互引用,那如果B组件要用到A组件中的数据该怎么办呢?那么我们可以通过Arouter中的暴露服务。在module_common组件中定义接口,然后在需要暴露数据的组件中实现该接口并定义路由,这样就可以在其他组件调用到该组件的数据了。

//common组件IBuildConfigProvider
interface IBuildConfigProvider : IProvider {

    fun getApplicationId(): String

    fun getApiHost(): String

    fun getBaseUrl(): String

    fun getGhzsUrl(): String

    fun getVersionName(): String

    fun getFlavor(): String
}

//A组件BuildConfigImpl
@Route(path = RouteConsts.provider.buildConfig, name = "BuildConfig暴露服务")
class BuildConfigImpl : IBuildConfigProvider {
    override fun getApplicationId(): String = BuildConfig.APPLICATION_ID

    override fun getApiHost(): String = BuildConfig.API_HOST

    override fun getBaseUrl(): String = BuildConfig.BASE_URL

    override fun getGhzsUrl(): String = BuildConfig.GHZS_URI

    override fun getVersionName(): String = BuildConfig.VERSION_NAME

    override fun getFlavor(): String = BuildConfig.FLAVOR

    override fun init(context: Context?) {

    }
}
//在B组件中使用依赖查找的方式发现服务
 val buildConfig = ARouter.getInstance().build(RouteConsts.provider.buildConfig).navigation() as? IBuildConfigProvider

Application生命周期分发

我们通常会在Application的onCreate中做一些初始化任务,而业务组件有时也需要获取应用的Application,也要在应用启动时进行一些初始化任务。直接在壳工程Application的onCreate操作就可以啊,但是这样做会带来问题:因为我们希望壳工程和业务组件代码隔离,并且我们希望组件内部的任务要在业务组件内部完成。那么如何做到各业务组件无侵入地获取 Application生命周期呢?答案是使用spi技术,它可用于在Android组件化开发中Application生命周期主动分发到组件。具体使用如下:

引入依赖:

 //common组件 build.gradle
 api 'com.google.auto.service:auto-service:1.0-rc7'
 kapt 'com.google.auto.service:auto-service:1.0-rc7'

然后在Common组件中创建一个接口,这个接口和Application中的接口方法一致:

interface IApplication {
    fun attachBaseContext(base: Context)
 
    fun onCreate()

    fun onLowMemory()

    fun onTerminate()

    fun onTrimMemory(level: Int)

    fun onConfigurationChanged(newConfig: Configuration)
}

接下来就在各个组件中实现IApplication接口:

@AutoService(IApplication::class)
class ApplicationImpl : IApplication {

    override fun attachBaseContext(base: Context) {
        app = base as Application
    }

    override fun onCreate() {

    }
    override fun onLowMemory() {

    }
    override fun onTerminate() {

    }
    override fun onTrimMemory(level: Int) {

    }
    override fun onConfigurationChanged(newConfig: Configuration) {

    }
    companion object {
        lateinit var app: Application
    }
}

最后就在app模块中使用ServiceLoader加载出每个模块实现的ApplicationImpl,Application执行每个生命周期的时候分发给每个组件:

class App : Application() {
    private var mApplicationList: List<IApplication> = ServiceLoader.load(IApplication::class.java, javaClass.classLoader).toList()
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        mApplicationList.forEach {
            it.attachBaseContext(this)
        }
    }
    override fun onCreate() {
        super.onCreate()
        mApplicationList.forEach {
            it.onCreate()
        }
    }

    override fun onLowMemory() {
        super.onLowMemory()
        mApplicationList.forEach {
            it.onLowMemory()
        }
    }

    override fun onTerminate() {
        super.onTerminate()
        mApplicationList.forEach {
            it.onTerminate()
        }
    }

    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        mApplicationList.forEach {
            it.onTrimMemory(level)
        }
    }

    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        mApplicationList.forEach {
            it.onConfigurationChanged(newConfig)
        }
    }
}

这样就实现了Application生命周期分发,构建app包,查看META-INF/services目录会生成一个文件,这个文件包含了我们在各个模块实现的IApplication类的全类名:
在这里插入图片描述

资源冲突

整个项目中尽量不要出现重复的资源,否则会出现资源冲突问题,如果一个资源很多模块都要用到,需要放到common组件中。网上都是推荐在gradle文件中配置 resourcePrefix "moudle_prefix",但是感觉不太好,这样会导致重复资源打包进apk,导致包体积增大。

总结

本文介绍了单工程带来的问题、组件化的优势、组件的配置/调试、组件之间的数据共享、资源冲突、以及通过SPI技术实现Application生命周期分发等,本文提到的问题基本上就是组件化大部分所面对的问题。

参考

高级开发必须理解的Java中SPI机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值