Android组件化实践

最近做组件化的一点心得

什么是组件化

不同于插件化直接让项目的功能模块成为插件(apk)直接运行,组件化依然只有一个主工程(app),但是项目中的功能模块可以被单独编译并运行,开发过程中就可以将每个功能模块独立出来,分配给不同的人去开发。

优点

  • 代码解耦
  • 模块单独编译,减少编译等待时间
  • 功能整体在单个模块中,方便功能移植

组件化带来的便利体现在这3个方面,需要拆分不同的模块,代码势必要进行解耦。模块拆分完成后,可以单独编译,相比以前编译整个项目,减少等待时间,同时也方便以后功能移植。

如何实现

组件化主要通过gradle来实现,module项目是否可以编译取决于module的build.gradle文件的apply plugin是library还是application,所以可以通过一个可以配置的标识,让gradle根据不同的标识来执行不同的构建方式。

来看一个具体的实现例子

Android组件化实践_第1张图片

  • app 主应用module
  • baselib 基本base库
  • httplib 网络框架
  • login 登录模块
  • router 路由模块

组件的gradle配置

除了app,其他的4个module都在根目录下的gradle.properties中添加一个布尔类型的标志位。

baselibDebug = false
httpDebug = false
loginDebug = false
routerDebug = false

分别修改4个module根目录下的build.gradle文件,通过判断布尔值,选择是application还是library,以baselib为例

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

由于设置了false这里会去执行library,但是library Module不能有applicationId,所以需要删除build.gradle中的defaultConfig节点下的applicationId

    defaultConfig {
        applicationId "com.haibuzou.baselib" //删除
        minSdkVersion 16
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

baselib已经变成了一个库工程,baselib中的manifest.xml是作为application的manifest,现在baselib变成了library就需要新的manifest.xml。根据不同条件使用不同的manifest.xml文件,通过配置sourceSets来完成

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

准备2个manifest.xml
Android组件化实践_第2张图片

  • 作为library工程的manifest需要删除测试用声明的activity。
  • theme和icon的配置也需要删除避免manifest merge的时候重复声明导致冲突
  • debug中的manifest可以做任何修改

app的gradle配置

组件在测试运行时会变成application无法被依赖,所以需要配置app不去依赖测试组件,同样还是在根目录下的gradle.properties中添加布尔值标志位

appDebug = false

修改app下的gradle中的依赖

if(appDebug.toBoolean()){
    compile project('router')
}else{
    compile project(':login')
}

httplib,baselib,router都会与login依赖所以只依赖了login模块,appDebug为true时选择去依赖router而不是baselib,为什么会这么做,后文会讲到

到目前为止模块的配置已经完成,如果需要运行组件,只需要将appDebug设置为true,测试的组件的xxxDebug设置为true,然后点一下gradle sync按钮就可以了,现在来看看组件化中碰到的问题。

问题

  • module中对application代码引用
  • 依赖包重复引用
  • 不同模块之间activity的跳转
  • 代码的解耦

module中对application代码引用

我在开发中会写一些方法或者声明静态全局变量在application中,方便全局的调用,最典型的比如声明静态baseContext指向application本身,后续开发会使用这个baseContext作为全局的Context来使用。

我的解决办法是在baselib中创建application,将原来app项目的application中的代码迁移到baselib的application中,app项目的application直接继承baselib的application。因为baselib作为基本库,其他的组件都会与baselib有依赖关系,组件在测试运行时可以直接将baselib的application声明在debug的Manifest.xml中,组件测试运行就可以引用之前app项目中的application的代码,组件变为library时也可以直接引用baseApplication中的代码,具体的代码在最后的例子项目中这里就不细说了。

Android组件化实践_第3张图片

依赖包重复引用

不同的组件依赖很容易重复依赖或者产生冲突,并且根本无法管理,我用的是比较普遍的做法, 将一个组件作为基本组件,gradle中做所有的依赖包操作,其他的组件都与这个基本组件建立依赖关系。本文的例子中我选择router作为基本组件。

Android组件化实践_第4张图片

不同module之间activity 跳转

组件化后activity分别在不同的module中,显示跳转直接获取activity的class这条路就不要想了。隐式跳转跳转是个办法,但是管理起来有点麻烦。所以我还是用的显示跳转,使用反射来获取activity的class属性来执行跳转,给大家分享一个我项目中的一个路由类的写法

/**
 * Created by haibuzou on 2017/4/1.
 * 路由跳转管理类
 */
public class RounterManager {

    private static Map rounterMap =new HashMap<>();
    private static final int LOGIN = 1;

    private static String HTTP_SCHEME = "http";

    static {
        rounterMap.put("login", LOGIN);
    }


    public static void rounter(String uriStr,Activity activity){
        if (TextUtils.isEmpty(uriStr)) {
            return;
        }
        try {
            Uri uri = Uri.parse(uriStr);
            String scheme = uri.getScheme();
            String host = uri.getHost();
            //判断当前是scheme是http web链接执行web跳转
            if (scheme.contains(HTTP_SCHEME)) {
                Class webActivity = Class.forName("com.rnhighfinance.ui.HywinWebActivity");
                Method goToPage = webActivity.getDeclaredMethod("goToPage", Context.class, String.class);
                goToPage.invoke(webActivity.newInstance(), activity, uriStr);
                return;
            }
            //scheme不是http 判断host 决定跳转到哪个activity
            int rounterType = rounterMap.get(host);
            switch (rounterType) {
                case LOGIN: 
                    activity.startActivity(new Intent(activity, Class.forName("com.hywin.loginlib.login.LoginActivity")));
                break;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

路由的设计思路类似于web链接跳转,比如http://www.baidu.com会跳转到百度,uri的链接有2个主要的组成部分:scheme和host

  • scheme代表协议比如http,ftp等等
  • host代表主机地址比如www.baidu.com

Android组件化实践_第5张图片

RounterManager首先通过传入的uri链接获取scheme和host

 String scheme = uri.getScheme();
 String host = uri.getHost();

判断scheme包含http执行webActivity跳转。通过反射执行webActivity的goToPage方法,完成跳转。

if (scheme.contains(HTTP_SCHEME)) {
      Class webActivity = Class.forName("com.rnhighfinance.ui.HywinWebActivity");
      Method goToPage = webActivity.getDeclaredMethod("goToPage", Context.class, String.class);
      goToPage.invoke(webActivity.newInstance(), activity, uriStr);
            return;
 }

如果scheme不包含http,会通过host匹配到login,执行跳转到LoginActivity
这里写图片描述

int rounterType = rounterMap.get(host);
switch (rounterType) {
   case LOGIN: 
      activity.startActivity(new Intent(activity, Class.forName("com.hywin.loginlib.login.LoginActivity")));
      break;
}

代码的解耦

代码的解耦其实兼具苦力活和技术活,因为需要迁移代码,所以会有很多复制粘贴的苦力活。同时还需要处理错综复杂的依赖关系,所以也是门技术活。如果依赖层次太复杂不好拨开的时候,个人建议使用反射去解耦,java的反射用法其实并不复杂。

获取class
Class test = Class.forName("包名")

通过class获取Method方法

方法的参数需要传入参数的class类型

Method method = test.getDeclaredMethod("方法名",String.class)

创建对象和获取method之后 调用方法

method.invoke(test.newInstance(),"hehe")

test.newInstance()方法用来创建对象,调用的是无参的构造方法

通过有参数的构造方法创建对象

Constructor constructor = test.getConstructor(String.class,int.class)
constructor.newInstance("123",123)

简单的说就是通过Class获取Constructor,通过Constructor.newInstance()方法来创建对象

参考资料

http://www.tuicool.com/articles/77nINvU

项目链接
https://github.com/haibuzou/Modularity

你可能感兴趣的:(Android开发实用代码)