第二章 Android组件化初探

2.1 组件化演示案例

demo地址:https://github.com/syg13579/assembleDemo

2.1.1 概述

软件开发进程也是架构的演进过程,就拿Android来说,从最开始的MVC ,MVP ,MVVP ,再到后来的组件化,插件化,但归根到底一切的一切,都是为了项目更好的维护、迭代,降低开发成本。

在一个项目的开发过程中,前期我们可能把所有的功能模块都放到了一个moudle中,这样能够快速的开发,但随着项目壮大,开发人员和功能的增加,就回导致代码越来越臃肿,各个模块之间的耦合越来越重,牵一发而动全身,这个时候为了保证项目质量,我们就需要对项目进行重构。

我们可以根据业务模块进行查分,把不同的业务模块放到不同的moudle中,实现各个业务之间的结构,他们又共同依赖底层公共库,这就是模块化的概念,但是当多个模块中涉及到相同功能时代码的耦合又会增加,例如有两个模块都需要视频播放的功能,把视频播放放到两个组件中就会出现代码重复的问题,放到公共库感觉也不是很好,这时候就用组件化来解决这个问题

2.1.2 模块化和组件化

2.1.2.1 模块化

具体的业务模块,例如商品详情模块,商品发布模块 ,搜索模块

2.1.2.2 组件化

单一的功能组件,如视频播放组件、分享组件等,每个组件都可以以一个单独的 module 开发,并且可以单独抽出来作为 SDK 对外发布使用腾讯享学课堂 Android 高级架构系列免费直播公开课https://ke.qq.com/course/341933?taid=8674704361863085 模块化和组件化的思想是一样的,都是对代码进行拆分,但模块化是按功能模块进行查分(业务导向),组件化是按功能模块进行查分(功能导向),模块化的颗粒度更大一些,组件的颗粒度更小一些,一个项目中模块和组件同时存在也是很常见的,各自负责各自的事情

如上图所示 是个组件化项目的基本架构

  • 基础库、公共库:项目所需要的基础操作类,工具类 ,第三方库的引入封装 ,app宿主功能,各个模块,各个组件都依赖这个库
  • 组件层:项目用的功能模块或者业务模块,如:登录模块,视频播放组件,分享组件等
  • 应用层:宿主工程,APP的主项目,APP入口和主架子

2.1.3 组件化Demo

地址如下: https://github.com/syg13579/assembleDemo 我根据demo项目从以下几个方面来讲解

  1. 项目分析
  2. 组件application和library动态切换
  3. 组件间的数据传递和方法调用
  4. 组件类(例如:Fragment)的获取,以及夸组件页面跳转和通讯

2.1.3.1 项目分析

如上图所示,项目的主要结构

  • 应用层:app 项目的主入口
  • 组件层:goods login 商品详情页和登录组件
  • 基础库层:assemblebase用来各个组件数据和方法交互的 ,base是常用的工具类,各种类库的封装

2.1.3.2 组件application和library动态切换

在开发过程中,为了能够实现快速开发,组件能够独立运行就显的特别重要,moudle一般分为两种

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

我们可以通过配置可动态进行application和library的切换,我们在各个组件的gradle.properties文件中配置一个控制切换的变量

然后在build.gradle中就可以通过isRunAlone变量来进行application和library的切换了,主要设计的点有三部分

  • plugin属性的配置
  • applicationId的配置
  • AndroidManifest的配置
if (isRunAlone.toBoolean()) {
  apply plugin: 'com.android.application'
} else {
  apply plugin: 'com.android.library'
}
android {
  compileSdkVersion 26
  defaultConfig {
    if (isRunAlone.toBoolean()) {
      applicationId "ppzh.jd.com.goods"
    }
    minSdkVersion 15
    targetSdkVersion 26
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguardrules.pro'
    }
  }
  sourceSets {
    main {
      if (isRunAlone.toBoolean()) {
        manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
      } else {
        manifest.srcFile 'src/main/AndroidManifest.xml'
      }
    }
  }
}

如果以上配置就可以实现application和library的切换了

2.1.3.3 组件间的数据传递和方法调用

由于主项目、组件之间,组件和组件之间不能直接通过引用进行数据传递和方法调用,那么在开发的过程中怎么进行数据传递和方法调用呢,可以通过「接口」+「实现」的方式进行,assemblebase基础库就是用来进行数据传递和方法调用的,它被所有组件所依赖,assemblebase提供各个组件对外提供数据和方法调用的抽象service ,同时还有serviceFactory对service进行操作,各个组件在初始化的时候对各自的service进行实现。同时中也会提供所有的 Service 的空实现,以避免引起的空指针异常就以登录模块为例,对外提供两个数据

public interface ILoginService {
/**
 * 是否已经登录
 *
 * @return
 */
boolean isLogin();
/**
 * 获取登录用户的 AccountId
 *
 * @return
 */
String getAccountId();
}

相关的serviceFactory类如下,可以通过serviceFactory拉取相关service的实例

public class ServiceFactory {
    private ILoginService loginService;
    private IGoodsService goodsService;
    /**
     * 禁止外部创建 ServiceFactory 对象
     */
    private ServiceFactory() {
    }
    /**
     * 通过静态内部类方式实现 ServiceFactory 的单例
     */
    public static ServiceFactory getInstance() {
        return Inner.serviceFactory;
    }
    private static class Inner {
        private static ServiceFactory serviceFactory = new ServiceFactory();
    }
// ------------------------LoginService------------------------
    /**
     * 接收 Login 组件实现的 Service 实例
     */
    public void setLoginService(ILoginService loginService) {
        this.loginService = loginService;
    }
    /**
     * 返回 Login 组件的 Service 实例
     */
    public ILoginService getLoginService() {
        if (loginService == null) {
            loginService = new EmptyLoginService();
        }
        return loginService;
    }
}

在login组件中只需要实现ILoginService,并通过serviceFactory进行设置

public class LoginService implements ILoginService {
    @Override
    public boolean isLogin() {
        return false;
    }
    @Override
    public String getAccountId() {
        return null;
    }
}

在login的appliction中进行service的设置

public class LoginApp extends BaseApp {
    @Override
    public void onCreate() {
        super.onCreate();
        initModuleApp(this);
        initModuleData(this);
    }
    @Override
    public void initModuleApp(Application application) {
        ServiceFactory.getInstance().setLoginService(new LoginService());
    }
    @Override
    public void initModuleData(Application application) {
    }
}

但是有这样一个问题:在集成到app中,LoginApp是没有被执行的,这个怎么解决呢,我们可以通过反射进行解决

public class AssembleApplication extends BaseApp {
    @Override
    public void onCreate() {
        super.onCreate();
        initModuleApp(this);
        initModuleData(this);
        initComponentList();
    }
    @Override
    public void initModuleApp(Application application) {
    }
  
    @Override
    public void initModuleData(Application application) {
    }
    //初始化组件
    //通过反射初始化
    private void initComponentList(){
        for (String moduleApp : AppConfig.moduleApps) {
            try {
                Class clazz = Class.forName(moduleApp);
                BaseApp baseApp = (BaseApp) clazz.newInstance();
                baseApp.initModuleApp(this);
                baseApp.initModuleData(this);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

如上所示就完成了

2.1.3.4 组件类(例如:Fragment)的获取,以及夸组件页面跳转和通讯

fragment的获取也是通过service来完成的

public interface IGoodsService {
    /**
     * 创建 GoodsFragment
     * @param bundle
     * @return
     */
    Fragment newGoodsFragment(Bundle bundle);
}

相关组件实现该接口就行

各个组件间页面的跳转可以通过阿里的ARouter实现,我是通过设置ComponentName来实现的,但这种方式好像并没有实现真正的代码隔离

/**
 *
 * 去登陆
 *
 * 跨组件页面跳转
 */
private void toLogin(){
    Intent intent = new Intent();
    intent.setComponent(new ComponentName(mContext, "ppzh.jd.com.login.LoginActivity"));
    startActivityForResult(intent,LOGIN_REQUEST_CODE);
}

2.1.4 总结

通过上面就整体实现了项目组件化,在以后也可以更多的运用组件化来进行项目开发

2.2 WanAndroid APP 组件化项目实战附demo

2.2.1 简介

本项目基于 组件化 + Arouter + Jetpack + Rxjava + Retrofit + AOP 等框架实现的一款开源项目。如有任何疑问或bug 欢迎给我提issues,项目会一直维护下去,一起努力打造一个完美的app。 https://github.com/1170762202/WanAndroid

2.2.2 版本更新

  • module单独编译运行
  • module-main迁移至app,并移除module-main

2.2.3 效果图

2.2.4 主要功能

  • 首页、项目、广场、公众号、我的
  • 登录、注册 动画交互
  • 搜索页面共享元素动画过渡
  • 项目页面仿高德地图滑动面板交互
  • 广场页面tab跟随滑动系数渐变、列表采用谷歌爸爸的flexboxlayout流式布局
  • 广场页面根据滑动系数给indicator添加动画效果
  • 公众号页面点击左上角为一个90°的arc交互动画,列表数据从下往上过渡的动画效果
  • 我的页面仿百度外卖个人中心水波纹效果
  • 整体采用Material Design设计风格
  • 首页有彩蛋哦!

2.2.5 项目目录结构

|- WanAndroid
||-- app // app 入口
||librarys //library库
||--library-aop// aop 封装(登录校验、点击)
||--library-db// room数据库封装
||--library-network// 网络请求封装(livedata+rxjava+retrofit)
||--library-base// 基础封装(BaseAc、BaseFg、BaseUtil等)
||--library-common//共用的组件、适配器、api返回实体类等
||--library-widget// 控件封装
||--modules// 功能模块
||--module-home// 首页模块
||--module-login// 登录模块
||--module-project// 项目模块
||--module-square // 广场模块
||--module-public //公众号模块
||--module-mine//我的模块
||--module-web//网页模块
||-- README.md

module单独编译运行说明

gradle.properties 文件下有个"集成开发模式" 和 "组件开发模式"的切换开关 true表示组件独立运行,false表示一个library

isRunModule=true

2.2.6 主要开源框架

flexbox-layout
RxJava
RxAndroid
Retrofit
okhttp
Glide
BaseRecyclerViewAdapterHelper
EventBus
Arouter
ImmersionBar
Particle
banner
LoadSir
MagicIndicator
MMKV
SmartRefreshLayout
AgentWeb
aop
PersistentCookieJar