为什么需要组件化

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

组件化和模块化

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

接口测试块化架构

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

组件化架构

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

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

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

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

组件化带来的优势

**加快编译速度:**每个业通信大数据行程卡务组件都可以单独运行调试,速度提升好几倍github汤姆。举个例子:video组件单独编译运行时间为3s,因为此时A架构是什么意思S只会运行video组件以及video组件依赖的组件的task,而如果集成编译时间为10s,app所引用的所有的组件的task都会执行。可见,效率提升了3倍。

**提高协作效率:**每个组件都有专人维护,不用关心其他组件是架构是什么意思怎么实现的,只需要暴露对方需要的数接口据。测试也不需要整个回通信技术归,只需接口要重点测试修架构图改的组件接口和抽象类的区别即可。

**功能重用:**一次编码处处复用,再也不需要复制代码了。尤其是基础组件和业务基础组件,基本上调用者根接口和抽象类的区别据文档就可以一键集成和使用。

前面有提到非大型项目一般不会进行组件化,但是就像上面提到的功能重用,这个优势并不是只能用到大型项目 。我们可以在写需求或库时完全可以拥有组件化思想,把它们单独写成一个基础组giti轮胎件或业务基github直播平台永久回家础组件。当第二个项目来的时候正giti轮胎好也需要这个组件,那我们就省去了拆出这个组件的时间(因为写需求的时候通信行程卡很可能会造成github永久回家地址大量耦合,github中文官网网页后续拆分要花费接口自动化时间),比如登录组件,分享组件等等都是可以在一开始就写成组件的。

组件化需解决的问题

业务Git组件如何实现单独调试?

业务组件间通信没有依赖,如何实现页面跳转?

业务组件间没有依赖,如何实现数据通信

壳工程Application生命周期如何下发?通信行程卡

独立调试

单工程方案

所谓的单工程方案就是把所有组件都放到一个工程下,先看一下整体架构师和程序员的区别的目录:

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

ps通信:module_ 开头表示基础组件,fun_ 前缀表示业务基础组件,biz_前缀表示业务组件,exp架构师工资ort_前缀表示业务组件暴露接口。

单工程利弊分析:

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

首先在 gradle.properties 文件内声架构图怎么做word明一个变量:

// gradle.properties
isModule = true

isModule 为 true 时表示组件可以作为 apk 运行起来,false 表示组件只能作为 library。我github中文官网网页们根据需要改变这接口自动化个值后同步下gradle即可。

然后在某个 module 的 bu架构师证书ild.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页,github永久回家地址所以我们要区分这个文件,应用manifest指定的路径没有特定,随意找giti轮胎个路径创建即可。在应用AndroidManifest.xml里我们要接口crc错误计数设置启动页:

<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>

libgitirary 的 And接口的作用roidManifest.xml 不需要这些:

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

gradle 依赖 module 的方式主要有两gitee种:通信大数据行程卡

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

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

dependencies {
	implementation project(':module_common')
}

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

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

多工程方案

多工程就是每个组件都是一个工程,例如创建一个接口测试工程后 app 作为壳组件,它依赖接口和抽象类的区别 biz_接口的作用home 运行,因此不需要 isModule 来控制独立调试,它本身就github中文官网网页是一个工程可以独立调试。

多工程的利弊就是架构图模板和单工程相反的:

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

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

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"
    ]
}

这样方便版本统一管理, 然后在根目录github是干什么的的 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仓架构工程师库来使用。本文的例子gitlab是为了方便所以把所有组件写到一起了,最好的方式就是把 fungithub直播平台永久回家_ 和 module_ 开头的组件都拆分成单工程独立开发,业务组件写到一个工程内。

页面跳转

做完组件之间Gitgithub直播平台永久回家隔离后,暴露出来最明显的问题就是页面跳转和数据通信的问题。一般来说,页面跳转都是显示startActivity跳转,在组件化项目内就不适用了,隐式跳转可以用,但每个Activity都要写 intent-filter 就显得有点麻烦,所以最好的方式还是用路由框架。

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

首先肯定是引入依架构师工资赖,以 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"] 这行。通信工程然后需接口自动化要全局注册 ARoutegithub汤姆r,我是在 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().buil接口是什么d(RouterPath.APP_MAIN).navigation() 即可实现跳转。如果我们要加上数据传递也很方便:

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

然后在MainActivity使用依赖注giti架构接受数据:

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

Arouter方案github中文社区

在 export_biz_msggithub官网 组件下声明 IMsgProvider,此接口必git命令须实现 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 实例的方式也是这种。ARoute接口卡r把所有通信的方式都用一种api实现,让使用者上手非常容易。

Application生命周期分发

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

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

interface BaseApp {
    fun onCreate(application: Application)
}

然后每个组件都要创建一个 App 类实现此接口,比github汤姆如biz_h接口和抽象类的区别ome组件:

class HomeApp: BaseApp {
    override fun onCreate(application: Application) {
     		// 初始化都放在这里
        MLog.d(TAG, "BaseApp HomeApp init")
    }
}

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

val moduleInitArr = arrayOf(
    "com.sun.module_common.AppCommon",
    "com.sun.biz_home.HomeApp",
    "com.sun.biz_msg.MsgApp",
    "com.sun.biz_me.MeApp"
)
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()
        }
    }
}

github打不开们只需github中文社区要知道的每个实现 BaseApp 接口的类的全限GitHub定名并接口是什么写到moduleInitArr数组里,然后通过反射获取github直播平台永久回家 Class 对象从而获取构造函数创建实github下载体对象,最后调用 BaseApp 的 onCreate 方法将 application 传入,每个Application生命周期的方法都可以通过这种方式传递。