本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!

前言

咱们一般会在一个项目工程 (Project) 里开发,该工程包含了 App 的所有事务。最开始咱们会在一个 app 模块里完结事务功用,之后跟着事务增加,代码量越来越多,编译时间越来越长。可能会抽取一些事务代码到新的模块,可是模块之间仍是存在着扑朔迷离的依靠关系,因为有跳转页面、传递数据等需求,耦合度很高,这会导致以下痛点:

  • 任何修正都要编译整个项目工程,经常编译一次需求十分久。项目越大,编ProcessOn译的时间越长。
  • 事务模块耦合度很高,导致事务功用难以复用,即便把整个模块代码导入到其他项目工程也很难编译经过。
  • 多人协作开发简单相互影响,代码兼并经常抵触,使得协作开发的功率很低。
  • 简单牵一发而动全身,导致保护本钱变高。

这些代码耦合度高导致的问题都能够用组件化处理,本文会给咱们介绍组件化的优势及其应用,以及更多的实战技巧。

相关系列文章:

  • 《怎样更好地进行 Android 组件化开发(一)实战篇》
  • 《怎样更好地进行 Android 组件化开发(二)技巧篇》
  • 《怎样更好地进行 Android 组件化开发(三)ActivityResult 篇》
  • 《怎样更好地进行 Android 组件化开发(四)登录阻拦篇》
  • (待更新…)

组件化的优势

组件是事务单一的功用模块,每个组件都能够独立运转,也能够集成到其他组件中运转。组件化相对于前面说的模块化,耦合度更低,能有效处理上述所讲的痛点。

  • 开发时能够独立编译调试一个事务模块,无需编译整个工程,进步编译功率。
  • 事务模块的耦合度下降,代码更加独立,更简单复用。
  • 每个事务组件都有相应的负责人,咱们的开发互不打扰,代码质量的好坏也只会影响到自己的事务模块,削减代码抵触,进步协作开发功率。
  • 假如事务功用有问题,一般只需求修正对应事务组件,保护本钱更低。

组件化架构

以下是个人了解的组件化架构计划:

如何更好地进行 Android 组件化开发(一)实战篇

从上到下分红了四层,只要上层模块才干依靠下层。讲一下每一层的作用:

  1. 根底层,是最根底的开发结构,包含了根底开发所需的基类、工具类、第三方库等。依靠该模块就能快速进行开发。
  2. 中间层,包含了路由的功用,能够和事务组件进行交互。因为依靠了根底开发结构,也算是一个增强版的组件化开发结构。
  3. 事务层,依靠于中间层,包含了各个事务功用的组件。每个事务组件都能运转出一个小型的 App 进行调试,也可能给其它模块进行复用。
  4. 应用层,也便是俗称的 App 壳,能够集成各种所需的事务组件,组合出不同 App。

假如是需求复用组件或许开发组件,就依靠中间层。假如 App 功用比较简单,根本用不上组件,那就依靠根底层。

怎样组件化

统一依靠版本

跟着事务功用不断增加,模块会越来越多,简单出现依靠版本不共同的状况,需求统一版本。常见的做法有两种。第一种是在 gradle 文件增加 Extra 特点,比方在工程根目录的 build.gradle 增加 ext {} 代码块声明变量,这些变量能在其它 gradle 文件直接运用。这样尽管能统一版本号和依靠,可是不能主动补全代码,不支撑跳转。

所以个人引荐运用 buildSrc 的办法,完结起来也简单,在工程根目录增加一个 buildSrc 文件夹,并在文件夹创立一个 build.gradle.kts 文件,文件内容如下:

plugins {
  `kotlin-dsl`
}
repositories {
  gradlePluginPortal()
}

之后在 buildSrc\src\main\kotlin 目录下增加 Kotlin 常量,比方:

object Versions {
    const val COMPILE_SDK = 32
    const val TARGET_SDK = 32
    const val MIN_SDK = 23
    const val VERSION_CODE = 1
    const val VERSION_NAME = "1.0.0"
    const val ANDROID_GRADLE_PLUGIN = "7.1.2"
    const val KOTLIN = "1.7.20"
    const val APPCOMPAT = "1.5.1"
    const val APP_STARTUP = "1.1.0"
    const val CONSTRAINT_LAYOUT = "2.1.4"
    const val CORE_KTX = "1.8.0"
    const val ESPRESSO_CORE = "3.4.0"
    const val EXT_JUNIT = "1.1.3"
    const val JUNIT = "4.13.2"
    const val MATERIAL = "1.6.1"
    // ...
}
object Libs {
    const val APPCOMPAT = "androidx.appcompat:appcompat:${Versions.APPCOMPAT}"
    const val APP_STARTUP = "androidx.startup:startup-runtime:${Versions.APP_STARTUP}"
    const val CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:${Versions.CONSTRAINT_LAYOUT}"
    const val CORE_KTX = "androidx.core:core-ktx:${Versions.CORE_KTX}"
    const val ESPRESSO_CORE = "androidx.test.espresso:espresso-core:${Versions.ESPRESSO_CORE}"
    const val EXT_JUNIT = "androidx.test.ext:junit:${Versions.EXT_JUNIT}"
    const val JUNIT = "junit:junit:${Versions.JUNIT}"
    const val MATERIAL = "com.google.android.material:material:${Versions.MATERIAL}"
    // ...
}
object ClassPaths {
    const val ANDROID_GRADLE_PLUGIN = "com.android.tools.build:gradle:${Versions.ANDROID_GRADLE_PLUGIN}"
    const val KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.KOTLIN}"
    const val KOTLIN_SERIALIZATION = "org.jetbrains.kotlin:kotlin-serialization:${Versions.KOTLIN}"
    // ...
}

Sync Project 之后就能在 build.gradle 运用这些常量了。

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath ClassPaths.ANDROID_GRADLE_PLUGIN
        classpath ClassPaths.KOTLIN_GRADLE_PLUGIN
        classpath ClassPaths.KOTLIN_SERIALIZATION
    }
}
plugins {
    id 'com.android.library'
    id 'org.jetbrains.kotlin.android'
}
android {
    compileSdk Versions.COMPILE_SDK
    defaultConfig {
        minSdk Versions.MIN_SDK
        targetSdk Versions.TARGET_SDK
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }
    // ...
}
dependencies {
    api Libs.CORE_KTX
    api Libs.APPCOMPAT
    api Libs.MATERIAL
    api Libs.CONSTRAINT_LAYOUT
    // ...
}

buildSrc 支撑主动补全,显示语法高亮且能够点击跳转。略微有点小小不足是不能像第一种计划那样提示依靠有新版本,不过瑕不掩瑜。

独立调试与集成调试

经过设置 Gradle 的 plugin 能够装备 module 的类型,假如装备的是 com.android.application 插件,该模块就能打包运转。假如装备的是 com.android.library 插件,该模块就能被其它模块依靠运用。

所以当页面组件需求独立运转时就改成 com.android.application 插件,当需求集成调试被依靠时就改成 com.android.library 插件。一般会用一个调试开关变量来操控模块类型,所以咱们在 buildSrc 模块里增加一个 DEBUG_MODULE 常量。

object Plugins {
    const val DEBUG_MODULE = false
}

然后判别调试变量的值去设置不同的插件,这就只需修正变量的值并 Sync 一下就能切换模块类型。

if (Plugins.DEBUG_MODULE) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

apply plugin: 'xxxx' 是老的插件写法了,现在新建项目一般是新的 plugins DSL 写法:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

个人研讨了下在 id 后边加 apply() 函数能够操控是否应用该插件,所以改成下面的写法。

plugins {
    id 'com.android.application' apply(Plugins.DEBUG_MODULE)
    id 'com.android.library' apply(!Plugins.DEBUG_MODULE)
    id 'org.jetbrains.kotlin.android'
}

看似如同没什么问题,可是 Sync 一下就会有报错。

如何更好地进行 Android 组件化开发(一)实战篇

报错的意思是参数列表有必要是一个字面的 boolean 值,也便是有必要写 true 或许 false,目前看来写变量是不可的,那该怎样处理呢?个人想了很久实在没办法了,只好新老写法一同运用。

plugins {
    id 'org.jetbrains.kotlin.android'
}
if (Plugins.DEBUG_MODULE) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

注意 plugins {} 代码块要在最上面,这样编译才没问题。

当然只装备 application 插件仍是不足以让一个模块运转起来的,咱们还需求增加 applicationId,假如是 library 类型则不需求 applicationId。

并且 AndroidManifest 要做区分,想独立运转时在 AndroidManifest.xml 的 <appclication/> 节点要有图标、主题等信息,还要声明发动的 Activity。 假如事务模块已有的页面不适合做发动页,那么能够写个简单的测试页面。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.dylanc.componentization.account.impl">
    <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.ComponentizationSample">
        <activity
            android:name=".ui.SignInActivity"
            android:exported="false" />
        <activity
            android:name=".ui.TestActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

而作为 library 的时分只需把其它模块需求跳转的 Activity 声明即可,因为需求两份不同的装备,这儿在 src/main/manifest 文件夹新建了另一个 AndroidManifest.xml 文件。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.dylanc.componentization.account.impl">
    <application>
        <activity
            android:name=".ui.SignInActivity"
            android:exported="false"/>
    </application>
</manifest>

在事务组件的 build.gradle 依据开关变量的值设置 applicationId 和 AndroidManifest.xml 的途径。

android {
    defaultConfig {
        if (Plugins.DEBUG_MODULE) {
            applicationId "com.dylanc.componentization.account"
        }
        // ...
    }
    sourceSets {
        main {
            if (Plugins.DEBUG_MODULE) {
                manifest.srcFile 'src/main/AndroidManifest.xml' // 独立调试
            } else {
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml' // 集成调试
            }
        }
    }
}

创立组件模块时建议挑选 Phone & Tablet 新建一个 application 类型的模块,然后再更改 build.gradle 装备支撑切换成 library 类型。

如何更好地进行 Android 组件化开发(一)实战篇

假如反过来挑选 Android Library 新建一个 library 类型模块,想支撑 application 类型来独立运转还要增加发动页、图标、App 名称、主题等不少代码,比较麻烦。

再共享点小技巧,当咱们把前面的 DEBUG_MODULE 改为 true 时就能够独立调试各个模块,可是有的组件还会依靠于其它组件。比方社区发帖前肯定需求先登录,那么社区组件会依靠登录组件。当独立调试社区组件的时分,登录组件的模块也是 application 类型,此时是不能被依靠的。那么能够增加另一个变量来单独操控登录组件为 library 类型,这样就能正常调试社区组件了。

object Plugins {
    const val DEBUG_MODULE = true
    const val DEBUG_ACCOUNT_MODULE = false
}

还见过有人给每一个组件都增加变量,可能也是为了应对这种需求被依靠的状况,可是没有必要所有组件都一一对应一个调试变量,只需给独立调试时需求被依靠的组件增加调试变量即可。

代码阻隔

独立调试和集成调试其实只是切换 module 的类型,解耦问题还得靠代码阻隔。咱们要尽量避免组件之间直接引用,除了依靠根底开发模块之外,不应该直接依靠于任何事务组件,这样才干确保脱离了其它事务模块后也能编译经过。

比方许多事务功用都需求登录后才干运用,那么一些事务组件需求依靠于账户组件。假如直接依靠了账户组件,可能会有意无意地拜访到该组件的代码,增加了耦合度。可能有一天要在另一个 APP 运用新的账户体系,搭档写好了另一套账户组件,回来的账户信息都没有变。本来是把老账户组件的依靠换成新账户组件就能够,可是因为存在耦合,换依靠后可能会编译不过,这就无法独立调试了。

所以为了确保在各种状况下都正常独立调试组件,完结代码阻隔是十分必要的。这就能够用到 runtimeOnly 的依靠办法,这样依靠的模块只在运转时可用,咱们开发的时分是不能拜访到该模块的代码的,这就能完结代码阻隔。

dependencies {
    // ...
    runtimeOnly project(path: ':module-account')
}

经过代码阻隔就能下降事务组件间的耦合度,确保在各种状况下都正常独立调试或集成调试组件。不过这样就拜访不到组件的代码了,组件之间要怎样交互又是个新的问题。

页面导航跳转

完结代码阻隔后就不能拜访到该模块下的类,那怎样跳转页面呢?能够用 Android 的隐式 Intent 的办法,可是隐式 Intent 需求经过 AndroidManifest 会集办理,协作开发比较麻烦,所以一般是运用路由结构来完结事务组件间的页面跳转。

本文运用的是经典的路由结构 ARouter,还能够运用 TheRouter、WMRouter、等路由结构,用法和完结原理都是相似的。

依据 ARouter 的官方文档介绍,ARouter 是一个用于协助 Android App 进行组件化改造的结构,支撑模块间的路由、通讯、解耦。下面介绍下用法:

在 Kotlin 项目运用 ARouter 需求在 build.gradle 增加以下装备和依靠。

plugins {
    // ...
    id 'kotlin-kapt'
}
kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}
dependencies {
    implementation "com.alibaba:arouter-api:1.5.2"
    kapt "com.alibaba:arouter-compiler:1.5.2"
}

其间的 arouter-compiler 依靠会经过 APT 的办法生成代码,而生成代码的时分需求知道是在哪个模块,这是读取了 arguments 中的 AROUTER_MODULE_NAME 参数,所以还需求给 AROUTER_MODULE_NAME 设置为 project.getName()。简单来说便是上面的两段 kapt 代码有必要一同运用。

在 Application 初始化 ARouter:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            ARouter.openLog()
            ARouter.openDebug()
        }
        ARouter.init(this)
    }
}

给 Activity 增加 @Route 注解,这样类的信息就和 path 字符串树立映射关系。

@Route(path = "/account/sign_in")
class SignInActivity : BaseActivity<AccountActivitySignInBinding>() {
    // ...
}

path 由两部分组成,前面的 /account 是分组,为什么要加分组呢?假如只声明 sign_in,那多个组件都有 sign_in 路由的话,路由结构就不知道到底要用哪个了,加上分组就能躲避这个问题。注意一个模块内只能有一个分组,否则编译会不经过。

之后咱们就能用路由跳转到指定 path 的 Activity。

ARouter.getInstance().build("/account/sign_in").navigation()

调用 withXXXX() 函数能够传递参数,假如需求用 startActivityForResult(intent) 的办法跳转路由页面,要在 navigation() 函数加上 Activity 和 requestCode 参数。

ARouter.getInstance().build("/account/sign_in")
    .withString("email", email)
    .navigation(this, REQUEST_CODE_SIGN_IN)

现在 startActivityForResult(intent) 标记为弃用了,官方引荐用新的 ActivityResult API,可是目前的路由结构根本都不支撑,的确不太好适配,后边会单独发篇文章来讲讲怎样给路由结构适配 ActivityResult API。

获取 Fragment

跳转 Activity 是处理了,咱们开发中还会经常用到 Fragment,在拜访不了 Fragment 类的状况下怎样得到 Fragment 实例呢?一般路由结构还会支撑获取 Fragment。

咱们给 Fragment 增加 @Route 注解。

@Route(path = "/account/me")
class MeFragment : BaseFragment<AccountFragmentMeBinding>() {
    // ...
}

之后就能经过路由去实例化 Fragment 目标。

val meFragment = ARouter.getInstance().build("/account/me").navigation() as? Fragment

navigation() 函数是有回来值的,假如 path 对应的是一个 Activity 类,就会直接跳转页面并回来 null,假如 path 对应的是 Fragment 类,就会回来实例化的 Fragment 目标。不过回来的是 Object 类型,咱们需求强转成可空的 Fragment 类型。传递参数给 Fragment 也是同样调用 withXXXX() 函数。

组件间通讯

完结代码阻隔后拜访不到组件各个类的代码,怎样进行组件间的通讯?比方获取数据、调用办法、完结监听等。其实也能用路由结构处理。

比方咱们需求用账户组件判别是否登录,还有点击设置里的注销按钮时退出登录,先界说一个 AccountService 接口供给对应的办法,注意接口需求继承 IProvider

interface AccountService : IProvider {
  val isSignIn: Boolean
  fun signOut()
}

在账户组件增加该接口的完结类,并用 @Route 注解增加路由。

@Route(path = "/account/service")
class AccountServiceProvider : AccountService {
    override val isSignIn: Boolean
        get() = AccountRepository.isSignIn
    override fun signOut() {
        AccountRepository.signOut()
    }
    override fun init(context: Context) = Unit
}

之前就能经过路由获取对应 path 的接口实例,用法相似 Fragment,强转一下回来值。

val accountService = ARouter.getInstance().build("/account/service").navigation() as? AccountService
if (accountService?.isSignIn == true) {
    // ...
}

假如服务接口只要一个实例类,还能够用另一种获取办法,不传 path 字符串,直接传 Class 目标。这么用的时分最好将回来值声明为一个可空的类型,后边才不会忘了判空操作。

val accountService: AccountService? = ARouter.getInstance().navigation(AccountService::class.java)
if (accountService?.isSignIn == true) {
    // ...
}

还有一个很重要的问题,组件通讯的接口放在哪里?一般会用个单独的组件通讯模块来寄存所有组件路由接口,每个组件都依靠该模块完结自己事务的接口并供给路由。按照前面的架构图,咱们是放在中间层的 module-common 模块,这也是网上组件化较为常见的做法。

不过这样会存在中心化问题,所有组件模块都依附于一个组件通讯的 module-common 模块上。跟着事务的不断胀大,module-common 模块的代码会越来越臃肿,不仅仅只要组件通讯的接口,当需求获取组件的数据时会增加所需的 Bean 类,当需求监听时会增加对应的 Listener 类等。这样 module-common 模块会越来越凌乱,也很难知道每个组件对哪些组件接口有依靠,所以有必要去中心化。

去中心化

将组件通讯的 module-common 模块拆分红各个组件的 api 模块,比方有一个 module-account 组件,就会有对应的通讯模块 module-account-api。咱们修正一下前面架构图的中间层:

如何更好地进行 Android 组件化开发(一)实战篇

对中间层解耦后,运用一个组件都要增加两个依靠。

dependencies {
    // ...
    implementation project(path: ':module-account-api')
    runtimeOnly project(path: ':module-account')
}

尽管这么运用会略微有点麻烦,可是责任更加明晰。咱们能够把对应的 api 模块作为这个组件的协议,比方 module-account-api 模块有以下的类。

object AccountPaths {
    private const val GROUP = "/account"
    const val SERVICE = "$GROUP/service"
    const val FRAGMENT_ME = "$GROUP/me"
    const val ACTIVITY_SIGN_IN = "$GROUP/sign_in"
}
interface AccountService : IProvider {
    val isSignIn: Boolean
    val user: User
    fun signOut()
}

咱们经过 module-account-api 模块的代码就能知道能和 module-account 组件做些什么交互,能跳转哪些 Activity 能跳转,能获取哪些 Fragment。不需求查询组件文档或问搭档就能完结开发,削减运用和沟通本钱。假如发现该组件没有自己所需的功用,再反应给对应的开发搭档。

微信有个 api 计划,在组件增加 .api 后缀的文件,经过 gradle 脚本生成 xxxx-api 模块。这个思路蛮有意思的,不过个人觉得和手动 New Module 差不了多少,会增加些学习本钱,需求搭档额定了解一些开发标准,并不是很有必要,假如感兴趣的能够自行了解一下。

组件初始化

不管是独立调试用到各组件的 Application,仍是集成调试用到 App 壳的 Application,都需求确保所涉及组件的初始化逻辑能正常履行。这儿引荐运用 Jetpack 的一个组件 —— App Startup。App Startup 是用于应用程序在发动时初始化组件。

有不少开源库完结主动初始化会凭借 ContentProvider,尽管很奇妙,可是会增加许多额定的耗时。因为 ContentProvider 是 Android 四大组件之一,即便咱们的初始化操作很轻量,依靠 ContentProvider 后就变成了一个重量级的操作。官方测试一个空的 ContentProvider 大约会占用 2ms 的耗时,假如许多第三方库都用 ContentProvider 主动初始化,就会增加不少耗时。

所以官方推出了 App Startup,App Startup 也会创立一个 ContentProvider,不过供给了一套初始化的标准。官方期望第三方库都基于这套标准进行初始化,这样就能削减 ContentProvider 的数量,削减发动的耗时。

App Startup 是用于主动初始化,那也能很好地运用到组件化项目中。接下来讲下怎样运用,首先在 build.gradle 增加 App Startup 的依靠。

implementation "androidx.startup:startup-runtime:1.1.0"

写一个类继承 Initializer,在 create() 办法里完结初始化的逻辑。其间 dependencies() 函数能够操控在哪些 Initializer 之后再初始化,没有要求就回来个空列表。

class AccountInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        // 初始化
    }
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}

在事务模块的 AndroidManifest.xml 中增加 provider,这样就能完结主动初始化了。

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="com.dylanc.componentization.account.impl.AccountInitializer"
        android:value="androidx.startup" />
</provider>

上面的 <provider/> 一个固定的模版,需求修正的只要 android:name,改成对应 Initializer 完结类的全包名即可。

运用 App Startup 做初始化还有个优点是支撑自界说的初始化顺序,在重写的 dependencies() 函数回来其它 Initializer 的 Class 列表,这样就能在这些 Initializer 之后才初始化。

因为咱们做了代码阻隔拜访不到 Initializer 的代码,可能需求用 Class.forName(name) 得到 Class 目标,这么获取有点欠好是字符串不会和全包名同步,要人为确保共同。不过这个类一般不会动,其实也还好。

咱们能够在 api 模块界说一个特点获取 Initializer 的 Class,假如找不到类抛个反常提示一下。

@Suppress("UNCHECKED_CAST")
val accountInitializerClazz by lazy {
    (Class.forName("com.dylanc.componentization.account.AccountInitializer") as? Class<out Initializer<*>>)
        ?: throw IllegalStateException("Please depend on module-account.")
}

在其它模块的 Initializer 的 dependencies() 函数中回来该特点就能修正初始化顺序。

class NewsInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        // ...
    }
    // 在 AccountInitializer 之后初始化
    override fun dependencies() = listOf(accountInitializerClazz)
}

假如不期望事务组件主动初始化,咱们也能改成手动初始化。首先要在 provider 装备移除 Initializer,在 AndroidManifest.xml 增加以下装备:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="com.dylanc.componentization.account.impl.AccountInitializer"
        tools:node="remove" />
</provider>

关键代码是 tools:node="remove",这样在 merge 所有模块的 AndroidManifest.xml 时,会把对应的 meta-data 节点悉数删去,该 Initializer 就不会主动初始化了。

之后咱们只需在特定的时机再手动初始化即可,同样是用服务接口去获取 Initializer 的 Class 目标。

val accountService = ARouter.getInstance().navigation(AccountService::class.java)
    ?: throw IllegalStateException("Please depend on account-impl module.")
AppInitializer.getInstance(this).initializeComponent(accountService.initializerClazz)

总结

本文介绍了单一工程开发的缺点和组件化的优势,了解了组件化开发需求处理的问题和详细的处理计划,怎样独立和集成调试、完结代码阻隔、页面跳转、获取 Fragment、组件通讯、组件初始化等。其间有些是用路由结构来处理,本文运用的是 ARouter,咱们也能够挑选其它路由结构,用法都是大同小异。

其实组件化开发的步骤和原理并不难,但实践开发中还会遇到更多问题,比方怎样划分组件、有多套 UI 需求怎样处理等。下一篇文章会和咱们共享更多个人在实践开发中总结的经验,协助咱们更好地进行组件化开发。

示例代码:待弥补。

参考文献

  • 《“总算懂了” 系列:Android组件化,全面掌握!》
  • 《Android 组件化最佳实践》
  • 《Android组件化开发实践系列》

关于我

一个兴趣使然的程序“工匠” 。有代码洁癖,喜欢封装,对封装有一定的个人见解,有不少个人原创的封装思路。GitHub 有共享一些协助建立开发结构的开源库,有任何运用上的问题或许需求都能够提 issues 或许加我微信直接反应。

  • :/user/419539…
  • GitHub:github.com/DylanCaiCod…
  • 微信号:DylanCaiCoding