Android-组件化开发

333 阅读9分钟
原文链接: www.jianshu.com

参考资料:
www.jianshu.com/p/60c1b9ddd…

上一篇我们学习了ARouter,讲到ARouter是组件化开发的基础,那现在让我们开始组件化开发吧。

1.组件化,模块化概念

对于组件化的开发,首先要了解模块化及组件化的概念,这正是是好多小伙伴模糊的,所以我们有必要说明一下。

1.1 组件

组件的英文单词是component,意思是组件、部件、元件。在App工程上,件是构成业务或者功能模块的基本单位也就是组件不能被继续拆分,原则上,组件与组件之间互不依赖。比如我们的图片上传功能,可以叫图片上传组件,但不能叫图片上传模块,而且组件具有可替换性和重复利用性,可替换性指比如我们的地图定位组件可以用百度的,也可以用高德的。重复性是指我的地位功能可能在首页被调用,在其他页面也要被调用。

1.2 模块

模块的英文单词是Module,由多个组件构成。也就是从粒度上来看,模块要比组件大,也就是模块包含组件。举个例子:以安居客为例,安居客app内有二手房和新房相关功能, 这个二手房和新房就属于模块,但这二手房和新房模块都用到了分享房源的功能,而这个分享功能就属于组件了。

1.3 组件和模块的关系

image.png

我们以上图为例简单说一下,首页界面的天猫,聚划算,饿了么都属于模块同时也是一个业务,我们可以叫做业务模块,而我们继续点击天猫进入天猫超市,里面的分享功能,支付所用到的sdk等都算组件。那我们总结一下:模块化和组件化只不过是我们根据项目的需求,定义不同,一个工程可以由多个模块组成,每个模块可以由多个组件构成,模块可以单独存在,正是多个模块构成了整个项目。不论是模块化还是组件化,它们的目的都是把项目解耦便于代码的管理。高内聚、低耦合使开发人员分工明确,提高开发效率。

1.4 业务

我们上面说了模块也可以叫做业务模块,那么什么是业务呢,我们还是以上图为例:在淘宝的首页天猫,聚划算,充值中心等这些看起来完全不同的业务我们称之为Business业务。而天猫点击进入会有搜索业务,预约功能,签到等功能,聚划算点击进入也会有搜索业务,预约功能,签到等功能,像这种业务我们称之为基础业务。我们来张图说明一下Business业务和基础业务及组件之间的关系:

image.png

图中Business业务公用基础业务,而基础业务的实现可能依赖于某组件。

1.4 Library

我们刚才说了组件,比如上图提到的图片上传组件,网络组件,还有分享组件等,那这里要介绍另外一个概念:Library。你比如我们的分享组件用的是友盟分享sdk,我们的网络组件依赖的是okhttp库,这些依赖的三方库都称之为Library

2.组件化实践

参考资料:
mp.weixin.qq.com/s/-gC8JpmmC…
mp.weixin.qq.com/s/8_8gGpkpO…
我准备从以下几个方面来介绍组件化:

  • 代码解耦
  • 组件或模块的单独运行
  • 数据的传递与ui跳转
  • 生命周期管理

2.1代码解耦

ok,我们先看看我们的demo实现效果,就先简单上张图吧,不整gif图片了:

image.png

你看到的第一眼可能觉得简单,没什么大不了,但是这不同于我们平时的实现方法,平时我们的首页,资讯,我的都是在app中实现,但今天我们的目录结构是这样的:


image.png

我先来说一下各个目录的含义:

  • app
    app模块是我们的壳工程,平时我们最核心的代码都要往这里写,但今天不一样,app模块的职能改变了,它最主要的目的是对其他模块进行整合,确保app能正常运行,里面只有一些简单的代码。
  • commonlib
    commonlib 是一个依赖的Library,为什么说她是library呢?因为它的职能是把整个project所用到的library,公用的lib都放到这里,供其它模块引用,就不需要每个模块都写一遍了。以demo为例:


    image.png

注意:采用api代替implementation代替的目的就是为了让其他模块能够引用到

  • module_home,module_zixun,module_user,
    这三个模块是整个project最核心的地方, 对应我们上面效果图中的首页,资讯,我的三个tab。
    这样做的好处显而易见 :张三开发首页,李四开发资讯,王五开发我的 互不影响,提高开发效率 ,这也是组件化的优势
  • x5webview
    x5webview是作为组件存在的,是我基于腾讯TBS浏览服务封装的(类似webview作用),和分享组件,网路请求组件是一个级别,为了其他模块的调用。

这里就基本实现了代码的解耦,你可能有一个疑问?你这一会组件一会模块是不是有点懵,其实个人认为组件化和模块化只不过是概念不同,模块化包含组件化,组件化是模块化开发中不可缺少的,二者只不过是划分方式不同,实现方面没有太大区别。

2.2 组件或模块的单独运行

组件或模块的单独运行时组件化的又一亮点。还是以我们的demo为例,如果我们想单独运行module_home模块改怎么办?
首先你要知道一点组件变为一个能独立运行的app要变动那几个地方?我个人认为一般有三个地方需要变动:

  • 组件或模块的build.gradle 中的apply plugin: 'com.android.library' 变 apply plugin: 'com.android.application'
  • 组件或模块的build.gradle 中的defaultConfig配置中的applicationId 根据情况动态变动,如果作为组件或模块存在则不需要,反之亦然。
  • 功能清单文件的变动,你想如果作为组件或模块存在我们是不需要下面这些东西的:


    image.png

ok,那我们就依次解决这三个问题:
对于是否要将模块或组件单独运行,我们需要定义变量去控制,还是以demo为例,我们在project的gradle.properties文件中定义如下:


image.png

于是第一个问题解决了,我们只需要在模块的build.gradle中顶部添加如下代码:


image.png

于是第二个问题也解决了,我们只需要在defaultConfig中增加以下代码即可:


image.png

这里在提个醒默认情况下我们的applicationId值是我们的包名一致

于是第三个问题也解决了,我们还是在对应的build.gradle文件中的android中增加以下代码:


image.png

注意这个我们是要配置相关路径的,如:src/main/runalone/AndroidManifest.xml
那我们在这个位置就有相关文件夹,大家请看:


image.png

里面的内容如下(就是平常的啦):
单独运行的清单文件:


image.png

作为组件或模块的清单文件:


image.png

既然实现了组件的单独运行那么单独调试也就解决了。

2.3 数据的传递与ui跳转跳转

ui的跳转我们主要借助于阿里的Aroute,数据的传递可以Aroute和EventBus结合使用,效果更佳。
ARoute的介绍请参考我之前的文章Android-ARouter
还是以项目为例:

  • 跳转调用其它组件(这里主要是分享组件)
    比如我们要从首页模块调用x5组件,那么请看相关代码:
    首页相关代码,再点击跳转X5按钮后:
@OnClick(R2.id.homemodule_button)
    public void onViewClicked() {
        ARouter.getInstance().build(COMPONENT_X5).withString("url","https://www.baidu.com/").navigation();
    }

x5分享组件相关代码:


image.png

数据的回调可以结合EvnetBus,这里就不详细说了。

  • 利用IProvider跨moudle的服务调用,主要用于非Activity,因为我在写demo中发现在Activity中不好使。直接上代码了:
    应用场景是我们zhongmodule_zixun模块中的Fragment要调用X5组件中的X5Test类中需要用到的方法:
  1. 首先在common_lib中定义X5CompService接口继承IProvider接口,如下:


    image.png

2.x5组件中的X5Test类实现X5CompService接口


image.png

3.在zhongmodule_zixun模块中的Fragment中获取X5CompService实例进行调用,进行了简单的toast


image.png

注意:既然是zhongmodule_zixun模块中的Fragment中获取X5CompService的实现类进行调用,那么它就要把X5组件作为依赖:


image.png

2.4 生命周期管理

生命周期的管理我们主要是通过common_lib中的baseApplication(注:其他module中的Application都要继承baseApplication,确保唯一性)来管理,这里我直接贴出BaseApplication中的所有代码:

public class BaseApplication extends Application {
    //是否开启调试
    private  boolean isDebug =true;
    //全局唯一的context
    private static BaseApplication application;
    //Activity管理器
    private ActivityManage activityManage;

    @Override
    public void onCreate() {
        super.onCreate();
        application = this;
        activityManage = new ActivityManage();

        //初始化路由
        initRouter();
    }

    /**
     * 程序终止的时候执行
     */
    @Override
    public void onTerminate() {
        super.onTerminate();
        exitApp();
    }

    /**
     * 退出应用
     */
    public void exitApp() {
        activityManage.finishAll();
        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(0);
    }

    /**
     * 初始化路由
     */
    private void initRouter() {
        //必须在初始化之前写入这两行
        if (isDebug) {
            //打印日志
            ARouter.openLog();
            //开始调试
            ARouter.openDebug();
        }
        //ARouter的实例化
        ARouter.init(this);
    }


    /**
     * 获取全局唯一上下文
     *
     * @return BaseApplication
     */
    public static BaseApplication getApplication() {
        return application;
    }


    /**
     * 返回Activity管理器
     */
    public ActivityManage getActivityManage() {
        if (activityManage == null) {
            activityManage = new ActivityManage();
        }
        return activityManage;
    }
}

ActivityManage使我写的Activity管理工具类,详情请看相关代码。

2.5 其他

  • 混淆
    混淆我们是放在各个Module还是app的proguard-rules.pro文件中,答案是app的proguard-rules.pro文件中,因为如果在组件中进行混淆,一旦代码出现了bug,这个时候就很难根据日志去追踪bug产生的原因,而且不同组件分别进行混淆非常不方便维护和修改。
  • 使用ButterKnife遇到的问题
    当我将组件单独运行时是没有问题的,可如果作为module时就会出现什么需要常量等问题,解决办法是:


    image.png

    将原本的R.id.homemodule_button改为R2.id.homemodule_button,如果找不到R2,请确保你project的build.gradle中:

image.png

但依赖的butterknife版本是8.4.0:


image.png

每一个用到的模块中的build.gradle中都需要配置(默认模块都依赖common_lib,否则单独添加8.4.0依赖):
apply plugin: 'com.jakewharton.butterknife' 以及


image.png

就这么多吧,详情请看组件化Demo.