Android开发组件化架构设计原理到实战

目录
  • 为什么需要组件化
  • 组件化和模块化
  • 模块化架构
  • 组件化架构
    • 组件化带来的优势
    • 组件化需解决的问题
  • 资源冲突解决
    • AndroidManifest
  • 独立调试
    • 单工程方案
    • 多工程方案
  • 页面跳转
    • Arouter 实现组件间方法调用
  • 组件化的消息通信方式选择
    • 广播
    • 事件总线
  • Application生命周期分发

为什么需要组件化

小项目是不需要组件化的。当一个项目有数十个人开发,编译项目要花费10分钟,修改一个bug就可能会影响到其他业务,小小的改动就需要进行回归测试,如果是这种项目,那么我们需要进行组件化了

组件化和模块化

在技术架构演进的过程一定是先出现模块化后出现组件化,因为组件化就是解决了模块化的问题。

模块化架构

创建一个 Project 后可以创建多个 Module,这个 Module 就是所谓的模块。一个简单的例子,可能在写代码的时候我们会把首页、消息、我的模块拆开,每个 tab 所包含的内容就是一个模块,这样可以减少 module 的代码量,但是每个模块之间的肯定是有页面的跳转,数据传递等,比如 A 模块需要 B 模块的数据,于是我们会在 A 模块的 gradle 文件内通过 implementation project(':B')依赖 B 模块,但是 B 模块又需要跳转到 A 模块的某个页面,于是 B 模块又依赖了 A 模块。这样的开发模式依然没有解耦,改一个bug依然会改动很多模块,并不能解决大型项目的问题。

如下图所示,一开始我们定义的module之间并没有过多耦合:

然后,随着项目的不断迭代,相互调用的情况会增多,也会增加一些库的扩展和调用,工程的架构可能变为:

可以看出,各种业务之间的耦合非常严重,导致代码非常难以维护,更难以测试,扩展和维护性非常差,这样的架构肯定会被替代。

随着时间的推移,出现了组件化、插件化等组织架构。

组件化架构

这里先提几个概念,我们日常业务需求开发的组件叫做业务组件,如果这个业务需求是可以被普遍复用的,那么叫做业务基础组件,譬如图片加载、网络请求等框架组件我们称为基础组件。搭建所有组件的app组件称为壳组件/工程。接下来看一张架构图:

实线表示直接依赖关系,虚线表示间接依赖。比如壳工程肯定是要依赖业务基础组件、业务组件、module_common公共库的。业务组件依赖业务基础组件,但并不是直接依赖,而是通过”下沉接口“来实现间接调用。业务组件之间的依赖也是间接依赖。最后common组件依赖所有需要的基础组件,common也属于基础组件,它只是统一了基础组件的版本,同时也提供了给应用提供一些抽象基类,比如BaseActivity、BaseFragment,基础组件初始化等。

组件化带来的优势

  • 加快编译速度:每个业务组件都可以单独运行调试,速度提升好几倍。举个例子:video组件单独编译运行时间为3s,因为此时AS只会运行video组件以及video组件依赖的组件的task,而如果集成编译时间为10s,app所引用的所有的组件的task都会执行。可见,效率提升了3倍。
  • 提高协作效率:每个组件都有专人维护,不用关心其他组件是怎么实现的,只需要暴露对方需要的数据。测试也不需要整个回归,只需要重点测试修改的组件即可。
  • 功能重用:一次编码处处复用,再也不需要了。尤其是基础组件和业务基础组件,基本上调用者根据文档就可以一键集成和使用。
  • 确保了整体技术方案的统一性,为未来插件化公用一套底层模型做准备。

前面有提到非大型项目一般不会进行组件化,但是就像上面提到的功能重用,这个优势并不是只能用到大型项目 。我们可以在写需求或库时完全可以拥有组件化思想,把它们单独写成一个基础组件或业务基础组件。当第二个项目来的时候正好也需要这个组件,那我们就省去了拆出这个组件的时间(因为写需求的时候很可能会造成大量耦合,后续拆分要花费时间),比如登录组件,分享组件等等都是可以在一开始就写成组件的。

组件化需解决的问题

  • 如何解决资源配置冲突问题?
  • 业务组件如何实现单独调试?
  • 业务组件间没有依赖,如何实现页面跳转?
  • 业务组件间没有依赖,如何实现数据通信?
  • 业务组件间没有依赖,如何实现消息通信
  • 壳工程Application生命周期如何下发?

资源冲突解决

AndroidManifest

每个module都有一份AndroidManifest文件来记载信息,最终生成一个App的时候,只会有一份AndroidManifest来知道APP应该去如何配置,Manifest Merge Tools 会将多个AndroidManifest合成一个,但是又冲突需要解决。

在build/intermediates/manifest_merge_blame_file下会生成一份合并报告

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.liang.mosic"
   android:versionCode="1"
   android:versionName="1.0" >
   <uses-sdk
       android:minSdkVersion="21"
->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml
       android:targetSdkVersion="30" />
->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:12:5-67
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:12:22-64
    <application
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:6:5-48:19
        android:name="com.liang.mosic.ModuleApplication"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:10:9-42
        android:allowBackup="true"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:7:9-35
        android:appComponentFactory="androidx.core.app.CoreComponentFactory"
-->[androidx.core:core:1.5.0] C:\Users\Administrator.gradle\caches\transforms-2\files-2.1\4c9b62de2468f1520f5d232befb24ab8\core-1.5.0\AndroidManifest.xml:24:18-86
        android:debuggable="true"
        android:icon="@mipmap/ic_launcher"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:8:9-43
        android:label="@string/app_name"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:9:9-41
        android:supportsRtl="true"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:11:9-35
        android:testOnly="true"
        android:theme="@style/AppTheme" >
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:13:9-40
        <!-- <activity android:name=".MainActivity"> -->
        <!-- <intent-filter> -->
        <!-- <action android:name="android.intent.action.MAIN" /> -->
        <!-- <category android:name="android.intent.category.LAUNCHER" /> -->
        <!-- </intent-filter> -->
        <!-- </activity> -->
        <activity
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:21:9-28:20
            android:name="com.liang.mosic.AdaviceActivity"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:21:19-50
            android:theme="@style/AppWelcome" >
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:22:13-46
            <intent-filter>
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:23:13-27:29
                <action android:name="android.intent.action.MAIN" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:17-69
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:25-66
                <category android:name="android.intent.category.LAUNCHER" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:17-77
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:27-74
            </intent-filter>
        </activity>
        <activity android:name="com.liang.mosic.ModuleMainActivity" >
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:29:9-40:20
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:29:19-53
            <!-- <intent-filter> -->
            <!-- <action android:name="android.intent.action.MAIN" /> -->
            <!-- <category android:name="android.intent.category.LAUNCHER" /> -->
            <!-- </intent-filter> -->
            <intent-filter>
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:35:13-39:29
                <action android:name="com.liang.main" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:36:17-60
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:36:25-57
                <category android:name="android.intent.category.DEFAULT" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
            </intent-filter>
        </activity>
        <activity android:name="com.liang.mosic.ModuleExampleActivity" >
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:41:9-47:20
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:41:19-56
            <intent-filter>
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:42:13-46:29
                <action android:name="com.liang.moduleFragment" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:43:17-70
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:43:25-67
                <category android:name="android.intent.category.DEFAULT" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
            </intent-filter>
        </activity>
        <activity android:name="com.liang.a.MainActivity" >
-->[:a] C:\Users\Administrator\Desktop\mosic-master\mosic-master\a\build\intermediates\library_manifest\debug\AndroidManifest.xml:17:9-23:20
-->[:a] C:\Users\Administrator\Desktop\mosic-master\mosic-master\a\build\intermediates\library_manifest\debug\AndroidManifest.xml:17:19-61
            <intent-filter>
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:23:13-27:29
                <action android:name="android.intent.action.MAIN" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:17-69
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:25-66
                <category android:name="android.intent.category.LAUNCHER" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:17-77
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:27-74
            </intent-filter>
        </activity>
        <activity android:name="com.liang.b.BActivity" >
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:21:9-27:20
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:21:19-58
            <intent-filter>
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:22:13-26:29
                <action android:name="com.liang.b.act" />
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:23:17-61
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:23:25-58
                <category android:name="android.intent.category.DEFAULT" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
            </intent-filter>
        </activity>
    </application>
</manifest>

最终编译出的app会将其他所有module的AndroidManifest文件合并,合并规则为:

  • 如果功能module有Application,主module没有声明,则使用功能module的Application;
  • 如果主module有定义Application,其他module没有,则使用主module的;
  • 如果功能module有多个自定义Application,解决冲突后使用;
  • 如果主module有Application,功能module也有,则解决冲突后,最后编译的主module的Application会在AndroidManifest中。

如果子module中声明icon、theme等属性,会导致合并冲突,需要申明属性:

xmlns:tools="http://schemas.android.com/tools"
tools:replace="android:icon,android:theme"

权限申明:

在子module中申明的权限,会集成到主module中,四大组件也是相同的规则。shareUid只有在主module中申明,才会打包到最终的AndroidManifest中。

独立调试

单工程方案

所谓的单工程方案就是把所有组件都放到一个工程下,先看一下整体的目录:

ps

module_ 开头表示基础组件,

fun_ 前缀表示业务基础组件,

biz_前缀表示业务组件,

export_前缀表示业务组件暴露接口。

单工程利弊分析:

  • 利:一个模块修改后只需要编译一下,依赖它的其他模块就能马上感知到变化。
  • 弊:没能做到完全的职责拆分,每个模块的开发者都有修改其他模块的权限。

首先在 gradle.properties 文件内声明一个变量:

// gradle.properties
isModule = true

isModule 为 true 时表示组件可以作为 apk 运行起来,false 表示组件只能作为 library。我们根据需要改变这个值后同步下gradle即可。

然后在某个 module 的 build.gradle 文件内用这个变量做三个地方的判断:

// build.gradle
// 区分是应用还是库
if(isModule.toBoolean()) {
	apply plugin: 'com.android.application'
}else {
	apply plugin: 'com.android.library'
}
android {
	defaultConfig {
		// 如果是应用需要指定application
		if(isModule.toBoolean()) {
			applicationId "com.xxx.xxx"
		}
	}
	sourceSets {
		main {
			// 应用和库的AndroidManifest文件区分
			if(isModule.toBoolean()) {
				manifest.srcFile 'src/main/debug/AndroidManifest.xml'
			}else {
				manifest.srcFile 'src/main/AndroidManifest.xml'
			}
		}
	}
}

由于library是不需要 Application 和启动Activity页,所以我们要区分这个文件,应用manifest指定的路径没有特定,随意找个路径创建即可。在应用AndroidManifest.xml里我们要设置启动页:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sun.biz_home">
    <application
        android:allowBackup="true"
        android:label="@string/home_app_name"
        android:supportsRtl="true"
        android:theme="@style/home_AppTheme">
        <activity android:name=".debug.HomeActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

library 的 AndroidManifest.xml 不需要这些:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sun.biz_home">
</manifest>

gradle 依赖 module 的方式主要有两种:

  • implementation: A implementation B,B implementation C, 但 A 不能访问到 C 的东西。
  • api:A api B,B api C,A能访问到C的东西。

一般来说我们只需要使用 implementation 即可,api 是会造成项目编译时间变长,而且会引入该模块不需要的功能,代码之间耦合变得严重了。不过 module_common 是统一了基础组件版本的公共库,所有组件都应需要依赖它并拥有基础组件的能力,所以基本每个业务组件和业务基础组件都应该依赖公共库:

dependencies {
	implementation project(':module_common')
}

而 common 组件依赖基础组件应该是用 api,因为把基础组件的能力传递给上层业务组件:

dependencies {
	api project(':module_base')
	api project(':module_util')
}

多工程方案

多工程就是每个组件都是一个工程,例如创建一个工程后 app 作为壳组件,它依赖 biz_home 运行,因此不需要 isModule 来控制独立调试,它本身就是一个工程可以独立调试。

多工程的利弊就是和单工程相反的:

  • 利:做到职责完全拆分,其他项目复用更加方便,直接一行依赖引入。
  • 弊:修改后需要上传到maven仓库,其他工程再次编译后才能感知到变化,多了上传和编译的时间。

多工程组件依赖需要用到maven仓库。把每个组件的aar上传到公司内网的maven仓库,然后像这样去依赖:

implementation 'com.xxx.xxx:module_common:1.0.0'

我们把三方库统一放到 config.gradle 内管理:

ext {
    dependencies = [
            "glide": "com.github.bumptech.glide:glide:4.12.0",
            "glide-compiler": "com.github.bumptech.glide:compiler:4.12.0",
            "okhttp3": "com.squareup.okhttp3:okhttp:4.9.0",
            "retrofit": "com.squareup.retrofit2:retrofit:2.9.0",
            "retrofit-converter-gson"  : "com.squareup.retrofit2:converter-gson:2.9.0",
            "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:2.9.0",
            "rxjava2": "io.reactivex.rxjava2:rxjava:2.2.21",
            "arouter": "com.alibaba:arouter-api:1.5.1",
            "arouter-compiler": "com.alibaba:arouter-compiler:1.5.1",
            // our lib
            "module_util": "com.sun.module:module_util:1.0.0",
            "module_common": "com.sun.module:module_common:1.0.0",
            "module_base": "com.sun.module:module_base:1.0.0",
            "fun_splash": "com.sun.fun:fun_splash:1.0.0",
            "fun_share": "com.sun.fun:fun_share:1.0.0",
            "export_biz_home": "com.sun.export:export_biz_home:1.0.0",
            "export_biz_me": "com.sun.export:export_biz_me:1.0.0",
            "export_biz_msg": "com.sun.export:export_biz_msg:1.0.0",
            "biz_home": "com.sun.biz:biz_home:1.0.0",
            "biz_me": "com.sun.biz:biz_me:1.0.0",
            "biz_msg": "com.sun.biz:biz_msg:1.0.0"
    ]
}

这样方便版本统一管理, 然后在根目录的 build.gradle 内导入:

apply from: 'config.gradle'

最后在各自的模块引入依赖,比如在 module_common 中这么引入依赖即可。

dependencies {
	api rootProject.ext.dependencies["arouter"]
  kapt rootProject.ext.dependencies["arouter-compiler"]
  api rootProject.ext.dependencies["glide"]
  api rootProject.ext.dependencies["okhttp3"]
  api rootProject.ext.dependencies["retrofit"]
  api rootProject.ext.dependencies["retrofit-converter-gson"]
  api rootProject.ext.dependencies["retrofit-adapter-rxjava2"]
  api rootProject.ext.dependencies["rxjava2"]
  api rootProject.ext.dependencies["module_util"]
  api rootProject.ext.dependencies["module_base"]
}

个人觉得多工程适合"很大"的工程,每个业务组件可能都需要一个组开发,类似淘宝这样的app。但这只是针对业务组件来说的,业务基础组件和基础组件修改的频率不会很大,最好都是单工程上传至maven仓库来使用。本文的例子是为了方便所以把所有组件写到一起了,最好的方式就是把 fun_ 和 module_ 开头的组件都拆分成单工程独立开发,业务组件写到一个工程内。

页面跳转

做完组件之间的隔离后,暴露出来最明显的问题就是页面跳转和数据通信的问题。一般来说,页面跳转都是显示startActivity跳转,在组件化项目内就不适用了,隐式跳转可以用,但每个Activity都要写 intent-filter 就显得有点麻烦,如下所示:

<activity android:name="com.liang.lib_video.videoplayer.VideoActivity">
    <intent-filter>
        <action android:name="com.intent.openVideoActivity"></action>
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
Intent intent = new Intent();
intent.setClass("包名","Activity路径");
intent.setComponent(new ComponentName("包名"));
startActivity(intent);

使用上述方式跳转会奔溃,提示在AndroidManifest文件中注册,这里需要注意的是,setClass/setComponent是APP的包名而不是所在module的包名。 可以参考最终生成的AndroidManifest文件。使用隐式跳转也可以先用resolveActivity进行验证。 如果不想要其他应用通过隐式打开,需要设置exported=false。

隐式跳转是整个系统都能接收到,会相对想好性能,所以最好的方式还是用路由框架。

实际上市面已经有比较成熟的路由框架专门就是为了组件化而生的,比如美团的WMRouter,阿里的ARouter等,本例使用 ARouter 框架,看下ARouter页面跳转的基本操作。

首先肯定是引入依赖,以 module_common 引入ARouter举例,build.gradle 应该添加:

android {
	defaultConfig {
		javaCompileOptions {
       annotationProcessorOptions {
          arguments = [AROUTER_MODULE_NAME: project.getName()]
     	 }
    }
	}
	compileOptions {
      sourceCompatibility JavaVersion.VERSION_1_8
      targetCompatibility JavaVersion.VERSION_1_8
  }
}
dependencies {
	api rootProject.ext.dependencies["arouter"]
  kapt rootProject.ext.dependencies["arouter-compiler"]
}

kapt注解依赖没有办法传递,所以我们不可避免得需要在每个模块都声明这些配置,除了 api rootProject.ext.dependencies["arouter"] 这行。然后需要全局注册 ARouter,我是在 module_common 统一注册的。

class AppCommon: BaseApp{
    override fun onCreate(application: Application) {
        MLog.d(TAG, "BaseApp AppCommon init")
        initARouter(application)
    }
    private fun initARouter(application: Application) {
        if(BuildConfig.DEBUG) {
            ARouter.openLog()
            ARouter.openDebug()
        }
        ARouter.init(application)
    }
}

接着我们在 module_common 模块内声明一个路由表用作统一管理路径。

// RouterPath.kt
class RouterPath {
    companion object {
        const val APP_MAIN = "/app/MainActivity"
        const val HOME_FRAGMENT = "/home/HomeFragment"
        const val MSG_FRAGMENT = "/msg/MsgFragment"
        const val ME_FRAGMENT = "/me/MeFragment"
        const val MSG_PROVIDER = "/msg/MsgProviderImpl"
    }
}

然后在MainActivity类文件上进行注解:

@Route(path = RouterPath.APP_MAIN)
class MainActivity : AppCompatActivity() {
}

任意模块只需要调用 ARouter.getInstance().build(RouterPath.APP_MAIN).navigation() 即可实现跳转。如果我们要加上数据传递也很方便:

ARouter.getInstance().build(RouterPath.APP_MAIN)
            .withString("key", "value")
            .withObject("key1", obj)
            .navigation()

然后在MainActivity使用依赖注入接受数据:

class MainActivity : AppCompatActivity() {
    @Autowired
    String key = ""
}

Arouter 实现组件间方法调用

在 export_biz_msg 组件下声明 IMsgProvider,此接口必须实现 IProvider 接口:

interface IMsgProvider: IProvider {
    fun onCountFromHome(count: Int = 1)
}

然后在 biz_msg 组件里实现这个接口:

@Route(path = RouterPath.MSG_PROVIDER)
class MsgProviderImpl: IMsgProvider {
    override fun onCountFromHome(count: Int) {
    	  // 这里只是对数据进行分发,有监听计数的对象会收到
        MsgCount.instance.addCount(count)
    }
    override fun init(context: Context?) {
        // 对象被初始化时调用
    }
}

在 biz_home 首页组件中发送计数:

val provider = ARouter.getInstance().build(RouterPath.MSG_PROVIDER).navigation() as IMsgProvider
provider.onCountFromHome(count)

可以看到其实和页面跳转的方式基本雷同,包括获取 Fragment 实例的方式也是这种。ARouter把所有通信的方式都用一种api实现,让使用者上手非常容易。

组件化的消息通信方式选择

广播

作为Android中四大组件之一,Broadcast的职责是用于Android系统通信,但是普通的广播是全局广播,会造成安全泄露以及效率问题,如果只是在应用内部通知,可以使用更为高效的LocalBroadCast,相对于全局广播,本地广播只会在APp内部传播,不会造成隐私泄露,同时无法接受其他应用发送的广播,相对于全局广播来说更加高效。

事件总线

由于系统级别的广播传递比较耗时,消息通信科使用通过记录对象、使用监听者模式实现的事件总线框架,比如EventBus、LivaData等。

通过将消息的公用部分 ,如自定义消息的bean放入到baseMOdule下的单独模块来实现组件间消息的传递。组件化的数据库存储和消息通信的实现方式大同小异,都是将公用的东西放入到baseModule,如果内容比较多或者对于职责界限划分要求高的话可在base下新建一个DataBaseModule

Application生命周期分发

当 app 壳工程启动Application初始化时要通知到其他组件初始化一些功能。这里提供一个简单的方式。

首先我们在 module_common 公共库内声明一个接口 BaseApp:

public interface BaseAppInit {
    boolean onInitCreate(Application application);
    boolean onInitTerminal(Application application);
}

然后每个组件都要创建一个 App 类实现此接口,比如在某个业务组件:

public class AudioInit implements BaseAppInit {
    @Override
    public boolean onInitCreate(Application application) {
        return false;
    }
    @Override
    public boolean onInitTerminal(Application application) {
        return false;
    }
}

剩下最后一步就是从 app 壳工程分发 application 的生命周期了,这里用到反射技术:

val moduleInitArr = arrayOf(
    "com.liang.lib_audio.app.AudioInit",
    "com.liang.lib_audio.app.VideoInit",
    "com.liang.lib_audio.app.LoginInit",
)
class App: Application() {
    override fun onCreate() {
        super.onCreate()
        initModuleApp(this)
    }
    private fun initModuleApp(application: Application) {
        try {
            for(appName in moduleInitArr) {
                val clazz = Class.forName(appName)
                val module = clazz.getConstructor().newInstance() as BaseApp
                module.onCreate(application)
            }
        }catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

我们只需要知道的每个实现 BaseApp 接口的类的全限定名并写到moduleInitArr数组里,然后通过反射获取 Class 对象从而获取构造函数创建实体对象,最后调用 BaseApp 的 onCreate 方法将 application 传入,每个Application生命周期的方法都可以通过这种方式传递。由于反射会消耗一定的性能,这个操作可以放在子线程,然后线程间通信。 当然,在每个module定义相对应的初始化方法,然后主module 调用也可以实现初始化,此处使用反射是为了最大程度的解耦。

以上就是Android开发组件化架构设计原理到实战的详细内容,更多关于Android组件化架构设计的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android 架构之数据库框架搭建

    目录 1.先创建对应相关操作的注解 1.1 bTable 标识表 1.2 DbPrimaryKey 标识主键 1.3 DbFiled 标识成员属性 2.创建对应表操作类Dao层 2.1 建 待实现的基层 IBaseDao 2.2 建已实现的基层 BaseDao 2.3 建对应model 的Dao层 3.创建数据库工厂 4.创建对应model 5.最终使用 前言: 你还在苦恼的写SQL么?你还在为数据库升级而烦恼么?你还在因查询数据而写繁琐不可用的代码么? 在这,这些都将不复存在!在本篇中,将会让

  • Android Jetpack架构组件Lifecycle详解

    前言 Lifecycle是Jetpack架构组件中用来感知生命周期的组件,使用Lifecycles可以帮助我们写出和生命周期相关更简洁更易维护的代码. 生命周期 生命周期这个简单而又重要的知识相信大家早已耳熟能详.假设我们现在有这样一个简单需求: 这个需求只是一个实例,在真实的开发中当然不可能有这样的需要: 在Activity 可见的时候,我们去做一个计数功能,每隔一秒 将计数加1 ,当Activity不可见的时候停止计数,当Activity被销毁的时候 将计数置为0 OK,So easy~ ,

  • Android组件化开发路由的设计实践

    调研了一下目前的路由框架,ARouter(阿里的),ActivityRouter都使用了apt技术 编译时注解,个人想法是一口吃不成胖子,先做个比较实用的. VpRouter路由框架主要应用于组件化开发中 设计目的 解耦 跨模块跳转 方便服务器配置schema,实现动态配置跳转目标 对外部提供远程访问的功能,实现跨应用调用响应 主要功能点 支持intent,http,schema三种跳转 路由表支持xml配置,可自定义,支持多路径 有拦截器 同时支持反射和隐式意图 支持结果回调 支持参数传递 链

  • android module解耦组件化总体概述(推荐)

    原由 移动开发中,随着项目不断的跌代,需求越来越复杂后.项目工程也越来越庞大.那么此时的分module的开发,则是必然的选择了.在最终的组件化之路上,不妨把单一工程比如石器时代,那么接下来简单的拆分工程分多个moudle开来就是铜器时代. 铜器时代之简单分module 演进 由于从复杂的单工程拆分了多个module了,达到了代码及资源的初步的隔离,或需求模块的开发人员,开始专注于自己的需求模块module的开发了.但是随着部分需求有相关性,需要相互调用时.那么问题来了,在AXXX module中

  • Android操作系统的架构设计分析

    之前一直在Android应用层上做工作,最近开始研究Android平台上的东东了,主要是在Android Frameworks层和系统库层进行研究.以下是我自己的理解,领悟,希望与大家一块分享. Android系统架构分为Linux内核驱动.C/C ++框架.Java框架.Java应用程序. Android应用层: Android应用程序需要Java框架支持.主要是针对手机用户的.Android应用层都是由Java代码写的,运行在虚拟机中.虚拟机在Android平台中扮演着很重要的角色.虚拟机在

  • Android实现MVVM架构数据刷新详解流程

    目录 效果图 示例结构图 代码解析 导入dataBinding 实体类 xml视图 VM 绑定视图与数据层 效果图 示例结构图 代码解析 导入dataBinding dataBinding{ enabled = true } 实体类 继承BaseObservable public class Sensor extends BaseObservable 为字段添加@Bindable @Bindable public String getTmpValue() { return tmpValue; }

  • Android开发组件化架构设计原理到实战

    目录 为什么需要组件化 组件化和模块化 模块化架构 组件化架构 组件化带来的优势 组件化需解决的问题 资源冲突解决 AndroidManifest 独立调试 单工程方案 多工程方案 页面跳转 Arouter 实现组件间方法调用 组件化的消息通信方式选择 广播 事件总线 Application生命周期分发 为什么需要组件化 小项目是不需要组件化的.当一个项目有数十个人开发,编译项目要花费10分钟,修改一个bug就可能会影响到其他业务,小小的改动就需要进行回归测试,如果是这种项目,那么我们需要进行组

  • 解析rainbond以应用为中心的架构设计原理

    目录 前言碎语 一.云计算的发展 二.企业价值与IT 三.服务模式 四.以应用为中心的产品设计 应用的生产阶段 应用运行阶段 应用传播阶段 五.面向未来 前言碎语 今天博主安利一个国产开源的无服务器容器云平台,关注它已经有一年多了,虽然其迭代到现在很多功能还是一直处于测试验证中,但是其设计理念以应用为中心,我觉得这个是未来的趋势. 其实以docker+k8s这种架构也是一种以应用为中心的架构,rainbond底层深度集成k8s,为用户提供云原生应用全生命周期解决方案,构建应用与基础设施.应用与应

  • ​​​​​​​Android H5通用容器架构设计详解

    目录 背景 术语对齐 探索 如何优雅地提供接口调用? 怎样封装多个不同类型的H5容器容器? 整体架构 通用容器 框架容器 基础组件 这样的架构能带来什么样的好处? 背景 大家如果经历过Hybrid项目的开发,即项目中涉及到H5与Native之间的交互,那么很有可能会遇到各种各样的H5容器.为什么会有那么多各种各样的容器呢...这也是轮子多的通病了,轮子多到业务方不知道选哪个.当然,也有可能大家压根就不会使用到H5容器,直接用系统WebView就完事儿了,比如我的前东家就是这样做的.那这篇文章的主

  • Android开发组件flutter的20个常用技巧示例总结

    目录 1.map遍历快速实现边距,文字自适应改变大小 2.使用SafeArea 添加边距 3.布局思路 4.获取当前屏幕的大小 5.文本溢出显示省略号 6.一个圆角带搜索icon的搜索框案例 7.修改按钮的背景色 8.tab切换实例 9.点击事件组件点击空白区域不触发点击 10.使用主题色 11.往安卓模拟器中传图片 12.控制text的最大行数显示影藏文字 13.去掉默认的抽屉图标 14.图片占满屏 15.倒计时 16.固定底部 17.添加阴影 18.隐藏键盘 19.获取父级组件大小 20.点

  • Spring Cloud 中自定义外部化扩展机制原理及实战记录

    目录 自定义PropertySource 扩展PropertySourceLocator Spring.factories 编写controller测试 阶段性总结 SpringApplication.run PropertySourceBootstrapConfiguration.initialize ApplicationContextInitializer的理解和使用 创建一个TestApplicationContextInitializer 添加spi加载 Spring Cloud针对E

  • 详解Android业务组件化之URL Schema使用

    前言: 最近公司业务发展迅速,单一的项目工程不再适合公司发展需要,所以开始推进公司APP业务组件化,很荣幸自己能够牵头做这件事,经过研究实现组件化的通信方案通过URL Schema,所以想着现在还是在预研阶段,很有必要先了解一下URL Schema,看看是如何使用的?其实在之前做Hybrid混合编程的时候就接触过URL Schema,总来的来说还不算陌生,今天就来回顾总结一下. 什么是 URL Schema?  android中的scheme是一种页面内跳转协议,是一种非常好的实现机制,通过定义

  • Android组件化原理详细介绍

    目录 什么是组件化? 为什么使用组件化? 一步步搭建组件化 1.新建模块 2.统一Gradle版本号 3.创建基础库 4.组件模式和集成模式转换 5.AndroidManifest的切换 6.*业务Application切换 组件之间的跳转 1.添加依赖 2.初始化ARouter 3.添加跳转 组件之间的数据传递 1.定义接口 2.实现接口 组件Application的动态切换 1.定义抽象类 BaseApplication 继承 Application 2.所有的组件的 Application

  • 一条sql详解MYSQL的架构设计详情

    目录 1 前言 2 应用层 2.1 连接线程处理 3 服务层 3.1 SQL 接口 3.2 SQL解析器 3.3 SQL优化器 3.4 执行器 3.5 查询缓存 4 存储引擎层 4.1 概述 4.2 缓冲池(buffer pool) 4.2.1 数据页.缓存页和脏页 4.2.2 元数据 4.2.3 free链表 4.2.4 flush链表 4.2.5 LRU链表 4.2.6 小结 4.3 undo log 4.4 redo log 5 总结 1 前言 对于一个服务端开发来说 MYSQL 可能是他

  • Android开发Jetpack组件Lifecycle原理篇

    目录 前言 1.Lifecycle的生命周期状态事件和状态 2.Lifecycle如何观察Activity和Fragment的生命周期 前言 在上一篇文章中,我们学习了如何去使用Lifecycle: 当然之会使用是不够的,还需要了解它的原理,这是成为优秀工程师必备的:这篇文章就来学习Lifecycle的基本原理 1.Lifecycle的生命周期状态事件和状态 **Lifecycle使用两个枚举来跟踪其关联组件的生命周期状态,这两个枚举分别是Event和State:**State指的是Lifecy

随机推荐