“终于懂了” 系列,flutter蓝牙开发


3.1.1 动态配置组件工程类型

单工程模式,整个项目只有一个工程,它包含:App module 加上各个业务组件module,就是所有的代码,这就是单工程模式。 如何做到组件单独调试呢?

我们知道,在 AndroidStudio 开发 Android 项目时,使用的是 Gradle 来构建,Android Gradle 中提供了三种插件,在开发中可以通过配置不同的插件来配置不同的module类型。

  • Application 插件,id: com.android.application
  • Library 插件,id: com.android.library

区别比较简单, App 插件来配置一个 Android App 工程,项目构建后输出一个 APK 安装包,Library 插件来配置一个 Android Library 工程,构建后输出 ARR 包。

“终于懂了” 系列,flutter蓝牙开发_第1张图片 业务module配置的library插件

显然我们的 App module配置的就是Application 插件,业务组件module 配置的是 Library 插件。想要实现 业务组件的独立调试,这就需要把配置改为 Application 插件;而独立开发调试完成后,又需要变回Library 插件进行集成调试

如何让组件在这两种调试模式之间自动转换呢? 手动修改组件的 gralde 文件,切换 Application 和 library ?如果项目只有两三个组件那么是可行的,但在大型项目中可能会有十几个业务组件,一个个手动修改显得费力笨拙。

我们知道用AndroidStudio创建一个Android项目后,会在根目录中生成一个gradle.properties文件。在这个文件定义的常量,可以被任何一个build.gradle读取。 所以我们可以在gradle.properties中定义一个常量值 isModule,true为即独立调试;false为集成调试。然后在业务组件的build.gradle中读取 isModule,设置成对应的插件即可。代码如下:

//gradle.properties
#组件独立调试开关, 每次更改值后要同步工程
isModule = false

//build.gradle
//注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换
if (isModule.toBoolean()){
apply plugin: ‘com.android.application’
}else {
apply plugin: ‘com.android.library’
}

3.1.2 动态配置ApplicationId 和 AndroidManifest

我们知道一个 App 是需要一个 ApplicationId的 ,而组件在独立调试时也是一个App,所以也需要一个 ApplicationId,集成调试时组件是不需要ApplicationId的;另外一个 APP 也只有一个启动页, 而组件在独立调试时也需要一个启动页,在集成调试时就不需要了。所以ApplicationId、AndroidManifest也是需要 isModule 来进行配置的。

//build.gradle (module_cart)
android {

defaultConfig {

if (isModule.toBoolean()) {
// 独立调试时添加 applicationId ,集成调试时移除
applicationId “com.hfy.componentlearning.cart”
}

}

sourceSets {
main {
// 独立调试与集成调试时使用不同的 AndroidManifest.xml 文件
if (isModule.toBoolean()) {
manifest.srcFile ‘src/main/moduleManifest/AndroidManifest.xml’
} else {
manifest.srcFile ‘src/main/AndroidManifest.xml’
}
}
}

}

可见也是使用isModule分别设置applicationId、AndroidManifest。其中独立调试的AndroidManifest是新建于目录moduleManifest,使用 manifest.srcFile 即可指定两种调试模式的AndroidManifest文件路径。 “终于懂了” 系列,flutter蓝牙开发_第2张图片 moduleManifest中新建的manifest文件 指定了Application、启动activity:

//moduleManifest/AndroidManifest.xml









原本自动生成的manifest,未指定Application、启动activity:





独立调试、集成调试 ,分别使用“assembleDebug”构建结果如下: “终于懂了” 系列,flutter蓝牙开发_第3张图片

3.2 多工程方案

3.2.1 方案概述

多工程方案,业务组件以library module形式存在于独立的工程。独立工程 自然就可以独立调试了,不再需要进行上面那些配置了。

例如,购物车组件 就是 新建的工程Cart 的 module_cart模块,业务代码就写在module_cart中即可。app模块是依赖module_cart。app模块只是一个组件的入口,或者是一些demo测试代码。 “终于懂了” 系列,flutter蓝牙开发_第4张图片 那么当所有业务组件都拆分成独立组件时,原本的工程就变成一个只有app模块的壳工程了,壳工程就是用来集成所有业务组件的。

3.2.1 maven引用组件

那么如何进行集成调试呢?使用maven引用组件:1、发布组件的arr包 到公司的maven仓库,2、然后在壳工程中就使用implemention依赖就可以了,和使用第三方库一毛一样。另外arr包 分为 快照版本(SNAPSHOT) 和 正(Realease)式版本,快照版本是开发阶段调试使用,正式版本是正式发版使用。具体如下:

首先,在module_cart模块中新建maven_push.gradle文件,和build.gradle同级目录

apply plugin: ‘maven’

configurations {
deployerJars
}

repositories {
mavenCentral()
}

//上传到Maven仓库的task
uploadArchives {
repositories {
mavenDeployer {
pom.version = ‘1.0.0’ // 版本号
pom.artifactId = ‘cart’ // 项目名称(通常为类库模块名称,也可以任意)
pom.groupId = ‘com.hfy.cart’ // 唯一标识(通常为模块包名,也可以任意)

//指定快照版本 maven仓库url, todo 请改为自己的maven服务器地址、账号密码
snapshotRepository(url: ‘http://xxx/maven-snapshots/’) {
authentication(userName: ‘***’, password: ‘***’)
}
//指定正式版本 maven仓库url, todo 请改为自己的maven服务器地址、账号密码
repository(url: ‘http://xxx/maven-releases/’) {
authentication(userName: ‘***’, password: ‘***’)
}
}
}
}

// type显示指定任务类型或任务, 这里指定要执行Javadoc这个task,这个task在gradle中已经定义
task androidJavadocs(type: Javadoc) {
// 设置源码所在的位置
source = android.sourceSets.main.java.sourceFiles
}

// 生成javadoc.jar
task androidJavadocsJar(type: Jar) {
// 指定文档名称
classifier = ‘javadoc’
from androidJavadocs.destinationDir
}

// 打包main目录下代码和资源的task,生成sources.jar
task androidSourcesJar(type: Jar) {
classifier = ‘sources’
from android.sourceSets.main.java.sourceFiles
}

//配置需要上传到maven仓库的文件
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}

maven_push.gradle主要就是发布组件ARR的配置:ARR的版本号、名称、maven仓地址账号等。

然后,再build.gradle中引用:

//build.gradle
apply from: ‘maven_push.gradle’

接着,点击Sync后,点击Gradle任务uploadArchives,即可打包并发布arr到maven仓。

“终于懂了” 系列,flutter蓝牙开发_第5张图片 最后,壳工程要引用组件ARR,需要先在壳工程的根目录下build.gradle中添加maven仓库地址:

allprojects {
repositories {
google()
jcenter()
//私有服务器仓库地址
maven {
url ‘http://xxx’
}
}
}

接着在app的build.gradle中添加依赖即可:

dependencies {

implementation ‘com.hfy.cart:cart:1.0.0’
//以及其他业务组件
}

可见,多工程方案 和我们平时使用第三方库是一样的,只是我们把组件ARR发布到公司的私有maven仓而已。

实际上,我个人比较建议 使用多工程方案的。

  • 单工程方案没法做到代码权限管控,也不能做到开发人员职责划分明确,每个开发人员都可以对任意的组件进行修改,显然还是会造成混乱。
  • 多工程把每个组件都分割成单独的工程,代码权限可以明确管控。集成测试时,通过maven引用来集成即可。并且业务组件和业务基础组件也可以 和 基础组件一样,可以给公司其他项目复用。

注意,我在Demo里 使用的是多工程方案,并且是 把ARR发到JitPack仓,这样是为了演示方便,和发到公司私有maven仓是一个意思。 1、需要根目录下build.gradle中添加JitPack仓地址:maven { url ‘jitpack.io’ } ; 2、JitPack是自定义的Maven仓库,不过它的流程极度简化,只需要输入Github项目地址就可发布项目。

四、页面跳转

4.1 方案—ARouter

前面说到,组件化的核心就是解耦,所以组件间是不能有依赖的,那么如何实现组件间的页面跳转呢?

例如 在首页模块 点击 购物车按钮 需要跳转到 购物车模块的购物车页面,两个模块之间没有依赖,也就说不能直接使用 显示启动 来打开购物车Activity,那么隐式启动呢? 隐式启动是可以实现跳转的,但是隐式 Intent 需要通过 AndroidManifest 配置和管理,协作开发显得比较麻烦。这里我们采用业界通用的方式—路由

比较著名的路由框架 有阿里的ARouter、美团的WMRouter,它们原理基本是一致的。

这里我们采用使用更广泛的ARouter:“一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦”。

4.2 ARouter实现路由跳转

前面提到,所有的业务组件都依赖了 Common 组件,所以我们在 Common 组件中使用关键字**“api”**添加的依赖,业务组件都能访问。 我们要使用 ARouter 进行界面跳转,需要Common 组件添加 Arouter 的依赖(另外,其它组件共同依赖的库也要都放到 Common 中统一依赖)。

4.2.1 引入依赖

因为ARouter比较特殊,“arouter-compiler ” 的annotationProcessor依赖 需要所有使用到 ARouter 的组件中都单独添加,不然无法在 apt 中生成索引文件,就无法跳转成功。并且在每个使用到 ARouter 的组件的 build.gradle 文件中,其 android{} 中的 javaCompileOptions 中也需要添加特定配置。然后壳工程需要依赖业务组件。如下所示:

//common组件的build.gradle
dependencies {

api ‘com.alibaba:arouter-api:1.4.0’
annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
//业务组件、业务基础组件 共同依赖的库(网络库、图片库等)都写在这里~
}

//业务组件的build.gradle
android {

defaultConfig {

javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}

}
dependencies {

annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
implementation ‘com.github.hufeiyang:Common:1.0.0’//业务组件依赖common组件
}

//壳工程app module的build.gradle
dependencies {

//这里没有使用私有maven仓,而是发到JitPack仓,一样的意思~
// implementation ‘com.hfy.cart:cart:1.0.0’
implementation ‘com.github.hufeiyang:Cart:1.0.1’ //依赖购物车组件
implementation ‘com.github.hufeiyang:HomePage:1.0.2’ //依赖首页组件

//壳工程内 也需要依赖Common组件,因为需要初始化ARouter
implementation ‘com.github.hufeiyang:Common:1.0.0’
}

4.2.2 初始化

依赖完了,先要对ARouter初始化,需要在Application内完成:

public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();

// 这两行必须写在init之前,否则这些配置在init过程中将无效
if (BuildConfig.DEBUG) {
// 打印日志
ARouter.openLog();
// 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
ARouter.openDebug();
}
// 尽可能早,推荐在Application中初始化
ARouter.init(this);
}
}

4.2.3 路由跳转

好了,准备工作都完成了。并且知道 首页组件是没有依赖购物车组件的,下面就来实现前面提到的 首页组件 无依赖 跳转到 购物车组件页面

而使用ARouter进行简单路由跳转,只有两步:添加注解路径、通过路径路由跳转。

1、在支持路由的页面上添加注解@Route(path = “/xx/xx”),路径需要注意的是至少需要有两级,/xx/xx。这里就是购物车组件的CartActivity:

@Route(path = “/cart/cartActivity”)
public class CartActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cart);
}
}

2、然后在首页组件的HomeActivity 发起路由操作—点击按钮跳转到购物车,调用ARouter.getInstance().build("/xx/xx").navigation()即可:

@Route(path = “/homepage/homeActivity”)
public class HomeActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);

findViewById(R.id.btn_go_cart).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过路由跳转到 购物车组件的购物车页面(但没有依赖购物车组件)
ARouter.getInstance()
.build("/cart/cartActivity")
.withString(“key1”,“value1”)//携带参数1
.withString(“key2”,“value2”)//携带参数2
.navigation();
}
});
}
}

另外,注意在HomeActivity上添加了注解和路径,这是为了壳工程的启动页中直接打开首页。还看到路由跳转可以像startActivity一样待参数。

最后,壳工程的启动页中 通过路由打开首页(当然这里也可以用startActivity(),毕竟壳工程依赖了首页组件):

//启动页
public class SplashActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//通过路由直接打开home组件的HomeActivity,
ARouter.getInstance().build("/homepage/homeActivity").navigation();
finish();
}
}

我们run壳工程 最后看下效果: “终于懂了” 系列,flutter蓝牙开发_第6张图片 到这里,组件间页面跳转的问题也解决了。

五、组件间通信

组件间没有依赖,又如何进行通信呢?

例如,首页需要展示购物车中商品的数量,而查询购物车中商品数量 这个能力是购物车组件内部的,这咋办呢?

5.1 服务暴露组件

平时开发中 我们常用 接口 进行解耦,对接口的实现不用关心,避免接口调用与业务逻辑实现紧密关联。这里组件间的解耦也是相同的思路,仅依赖和调用服务接口,不会依赖接口的实现。

可能你会有疑问了:既然首页组件可以访问购物车组件接口了,那就需要依赖购物车组件啊,这俩组件还是耦合了啊,那咋办啊?答案是组件拆分出可暴露服务。见下图: “终于懂了” 系列,flutter蓝牙开发_第7张图片 左侧是组件间可以调用对方服务 但是有依赖耦合。右侧,发现多了export_homeexport_cart,这是对应拆分出来的专门用于提供服务的暴露组件。操作说明如下:

  • 暴露组件 只存放 服务接口、服务接口相关的实体类、路由信息、便于服务调用的util等
  • 服务调用方 只依赖 服务提供方的 露组件,如module_home依赖export_cart,而不依赖module_cart
  • 组件 需要依赖 自己的暴露组件,并实现服务接口,如module_cart依赖export_cart 并实现其中的服务接口
  • 接口的实现注入 依然是由ARouter完成,和页面跳转一样使用路由信息

下面按照此方案 来实施 首页调用购物车服务 来获取商品数量,更好地说明和理解。

5.2 实施

5.2.1 新建export_cart

首先,在购物车工程中新建module即export_cart,在其中新建接口类ICartService并定义获取购物车商品数量方法,注意接口必须继承IProvider,是为了使用ARouter的实现注入:

/**

  • 购物车组件对外暴露的服务
  • 必须继承IProvider
  • @author hufeiyang
    */
    public interface ICartService extends IProvider {

/**

  • 获取购物车中商品数量
  • @return
    */
    CartInfo getProductCountInCart();
    }

CartInfo是购物车信息,包含商品数量:

/**

  • 购物车信息

  • @author hufeiyang
    */
    public class CartInfo {

/**

  • 商品数量
    */
    public int productCount;
    }

接着,创建路由表信息,存放购物车组件对外提供跳转的页面、服务的路由地址:

/**

  • 购物车组件路由表
  • 即 购物车组件中 所有可以从外部跳转的页面 的路由信息
  • @author hufeiyang
    */
    public interface CartRouterTable {

/**

  • 购物车页面
    */
    String PATH_PAGE_CART = “/cart/cartActivity”;

/**

  • 购物车服务
    */
    String PATH_SERVICE_CART = “/cart/service”;

}

前面说页面跳转时是直接使用 路径字符串 进行路由跳转,这里是和服务路由都放在这里统一管理。

然后,为了外部组件使用方便新建CartServiceUtil:

/**

  • 购物车组件服务工具类
  • 其他组件直接使用此类即可:页面跳转、获取服务。
  • @author hufeiyang
    */
    public class CartServiceUtil {

/**

  • 跳转到购物车页面
  • @param param1
  • @param param2
    */
    public static void navigateCartPage(String param1, String param2){
    ARouter.getInstance()
    .build(CartRouterTable.PATH_PAGE_CART)
    .withString(“key1”,param1)
    .withString(“key2”,param2)
    .navigation();
    }

/**

  • 获取服务
  • @return
    */
    public static ICartService getService(){
    //return ARouter.getInstance().navigation(ICartService.class);//如果只有一个实现,这种方式也可以
    return (ICartService) ARouter.getInstance().build(CartRouterTable.PATH_SERVICE_CART).navigation();
    }

/**

  • 获取购物车中商品数量
  • @return
    */
    public static CartInfo getCartProductCount(){
    return getService().getProductCountInCart();
    }
    }

注意到,这里使用静态方法 分别提供了页面跳转、服务获取、服务具体方法获取。 其中服务获取 和页面跳转 同样是使用路由,并且服务接口实现类 也是需要添加@Route注解指定路径的。

到这里,export_cart就已经准备完毕,我们同样发布一个export_cart的ARR(“com.github.hufeiyang.Cart:export_cart:xxx”)。

再来看看module_cart对服务接口的实现。

5.2.2 module_cart的实现

首先,module_cart需要依赖export_cart:

//module_cart的Build.gradle
dependencies {

annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
implementation ‘com.github.hufeiyang:Common:1.0.0’

//依赖export_cart
implementation ‘com.github.hufeiyang.Cart:export_cart:1.0.5’
}

点击sync后,接着CartActivity的path改为路由表提供:

@Route(path = CartRouterTable.PATH_PAGE_CART)
public class CartActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cart);
}
}

然后,新建服务接口的实现类来实现ICartService,添加@Route注解指定CartRouterTable中定义的服务路由

/**

  • 购物车组件服务的实现
  • 需要@Route注解、指定CartRouterTable中定义的服务路由

结尾

最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。

首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)

泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter

“终于懂了” 系列,flutter蓝牙开发_第8张图片

接下来是资料清单:(敲黑板!!!

领取通道在这里给你们摆上了~

点击我的GitHub免费获取

1.数据结构和算法

“终于懂了” 系列,flutter蓝牙开发_第9张图片

2.设计模式

“终于懂了” 系列,flutter蓝牙开发_第10张图片

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

“终于懂了” 系列,flutter蓝牙开发_第11张图片

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

“终于懂了” 系列,flutter蓝牙开发_第12张图片

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

“终于懂了” 系列,flutter蓝牙开发_第13张图片
P7/blob/master/Android%E5%BC%80%E5%8F%91%E4%B8%8D%E4%BC%9A%E8%BF%99%E4%BA%9B%EF%BC%9F%E5%A6%82%E4%BD%95%E9%9D%A2%E8%AF%95%E6%8B%BF%E9%AB%98%E8%96%AA%EF%BC%81.md)**


1.数据结构和算法

[外链图片转存中…(img-GljP7iMw-1643962693713)]

2.设计模式

[外链图片转存中…(img-ttqS60jx-1643962693714)]

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

[外链图片转存中…(img-O4qdv6NN-1643962693714)]

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

[外链图片转存中…(img-HHyUBINk-1643962693715)]

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

[外链图片转存中…(img-oOMj9FeM-1643962693715)]

你可能感兴趣的:(程序员,架构,移动开发,android)