你不会组件化,就是两个组件

2019-09-30 15:32栏目:美高梅开户送58元官网
TAG:

ArchivesActivityModule的代码如下:

组件化核心:router##

我们在抽象 module 时,module 之间是没有相互依赖的,是严格解耦的,为了达到我们复用的目的。module 之间不能相互依赖,就没法调用别的 module 的代码了,那么面对业务之间的页面相互调起,相互通信这些常见的需求我们该怎么办,没错就是大伙在上面的图里面看见的东西 router。

router 是我们统一制定的模块间通讯协议,router 中我们主要是处理一下几个问题:

  • 模块之间页面跳转
  • 模块之间数据传递
  • 模块初始化处理

router.png

router 这东西有现成的,你也可以自己封装。使用的思路都是把 router 作为一个组件,所有的业务 module 都依赖这个 router 组件,当然壳app 也是,然后我们把需要的模块间页面跳转,数据传递,初始化都注册到 router 中,这里面就体现到我们定义的统一,通用的模块通讯协议的重要性了,router 维护多个集合保存这里关系,然后我们通过router 就可以实现模块间的通讯了。

router 的封装还是挺麻烦的,要写好了不容易,现在用的比较多的有:

  • 阿里的 ARouter
  • 最早出现的 ActivityRouter
  • spiny同学的router这是我的最爱,目前不维护了,思路很棒,并且考虑到了进程化的问题,可惜没有使用 APT 注解技术
  • 练手的 router

上面我介绍了几个 router 路由,基本上不论时自己写还是用现成的,router 基本上都是上面这几个的样子了,当然了现在好的 router 还是要使用 APT注解技术来动态去 router 注册模块方法,自己写代码去注册的话使用很使用,有些问题不好处理,比如 router 的静态实例要是被回收了,你再 new 一个出来,那么模块注册的方法怎么办,写起来太麻烦,还不如 APT 注解来的方便,扩展性也好。这里有个ToyBricks_Android项目模块化解决方案 可以解决 APT不能扫描 arr 包的问题。

最后说一下,module 间的通讯其实可以分成3种:

  • 页面调起
  • 某种事件的通知
  • 直接调用某些模块的业务方法

页面调起现在的 router 都可以很好的完成这个任务。

某些事件的通知,比如我切换城市了,通知某些页面去显示或是刷新数据,这个根据业务来说影响的范围会很广的,会影响多个业务的,因为 module 的复用性,我们在 module 中是不能确定会具体影响哪些业务module 的,那么这种场景使用 eventbus/广播比较合适了。

直接调用默写模块的业务方法,这属性业务模块间在业务上的强耦合了,这个碰到产品这么设计你也没办法,一般碰到这样的场景也是会保证相关的业务module 都是会加载的,所以呢在定义 router 灵活一些,可以做到调用指定module 的某些方法


router 我不想说太多,也说不好,这部分大伙看我最后的链接吧,或是看看上面4个路由也可以,不论如何实现,router 是为了给 module 模块搭建一个通讯的中间平台,目的就是这样。仔细的大家多看吧,这里我也是看别人的。

我再逼逼一下,module 和 library 我们尽量不要提供源代码的方式提供依赖,这不符合我们复用的目的,到时候你发布几个影子功能或是别的 app,那么你使用源代码依赖方式的 module 和 library 你怎么提供维护,所以尽量使用 arr 和 jar 的方式。我们可以把一些定位相近library 打包成一个 module 也是不错的。

我们一定要熟悉gradle的使用,在组件化中我们会大量的使用 gradle 提供各种资源加载的配置和环境配置

组件化最核心的目的就是代码的高可复用和高可维护和高可扩展性能,其他的优点都是属于连带性质的,我们要先把握住核心点学习,其他的都不是主要,有时间再看


6、依赖注入

依赖注入大家应该要很熟悉,这是一种很好的代码解耦方式,不了解的请自行学习。

Android有dagger这个出名的依赖注入框架,不过我没有用,Arouter也带了依赖注入功能,够用了。

美高梅开户送58元官网 1图6

项目采用MVP模式,view和presenter是一一对应,所以直接在Activity里new出Presenter对象,没弄什么花样。

Model层根据业务分为各种service,比如TaskService、UserService、SettingService,对外只暴露接口。这个时候使用依赖注入就很合适,Presenter只需要持有service的引用,实例由Arouter负责注入。

类似Activity定义路径,为service实现类定义注解:

@Route(path = "/test/service/task")public class TaskServiceImpl implements TaskService {}

然后在Presenter,用Autowired注解需要被注入的Service。例子里有两种方式,一种是全局注入,一种是单个注入,根据实际情况使用。

@AutowiredTaskService taskService;@AutowiredUserService userService;public MyPresenter() { ARouter.getInstance().inject; //taskService = ARouter.getInstance().navigation(PollingTaskService.class); //userService = ARouter.getInstance().navigation(UserService.class);}

上面无非是省略了new的过程,下面再举个复杂一点的例子。

美高梅开户送58元官网 2图7

select user展示了user列表,提供选择user的功能,但是user列表的生成方法,只有对应的caller知道。在caller和select user已经组件化的情况下,可以使用依赖注入简化代码。

首先,在它们俩共同的业务库层定义一个接口BaseSelectUserService,里面有一个方法listUser()。caller1和caller2分别实现BaseSelectUserService接口,完成各自listUser()的逻辑。

BaseSelectUserService selectUserService = (BaseSelectUserService) ARouter.getInstance().build(servicePath).navigation();

最关键是为select user注入合适的SelectUserServiceImpl对象,其中servicePath是页面跳转传递过来的参数,这样就可以正确地调用到对应caller的listUser()。如果有第三个caller,完全不用管select user,只需要依葫芦画瓢实现BaseSelectUserService接口并传递service路径。

这样在主app的AppComponent类中加入:

优秀的router 路由器设计

  • Android 组件化 —— 路由设计最佳实践
  • 使用 Router 实现的模块化,如何优雅的回到主页面&version=12020010&nettype=WIFI&fontScale=100&pass_ticket=4SuwGAfl93vCbMaGvZyTnS5rJFW1CezWNCgqWPgC%2Fxeo%2FSOLFfZv1Hw%2BFPAGEme7)

4、Application处理

类似于AndroidManifest.xml,最终运行时Application只有一个,Application类也要区分开发模式和集成模式。

  • 开发模式:photo module有自己的Application,就叫PhotoApplication,里面初始化第三方库或者添加其他一些操作。对应地PhotoApplication需要定义到debug/AndroidManifest.xml,PhotoApplication文件放进java/debug中。
  • 集成模式:PhotoApplication是photo module单独编译时才有用的。集成后,需要在mainapp中定义MainApplication作为最终唯一的Application。

很容易想到,这个时候还需要一个BaseApplication,作为PhotoApplication和MainApplication的父类,提供公有的初始化方法和全局Context的获取。

 Object result = null; Class<?> c = Class.forName(className); if (c != null) { result = c.newInstance(); } return result;

今天来回味下组件化和模块化,这2种说法时一回事,当然还是有区别的,下面再详细说,其实很简单,只是设计范围的不同,也都是约定俗成的东西。为了方便我下面都说组件化了

2、photo module

下面以拍照和看图的一个module为例子,它提供CameraActivity和PhotoActivity两个Activity。这是很基础的功能,如果是组件化之前的框架,我会毫不犹豫将功能扔进业务基础库,因为所有app都需要用到。

module之间的直接引用用起来很方便,但不利于长远代码的维护,毕竟只靠着包名划分功能,代码边界很容易破坏。最好的方法是编译上的隔离,调用者和photo module之间没有直接引用。

利用application和library切换的方法,我们可以达到如下效果。

美高梅开户送58元官网 3图4

对于photo module,除了CameraActivity和PhotoActivity,还添加了DebugMainActivity。当photo module单独编译成photo app时,以DebugMainActivity为主页。当需要编译mainapp时,photo module作为library和其他module打包进mainapp,注意,这个时候DebugMainActivity没有用了,只有橙色框部分需要。红色箭头跳转不能再使用显式Intent,可以用隐式Intent跳转,或者使用后面介绍的路由跳转。

好处显而易见,模块间完全解耦了。在开发阶段,可以单独编译photo app,在DebugMainActivity中调试拍照和看图,省掉编译完整app和在app中点击测试的时间。

上面模式的实现,需要对配置进行改造,接下来一步步来讲解。

public class ApplicationModule { @Provides @Singleton Context provideContext(Application application) { return application; } @Provides @Singleton UserSystem provideUserSystem() { return new UserSystem(); } @Provides @Singleton ThreadExecutor provideThreadExecutor(JobExecutor jobExecutor) { return jobExecutor; } @Provides @Singleton PostExecutionThread providePostExecutionThread(UIThread uiThread) { return uiThread; } @Provides @Singleton MrService provideMrService() { return new MrService(); } @Provides @Singleton UserCache provideUserCache(UserCacheImpl userCache) { return userCache; }}

组件化文章:

  • Android 模块化探索与实践 安居客的时间案例,对概念的解释非常到位,适合初学者和概念混乱的同学看,特别是有提供详细的 demo 哦

  • 关于Android模块化我有一些话不知当讲不&version=12020010&nettype=WIFI&fontScale=100&pass_ticket=GtniR9NmdW%2FyGY%2FJ%2FPmTsxk4ZicBIJGMh2pyMCDorWwqm9O1p2lWqa%2BgCOiwQIQU) 这是我目前看到的最贴近实际的好文了,绝对值得一看的

  • Android组件化方案这篇文章讲的最好,还设计大量 gradle 配置的思考

  • ToyBricks_Android项目模块化解决方案使用 apt 注解技术解耦

  • android 开发:由模块化到组件化

  • 一次 Android 项目组件化 介绍了不少实际 gradle 配置使用

  • android组件化方案,让团队开发更有效率借鉴 retrofit 的方式声明
    router 接口,时个思路,但是没有达到彻底解耦,可以参考下。

  • 蘑菇街 App 的组件化之路这和讲 IOS 的

  • 项目组件化之遇到的坑


项目框架

最开始项目只有一个app,项目结构很简单,就是一个业务module加上一个通用的基础库。

美高梅开户送58元官网 4图1

随着业务的开展,有了第二个第三个乃至第N个app,项目结构变成如下样子。

美高梅开户送58元官网 5图2

不同app有公共的功能,于是增加一个业务基础库,将公用部分移到里面。

这个框架维持了很长一段时间,随着业务的快速发展,开发人员的增加,代码也越来越臃肿,一些问题开始出现。正好由于多个app不利于推广,项目开始向云平台的方向发展,需要一个融合的平台app,将原先的app作为业务模块加入到平台app中。

apply plugin: 'maven'ext {// ext is a gradle closure allowing the declaration of global properties PUBLISH_GROUP_ID = 'com.wkw' PUBLISH_ARTIFACT_ID = 'archives' PUBLISH_VERSION = rootProject.ext.versionName}uploadArchives { repositories.mavenDeployer { //这里就是最后输出地址,在自己电脑上新建个文件夹,把文件夹路径粘贴在此 //注意”file://“ + 路径,有三个斜杠,别漏了 repository(url: "file:///Users/wukewei/Documents/android/ModularizationExample/repo") pom.project { groupId project.PUBLISH_GROUP_ID artifactId project.PUBLISH_ARTIFACT_ID version project.PUBLISH_VERSION } }}//以下代码会生成jar包源文件,如果是不开源码,请不要输入这段//aar包内包含注释task androidSourcesJar(type: Jar) { classifier = 'sources' from android.sourceSets.main.java.sourceFiles}artifacts { archives androidSourcesJar}

下面来正式说说组件化

组件化这个东西其实并不复杂,他就是种思路,本质上是一种 app 架构思路,说穿了很简单的,难在组件化改造的时候,真正写起代码会出现不少棘手的问题,当然这些坑基本前人都趟完了,这里我主要时记录下,要是你看到熟悉的部分,请不要骂我啊,毕竟都是前辈们的东西啊。

这里补充一下,组件化时一种 app 架构,他的发展也是沿着正常的技术发展脉络来的,也是为了以追求高复用,高可维护性的目的的代码封装,区别是组件化是对整个 app 的再次封装。

废话了这么多,那么什么是组件化呢,各位看官想不要着急,在详细说组件化之前,我们要搞懂2个概念,就是上面说的组件和模块。

首先组件和模块都不是官方规定的,都是这些技术发展下来大家约定俗成的概念,其实很简单,一说就明白

  • 模块:android 中的模块就是业务模块,单指业务,是按照业务对 app 进行拆分,比如说订单我们搞成一个模块,个人中心我们搞成一个模块,视频,音频这些都搞成模块,在app中的体现就是 一个个module,module 的中文意思也是模块,这不准这就是 google 对我们的暗示呢。模块化的目的时为了搭积木,随便拿几个模块module 出来就可以谁谁便便的上线一个 app,你还别说现在影子 app 的需求很旺盛,你去看看大公司的项目那个不是一堆影子工程,头条还搞出一个头条视频的马甲呢,这其实就是把视频 module 拿出来,加上一个启动页。这样的例子是比比皆是的,要不说不会组件化影子工程对你就是噩梦呢,哈哈,到时候维护那是想也别想了,代码你要搞多少份啊。

  • 组件:这个一样简单啊,说穿了就是我们平时干的事,对功能的封装,这就是组件,一个功能就是一个组件,IO,数据库,网络等等这些功能都是组件,这么说你就明白了吧。既然这样那为毛线我们还要搞出来这个一个组件的概念,当然了任何事都是有其意义的,因为组件对功能代码的封装有个很高了明确的要求:一处封装,处处使用。要我们把维护性,复用性,扩展性,性能做到极致,因为这样才能真正做到一处封装,处处使用。当然组件的范围现在也是覆盖的很广的,app 中的一切都是组件,基本上我们分为:基础功能组件,通用UI组件,基础业务组件。

以上我谈了下我自己对于模块化,组件化的理解,是目前开发中对于模块和组件的理解。在模块化和组件化的发展中概念也是有些调整变化的,大家只要看现在时什么样子就好了,深入学习的话有兴趣可以看看组件化,模块化的发展历程。

我认为 Android 模块化探索与实践 对于模块化,组件化概念的解释时最优秀的。

组件化和模块化在现在看是一回事了,如果把一个项目看成是袋中的组合的话,那么模块就是体积最大的哪些袋子,组件就是体积小的袋子,大的袋子是最直接可被外接观测和接触的袋子,大的袋子也是用小的袋子组成的,一个不太恰当的比喻吧,模块和组件就是这样的关系,是我们对业务和功能拆分,封装的理解。

5、路由跳转

由于模块的拆分,页面间无法使用显式Intent跳转。隐式Intent可以用,但是书写比较麻烦,一些面向切面的功能难以实现,所以我不用。

我使用了支持路由功能的这个库alibaba/ARouter。项目有详细的文档和demo,我就不复制粘贴了,下面说说我怎样用。

首先为CameraActivity定义地址,直接对class添加注解:

@Route(path = "/photo/activity/camera")

然后在需要调用的地方这样写:

Bundle bundle = new Bundle();//set bundleARouter.getInstance() .build("/photo/activity/camera") .with .navigation(activity, BizConstant.RequestCode.TAKE_PHOTO);

定义好路径、参数和requestCode,很简单地实现了一次路由跳转。

跳转一定成功吗?如果在开发阶段单独编译一个业务app,photo module不存在,前置登录的login module也不存在,如下图所示:

美高梅开户送58元官网 6图5

photo module不存在比较好办,Arouter提供了一个Callback函数:

public abstract class NavCallback implements NavigationCallback { public NavCallback() { } public void onFound(Postcard postcard) { } public void onLost(Postcard postcard) { } public abstract void onArrival(Postcard var1); public void onInterrupt(Postcard postcard) { }}
  1. 跳转失败时,可以直接返回一些测试数据,photo module应该是他人维护的稳定组件,不需要在开发阶段浪费点击时间。
  2. 如果需要测试调用photo module,只能启用集成模式,不过可以手动修改mainapp的配置,因为业务app层的module可以任意组合,只需要包括用到的,最大限度减少编译时间。

至于前置的login,那是必须得有,要输入账号密码好烦啊,而且login module在开发阶段我不想集成,有什么办法?

回想之前每个业务app层的module在开发阶段都有自己的Application,完全可以把模拟登陆过程放在里面。这是一个思路,写一次,受益几个月。

CleanArchitecture框架的github地址,这里再介绍分享一篇文章小鄧子的Easy Clean architecture on Android,我把data和domain会写在各自的业务模块中,自己的模块只要定义自己的就可以了,有个ApplicationModule会定义一些每个模块都需要的,

组件化碰到的问题

1. 子模块单独编译测试

在做组件化开发时,我们测试 module 库都是把 module 单独达成 apk 文件,在发布module时 提供 library 供外界依赖,这都是通过配置 module 的 gradle 的编译模式实现的

首先在子模块build.gradle中定义常量,来标示模块目前是否处于开发模式

def isDebug = true

在子模块的build.gradle中进行模式配置。debug模式下编译成独立app,release模式下编译成library。

if (isDebug.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

两种模式下模块AndroidManifest.xml文件是有差别的。作为独立运行的app,有自己的Application,要加Launcher的入口intent,作为library不需要。这个问题很好解决,写两个不同的AndroidManifest.xml即可,并在gradle中进行配置。

在 gradle 脚本中配置

android {
    sourceSets {
        main {
            if(isDebug.toBoolean()) {
                manifest.srcFile 'src/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/release/AndroidManifest.xml'
            }
        }
    }
}

2. sdk和第三方库的版本一致性

不同module依赖sdk版本不一致,会因兼容性问题导致编译问题。
不同module引用了同一个第三方库的不同版本,并且这个库没有做到向前兼容,就有可能出现方法找不到、参数不对应等问题。
所以有必要统一整个project的依赖版本。

在最外层build.gradle中定义的常量能被整个project的build.gradle文件引用,统一的版本定义可以放在这里。

ext {
    android_compileSdkVersion = 25
    android_buildToolsVersion = '25.0.2'
    android_minSdkVersion = 21
    android_targetSdkVersion = 25

    lib_appcompat = 'com.android.support:appcompat-v7:25.1.1'
    lib_picasso = 'com.squareup.picasso:picasso:2.5.2'
    lib_gson = 'com.google.code.gson:gson:2.6.1'
}

我没试过 arr资源的 module 是否还可以使用这种方式


3. 资源id冲突

美高梅开户送58元官网,android 中 module的资源文件最后都是会合并到主项目中的,资源文件的 id 最终和 moudle 是的 id 时不一样的,所以这就会出现资源重名的问题,解决这个问题,我们的做法就是module 资源加一个统一的前缀

andorid{
    ...

    buildTypes{
        ...
    }

    resourcePrefix "moudle_prefix"

}

但是注意 res 文件夹下的文件可以用 gradle 脚本加前缀,但是图片资源不行,图片资源我们还是需要在命名时自己添加前缀

4. application初始化的问题

子模块作为application时,有一些初始化的工作需要在Application.onCreate时进行。而作为library时,调不到这个onCreate。所以自己写一个静态方法,供主工程的Application调用。

public class ApplicationA extends Application {

@Override public void onCreate() {
  super.onCreate();
  //给底层library设置context
  AppContext.init(getApplicationContext());
}

  /**
   * 作为library时需要初始化的内容
   */
  public static void onCreateAsLibrary() {
    //给FunctionBus传入接口的实例
    FunctionBus.setFunction(new FunctionA() {
      @Override public String getData(String key) {
        return "xixi";
      }
    });
  }
}
主工程的Application onCreate时记得初始化子模块。

public class MainApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    AppContext.init(getApplicationContext());
    ApplicationA.onCreateAsLibrary();
    ApplicationB.onCreateAsLibrary();
  }
}

除了提供方法在壳工程里面调用,还可以结合使用了 APT 技术的 router 来做,使用注解,就不用我们自己去调用了,彻底解耦

5. library依赖问题

先说一个问题,在组件化工程模型图中,多媒体组件和Common组件都依赖了日志组件,而A业务组件有同时依赖了多媒体组件和Common组件,这时候就会有人问,你这样搞岂不是日志组件要被重复依赖了,而且Common组件也被每一个业务组件依赖了,这样不出问题吗?

其实大家完全没有必要担心这个问题,如果真有重复依赖的问题,在你编译打包的时候就会报错,如果你还是不相信的话可以反编译下最后打包出来的APP,看看里面的代码你就知道了。组件只是我们在代码开发阶段中为了方便叫的一个术语,在组件被打包进APP的时候是没有这个概念的,这些组件最后都会被打包成arr包,然后被app壳工程所依赖,在构建APP的过程中Gradle会自动将重复的arr包排除,APP中也就不会存在相同的代码了;

但是虽然组件是不会重复了,但是我们还是要考虑另一个情况,我们在build.gradle中compile的第三方库,例如AndroidSupport库经常会被一些开源的控件所依赖,而我们自己一定也会compile AndroidSupport库 ,这就会造成第三方包和我们自己的包存在重复加载,解决办法就是找出那个多出来的库,并将多出来的库给排除掉,而且Gradle也是支持这样做的,分别有两种方式:根据组件名排除或者根据包名排除,下面以排除support-v4库为例:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
        exlude module: 'support-v4'//根据组件名排除
        exlude group: 'android.support.v4'//根据包名排除
    }
}

library重复依赖的问题算是都解决了,但是我们在开发项目的时候会依赖很多开源库,而这些库每个组件都需要用到,要是每个组件都去依赖一遍也是很麻烦的,尤其是给这些库升级的时候,为了方便我们统一管理第三方库,我们将给给整个工程提供统一的依赖第三方库的入口,前面介绍的Common库的作用之一就是统一依赖开源库,因为其他业务组件都依赖了Common库,所以这些业务组件也就间接依赖了Common所依赖的开源库。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    //Android Support
    compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
    compile "com.android.support:design:$rootProject.supportLibraryVersion"
    compile "com.android.support:percent:$rootProject.supportLibraryVersion"
    //网络请求相关
    compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
    compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
    compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
    //稳定的
    compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
    compile "com.orhanobut:logger:$rootProject.loggerVersion"
    compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
    compile "com.google.code.gson:gson:$rootProject.gsonVersion"
    compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"

    compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
    compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"

    //router
    compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
}

6. module不同业务环境使用不同代码

我们做项目至少会有测试和线上2套环境吧,组件化让我们开始重视 gradle,通过 gradle 配置我们可以减少很多代码的书写的,切换环境我们也是可以用 gradle 实现的,在不通过的环境下注册不同的代码文件,看下面这张图

1281144-3d9c01d6ac6d30fe.png

我们有 debug 和 release2个环境,里面放的是不同环境执行的代码,main 里面是跟环境切换无关的代码部分,我我们这样设置 gradle 就可以了

android {
    // ...
    sourceSets {
        debug {
            java.srcDirs = ['src/main/java', 'src/debug/java']
        }
        release {
            java.srcDirs = ['src/main/java', 'src/release/java']
        }
    }
}

此外在发布该 library 时,需要指定一些设置,如下:

android {
    // ...
    defaultConfig {
        // ...
        defaultPublishConfig 'release'
        publishNonDefault true
    }
}

说明:

  • defaultPublishConfig 'release',默认 library 只会生产 release 下的版本,此版本将会被所有项目使用,通过defaultPublishConfig可以控制默认生产哪个版本的库。
  • publishNonDefault true,默认情况下不能生产所有版本的 library,通过设置publishNonDefault为true,可以同时生产所有版本的 library。

业务组件 module 依赖不同的基础组件生产的 library,如下:

dependencies {
    // ...
    debugCompile project(path: ':baselibrary', configuration: "debug")
    releaseCompile project(path: ':baselibrary', configuration: "release")
}

在使用通过这样的配置脚本解决了多个 APK 包依赖同一组件生产的不同的 library,最终得到我们需要的开发/测试/生产 APK 包。


一个好的组件化文档是必须的

组件化是 android 开发步入新时代的未来,是代码膨胀,支持快速开发的必然,一个好的组件化文档在现今来看也是必须的了

下面贴个图

20160311130348_213.jpg

20160311130349_276.jpg


组件化的坑

组件化是好,但是坑也是不少,不好填,尤其是 databinding,dagger,bufferkinft,这是源于 studio 编译的问题。

studio 中 module 虽然时在代码上独立于壳工程的,但是在编译时最后还是要合并到壳工程中的,要不怎么达成一个 APK 文件,要是多个 APK 文件把不成了插件化了嘛,插件化坑更多啊。合并 module 到壳工程就会产生一个根本问题,module 的 R 文件数值改变了。module 的 R文件数据不是固定的,只有壳工程的 R 文件才是常量值,时不变的,module 的 R 文件数值在把 modul 的资源合并到壳工程后才会确定下来,那么这就对依靠编译时注解的技术造成了难题,你指定的 R 路径最后找不到,并且据说这里面还涉及注解的 final ,不了解,看到有人这么说,所以大家在开发组件化时对于带注解技术的框架要多注意,有坑要多看才能爬过去

7、数据库

项目的数据库使用sqlite,orm框架是greenDAO。组件化之前,各业务app维护自己的数据库,集成后就要考虑各数据库之间的关系。

数据库表分为两类,一是公共的表,比如用户表、资源表;二是各业务app自身的业务表。组件化之后,有三种方向:

  1. 业务app依旧各自维护数据库;
  2. 提取公共表到上层的业务基础层,统一管理;
  3. 所有表放在业务基础层。

第二种是在模块划分上是理想的,公共表在业务基础层,业务表维护在各自业务app中,合情合理,不过有拦路虎。greenDAO通过@Entity将对象定义为表,在编译时,不同module中的表会分别生成DaoMaster和DaoSession,换言之,每个module都有一个数据库。跨数据库的多表查询难搞,不用第二种了。

第一种改动最少,但是由于公共表不是定义在业务基础库,所有公共表的逻辑都需要在业务app中实现一遍,我不能接受咯。

剩下第三种,虽然所有业务表需要定义在业务基础库,感觉不太好,但也仅仅是表定义,增删改查的逻辑还是在业务app中,在不修改数据库品种的情况下,不好也先用着。

想必大家都知道了,定义一个isBuildModule=false,在组件的build.gradle中加入

优秀的组件化方案

  • Android彻底组件化方案实践

  • Android彻底组件化demo发布

  • Android彻底组件化源码分析&version=12020010&nettype=WIFI&fontScale=100&pass_ticket=4SuwGAfl93vCbMaGvZyTnS5rJFW1CezWNCgqWPgC%2Fxeo%2FSOLFfZv1Hw%2BFPAGEme7)

  • D项目Android模块化 VCS演进之路

  • Android 组件化探索与思考


8、资源冲突

多个module由多名开发人员并行开发,无可避免会出现资源名称的重复。在最终合并到mainapp时,肯定会出现冲突,最好的方法是为资源定义一个前缀。例如photo module中所有资源都加个前缀photo_。

build.gradle可以增加一个配置,强制资源指定前缀。

android{ resourcePrefix "photo_"}

这个配置只能限制xml文件里的资源,对于图片资源,养成习惯添加吧。

@Beta@Module(includes = AndroidInjectionModule.class)public abstract class AndroidSupportInjectionModule { @Multibinds abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>> supportFragmentInjectorFactories(); private AndroidSupportInjectionModule() {}}

到现在组件化真的不是什么新鲜东西了,大公司都用的滚瓜烂熟,龙飞凤舞了,也就是现在部分中型项目和小项目在组件化的路上努力。所以同志们,组件化没玩过的,不熟悉的赶紧搞起来,说一点,你不会组件化,发布影子工程那么对你来说就是个噩梦。从本质上来讲任何技术进步都是在现实需求的逼迫下抓耳挠腮,耗尽无数头发才想出来的。哈哈,这里说个笑话罢了。所以呢组件化这个东西出来这么久了,页发展了这么久了,用的人越来越多,那肯定是对我们显示开发大有裨益的,下伙伴们不会,不熟悉抓紧啦,要不面试问你你怎么回答呢!

1、application和library

能够独立运行的app,module的属性是application,在build.gradle定义为:

apply plugin: "com.android.application"

不能独立运行,提供其他module依赖的叫library,在build.gradle定义为:

apply plugin: 'com.android.library'

看回上面的结构图,基础库和业务基础库自然都是library,mainapp是application。对于业务app,则要区分开发阶段和集成阶段。在开发阶段,希望业务app可以单独运行;集成阶段,希望业务app摇身一变,以library形式整合到平台app。

开发阶段和集成阶段的切换,可以通过在gradle定义全局变量,提供给module读取。我定义了一个config.gradle,在根build.gradle引入:

apply from: "config.gradle"

config.gradle的用途是管理配置、版本号和依赖库,避免散落到各个module中,方便集中管理。

ext { buildBizApp = false //是否构建单独的业务app compileSdkVersion = 24 buildToolsVersion = "26.0.1" minSdkVersion = 15 targetSdkVersion = 19 versionCode = 1 versionName = "1.0.0" dependencies = [ supportV4 : 'com.android.support:support-v4:24.2.1', appcompatV7 : 'com.android.support:appcompat-v7:24.2.1', recyclerviewV7 : 'com.android.support:recyclerview-v7:24.2.1', design : 'com.android.support:design:24.2.1' ]}

在业务app的build.gradle,通过判断变量buildBizApp,达到自由切换的目的。

if (rootProject.ext.buildBizApp) { apply plugin: "com.android.application"} else { apply plugin: 'com.android.library'}
 defaultConfig { if (isBuildModule.toBoolean { applicationId "com.wkw.archives" } minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" resourcePrefix "archives" }

好了正式开始介绍了组件化啦

组件化在工程表现上就是我们把 app 按照其业务的不同,划分为不同的 module模块,把各种功能封装成一个个 library,module 之间时严格禁止横向依赖的,要不怎么单独使用呢,我不能为了用一个 module,把相关的module 都带上吧,要是这么 module 还有依赖的module 呢,这样谈复用性就是扯淡了。

主 app 就是我们常说的壳工程依赖这些 module,library 由需求的 module 依赖,但是要考虑library 版本的问题,随着业务和功能的扩展,library 的数量也是巨大的,微信在组件化拆分时据说拆分出80多个 module,可见 library 也是少不了的。

module 和 library 多数时候我们时提供arr 和 jar 来给壳工程引用的,arr 和 jar 在编译时是不会再编译的,只会检查版本,保留一个最新的版本,既提高了 app 的编译速度,页提供一种资源冲突解决方式。

下面我方一些图来描述一下组件化,大伙仔细看看,图比文字可生动多了

modularization.png

modules.png

20170118062508842.png

20170118064804889.png

20161202165647074.png

项目如何组件化:

20170522211601227.png

3688153-dba93d79b7426568.png

问题在哪里

旧框架最大问题是业务基础库在膨胀,任意两个app需要用到的公用功能,只能将代码移入业务基础库。长时间后,这个库无法看了,里面什么都有,所有app都直接引用,耦合严重,简单归纳几个问题:

  • 业务基础库只会越来越大,功能简单地用文件夹区分,没人可以完全掌握;
  • 代码只增不减,编译时间越来越长;
  • 修改一个功能,不得不测试调用到这个功能的每个app,测试成本高;
  • 多名开发人员对业务基础库进行修改,带来较多代码冲突,沟通成本高;
  • 直接引用代码,缺少接口化和封装,业务迭代不够灵活。

一句话,整个工程要拆。

sdk: 一些公用库,各种辅助类,和第三方viewbasic: 网络访问初始化,本地缓存和第三方包等。commonbusiness: 这里为什么我会多出这一层,因为有很多公共的业务,好比公司的app是强登录的,我会把登录模块写在这里,里面也包含了一些baseActivity和BaseApplication和各个组件的一些公共方法还有组件各种的服务接口的定义。module_archives和module_knowledge: 就是两个组件,可以单独运行。

gradle学习文章

学习 gradwle 的入门系列,翻译自官方文档,适合入口看,学习各种概念

  • Gradle for Android 第一篇( 从 Gradle 和 AS 开始 )
  • Gradle for Android 第二篇( Build.gradle入门 )
  • Gradle for Android 第三篇( 依赖管理 )
  • Gradle for Android 第四篇( 构建变体 )
  • Gradle for Android 第五篇( 多模块构建 )
  • Gradle for Android 第六篇( 测试)
  • Gradle for Android 第七篇( Groovy入门 )

gradle 各种常用使用

  • Gradle 使用配置总结
  • android gradle使用详解
  • 关于Gradle配置的小结
  • android gradle自动化打包方案完整总结
  • Android Gradle 常用使用场景实现方式的总结&version=12020010&nettype=WIFI&fontScale=100&pass_ticket=uSQC2oCfGYWnnaVgn2XXQiluF%2B98YeAhtq59jdFqHYTytShgS8PE5WS0DMwzO64t)
  • Android Gradle高级用法,动态编译技术:Plugin Transform Javassist操作Class文件

飞雪无情的 gradle 教程

  • 隐藏Android签名文件和密钥信息
  • 自动生成版本信息
  • 批量控制生成的APK文件名
  • 自动瘦身APK文件
  • 善用AndroidManifest占位符
  • 自动化多渠道快速打包APK

吴小龙的gradle 的教程

  • Gradle for Android(一)基本配置、依赖管理
  • Gradle for Android(二)全局设置、自定义BuildConfig、混淆
  • Gradle for Android(三)多渠道打包、配置签名信息
3、AndroidManifest合并

每一个module都有AndroidManifest.xml,很明显,当module分别处于application和library时,它需要的AndroidManifest.xml是不同的。

  • application:定义CameraActivity、PhotoActivity、DebugMainActivity,其中DebugMainActivity定义为启动页。
  • library:只需要定义CameraActivity、PhotoActivity,最终合并到mainapp的AndroidManifest.xml,描述了photo module提供了什么页面。
sourceSets { main { if (rootProject.ext.buildBizApp) { manifest.srcFile 'src/main/debug/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' //移除debug资源 java { exclude 'debug/**' } } }}

AndroidManifest.xml的内容无什么特别,就不贴了,然后配置build.gradle,区分开发模式和集成模式对应AndroidManifest.xml文件位置。对于类似DebugMainActivity正式发布不需要的测试文件,可以放入java/debug文件夹,然后在集成模式排除。

版权声明:本文由美高梅开户送58元官网发布于美高梅开户送58元官网,转载请注明出处:你不会组件化,就是两个组件