盒子
盒子
文章目录
  1. 为何要使用组件化
  2. 组件化能够解决的问题
  3. 组件化需要解决的问题
    1. 组件单独运行
      1. 运行方式动态配置
      2. ApplicationId与AndroidManifest
    2. 组件间的数据传递
      1. BridgeInterface
      2. BridgeStore
      3. Factory
      4. Provider

AwesomeGithub组件化探索之旅

之前一直听说过组件化开发,而且面试也有这方面的提问,但都未曾有涉及具体的项目。所以就萌生了基于Github的开放Api,并使用组件化的方式来从零搭建一个Github客户端,起名为AwesomeGithub

在这里对组件化开发进行一个总结,同时也希望能够帮助别人更好的理解组件化开发。

先来看下项目的整体效果

AwesomeGithub

下面是项目的结构

为何要使用组件化

  1. 对于传统的开发模式,一个app下面是包含项目的全部页面模块与逻辑。这样项目一旦迭代时间过长,业务模块逐渐增加,相应的业务逻辑复杂度也成指数增加。模块间的互相调用频繁,这样必定会导致模块间的耦合增加,业务逻辑嵌套程度加深。一旦修改其中一个模块,可能就牵一发动全身了。
  2. 传统的开发模式不利于团队的集体开发合作,因为每个开发者都是在同一个app模块下开发。这样导致的问题是,不能预期每个开发者所会修改到的具体代码部分,即所能够修改的代码区域。因为模块耦合在一起,涉及的区域不可预期,导致不同开发者会修改同一个文件或者同一段代码逻辑,从而导致异常冲突。
  3. 传统开发模式不利于测试,每次迭代都要将项目整体测试一遍。因为在同一个app下面代码是缺乏约束的,你不能保证只修改了迭代过程中所涉及的需求逻辑。

以上问题随着项目的迭代周期的增大,会表现的越来越明显。那么使用组件化又能够解决什么问题了?

组件化能够解决的问题

  1. 组件化开发是将各个相关功能进行分离,分别独立成一个单独可运行的app,并且组件之间不能相互直接引用。这样就减少了代码耦合,达到业务逻辑分层效果。
  2. 组件化可以提高团队协作能力,不同的人员可以开发不同的组件,保证不同开发人员互不影响。
  3. 组件化将app分成多个可单独运行的子项目,可以用自己独立的版本,可以独立编译,打包、测试与部署。这样不仅可以提高单个模块的编译速度,同时也可以提高测试的效率。
  4. 组件化可以提高项目的灵活性,app可以按需加载所要有的组件,提高app的灵活性,可以快速生成可定制化的产品。

现在我们已经了解了组件化的作用,但要实现组件化,达到其作用,必须解决实现组件化过程中所遇到的问题。

组件化需要解决的问题

  1. 组件单独运行
  2. 组件间数据传递
  3. 组件间界面的跳转
  4. 主项目使用组件中的Fragment
  5. 组件解耦

以上是实现组件化时所遇到的问题,下面我会结合AwesomeGithub来具体说明解决方案。

组件单独运行

组件的创建,可以直接使用library的方式进行创建。只不过在创建完之后,要让组件达到可以单独运行调试的地步,还需要进行相关配置。

运行方式动态配置

首先,当创建完library时,在build.gradle中可以找到这么一行代码

1
apply plugin: 'com.android.library'

这是gradle插件所支持的一种构建类型,代表可以将其依赖到主项目中,构建后输出aar包。这种方式对于我们将组件依赖到主项目中完全吻合的。

而gradle插件的另一种构建方式,可以在主项目的build.gradle中看到这么一行代码

1
apply plugin: 'com.android.application'

这代表在项目构建后会输出apk安装包,是一个独立可运行的项目。

明白了gradle的这两种构建方式之后,我们接下需要做的事也非常明了:需要将这两种方式进行动态配置,保证组件在主项目中以library方式存在,而自己单独的时候,则以application的方式存在。

下面我以AwesomeGithub中的login组件为例。

首先我们在根项目的gradle.properties中添加addLogin变量

1
addLogin = true

然后在login中的build.gradle通过addLogin变量来控制构建方式

1
2
3
4
5
if (addLogin.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}

这样就实现了对login的构建控制,可单独运行,也可依赖于app项目。

ApplicationId与AndroidManifest

除了修改gradle的构建方式,还需要动态配置ApplicationId与AndroidManifest文件。

有了上面的基础,实现方式也很简单。

可以在defaultConfig中增加对applicationId的动态配置

1
2
3
4
5
6
7
8
9
10
11
defaultConfig {
if (!addLogin.toBoolean()) {
applicationId "com.idisfkj.awesome.login"
}
minSdkVersion Versions.min_sdk
targetSdkVersion Versions.target_sdk
versionCode Versions.version_code
versionName Versions.version_name

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

而AndroidManifest文件可以通过sourceSets来配置

1
2
3
4
5
6
7
8
9
sourceSets {
main {
if (addLogin.toBoolean()) {
manifest.srcFile 'src/main/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
}
}
}

同时addLogin也可以作用于app,让login组件可配置依赖

这样login组件就可以独立于app进行单独构建、打包、调试与运行。

组件间的数据传递

由于组件与组件、项目间是不能直接使用类的相互引用来进行数据的传递,所以为了解决这个问题,这里通过一个公共库来做它们之间调用的桥梁,它们不直接拿到具体的引用对象,而是通过接口的方式来获取所需要的数据。

AwesomeGithub中我将其命名为componentbridge,各个组件都依赖于该公共桥梁,通过该公共桥梁各个组件间可以轻松的实现数据传递。

上图圈起来的部分都是componentbridge的重点,也是公共桥梁实现的基础。下面来分别详细说明。

BridgeInterface

这是公共桥梁的底层接口,每一个组件要向外实现自己的桥梁都要实现这个接口。

1
2
3
4
interface BridgeInterface {

fun onClear() {}
}

内部很简单,只有一个方式onClear(), 用来进行数据的释放。

BridgeStore

用来做数据存储,对桥梁针对不同的key进行缓存。避免桥梁内部的实例多次创建。具体实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BridgeStore {

private val mMap = HashMap<String, BridgeInterface>()

fun put(key: String, bridge: BridgeInterface) {
mMap.put(key, bridge)?.onClear()
}

fun get(key: String): BridgeInterface? = mMap[key]

fun clear() {
for (item in mMap.values) {
item.onClear()
}
mMap.clear()
}
}

Factory

桥梁的实例构建工厂,默认提供通过反射的方式来实例化不同的类。Factory接口只提供一个create方法,实现方式由子类自行解决

1
2
3
4
interface Factory {

fun <T : BridgeInterface> create(bridgeClazz: Class<T>): T
}

AwesomeGithub中提供了通过反射方式来实例化不同类的具体实现NewInstanceFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class NewInstanceFactory : Factory {

companion object {
val instance: NewInstanceFactory by lazy { NewInstanceFactory() }
}

override fun <T : BridgeInterface> create(bridgeClazz: Class<T>): T = try {
bridgeClazz.newInstance()
} catch (e: InstantiationException) {
throw RuntimeException("Cannot create an instance of $bridgeClazz", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("Cannot create an instance of $bridgeClazz", e)
}

}

Factory的作用是通过抽象的方式来获取所需要类的实例,至于该类如何实例化,将通过create方法自行实现。

Provider

支持一下
赞赏是一门艺术