Android组件化方案实践与思考

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hzw2017/article/details/83214753

Demo地址:https://github.com/751496032/ComponentDemo

效果图:
在这里插入图片描述

背景

Android从诞生到现在,不知不觉的走过十多个年头了,也产生了很多App,随着项目的推进不断的迭代,而App也从最初的单一功能演变成多任务功能,各种业务的错综复杂,开发人员也不断的增加,如果架构不做调整优化,会给开发带来很大的困难:

  • 各种业务代码耦合性及高,代码臃肿会越来越高,不利于团队间协同开发,维护成本也高;
  • 降低开发效率,工程的编译运行时间及长,在单一工程下,每修改一小处都要运行整个项目,导致非常耗时。
  • ……

基于代码耦合性问题,App的设计架构也不断的演变,从最初的MVC,到现在主流的MVP、MVVM,这些模式也确实起到代码解耦的效果,但还是很大局限性的;各业务间的耦合、运行效率问题都还是存在的;于是组件化思想就诞生了,组件化有如下优势:

  • 业务间代码互不干扰,解耦性好,代码复用性也高;
  • 各个组件能单独生成apk,可以单独调试,降低了编译运行时长。

组件化思想

组件化就是把单一工程的app分成多个Module,每个Module就相当于一个组件,而这些组件是不需要相互依赖的,可根据开发需求,自由将各个组件进行ApplicationLibrary模式切换进行调试开发。

组件化基础架构图
上面是组件化基础结构图,从上向下分为三部分,分别是app空壳、功能组件(业务组件)、基础组件;

  • App空壳只有一个组件就是App组件,需要依赖于各个业务组件,最终上线的就是App统筹所有的业务组件打包生成的;
  • 功能组件又称之业务组件,各个组件间并没有依赖关系,除了Login组件外,把Login组件单独分开的原因是,我认为基本上所有的业务组件都需要登录行为才可以操作,不排除少数业务组件是不需要的,于是干脆把所有的业务组件全部依赖于Login组件,在这里Login组件其实是一个共享组件;
  • 基础组件,这个很好理解就是我们封装的基础库,比如网络、路由、推送、图片等等

组件化需解决的问题

  • 模式切换,如何使每个Module在ApplicationLibrary间自由切换;
  • 依赖关系,如何处理每个Module间、工具类库的依赖关系,这个没有唯一模型,可根据项目需求也定,但一点可以肯定的是,同一层次的组件模块不能存在相互依赖的关系,不然就失去了组件化的意义了;
  • 资源冲突,如何处理App空壳中所依赖的Module间资源重名冲突;
  • 组件通信,如何处理业务组件间通信问题。
  • ……

上面这几个问题是组件化实现过程中的主要问题,解决了上述问题,组件化方案实施基本没有多大的问题,其他的一些问题可根据自身需求而定。

实现步骤

1、在项目根目录下的gradle.properties配置全局参数,方便管理各个Module的常用全局参数,比如版本号、常量等等

isModuleRun=false
compile_sdk_version=26
min_sdk_version=17
target_sdk_version=26
version_code=1
version_name=1.0

constraint_layout_version=1.1.3
support_version=26.1.0
leakcanary_version=1.6.1
arouter_version=1.3.1
arouter_annotation_version=1.2.0
eventbus_version=3.1.1

……

2、模式切换

在项目根目录下的gradle.properties设置一个boolean的变量isModuleRun,这个变量的作用就是控制业务组件ApplicationLibrary模式切换,当isModuleRun=true,组件处于Application可单独编译运行,反之则为Library是一个依赖库,在模式切换过程同时还需处理每个Module的AndroidMainfest文件的冲突,如下:
在这里插入图片描述

在每个业务Module下的build.gradle下编写切换判断的代码处理模式切换
在这里插入图片描述
isModuleRun的值不同,Module的AndroidMainfest文件内容也会有所不同,当Module处于Application下,此时是独立应用,需要配置applicationId,以及应用的启动页设置,而在Library下则不需要这些,因为我们针对不同模式下引用不同AndroidMainfest

首页在Module的main目录下创建一个module_run目录单独存放Application所需的AndroidMainfest文件,接着在Module下的build.gradle引入:
在这里插入图片描述

Lib下的AndroidMainfest文件内容
在这里插入图片描述

Application下的AndroidMainfest文件内容
在这里插入图片描述

到这里基本解决了模式切换的问题

3、资源冲突

从App空壳到基础组件,中间依赖很多其他组件,难免会有资源冲突的问题,在这情况下,建议在定义一个资源命名规范,大家统一遵守这个规范,能很好的避免资源冲突的问题,比如可以以Module名称作为前缀进行规范:
在这里插入图片描述

4、组件通信

组件间通信我们使用开源组件通信框架,比如阿里的ARouter,能很好的处理各组件间的跳转,并且同层次的组件间不会任何的依赖关系,实现了解耦的效果。使用如下:

在各组件下的build.gradle添加依赖和配置

android {
    defaultConfig {
        ...
        //注意:这里每个业务组件都需要配置
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
   	//这里在Base基础组件添加依赖即可,其他组件无需添加
    api "com.alibaba:arouter-api:${arouter_version}"
    //注解依赖需要在各个组件中添加依赖
    annotationProcessor "com.alibaba:arouter-compiler:${arouter_annotation_version}"
    ...
}

在BaseAppliction下初始化

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

简单的使用,比如获取Fragment实例、启动Activity、拦截跳转页面

/**
 * 路由管理类
 * 命名规则:/模块名/特殊描述/目标页面名称(特殊描述可选)
 * 需要登录的页面操作,带login_after字段
 */

public final class ARouterManager {

    public static final String LOGIN_AFTER="login_after";

    public static final String HomeFragment = "/home/HomeFragment";

    public static final String CartFragment="/cart/CartFragment";

    public static final String MeFragment="/me/CartFragment";

    public static final String LoginActivity="/login/LoginActivity";

    public static final String GoodsDetailActivity="/home/GoodsDetailActivity";

    public static final String ShareActivity="/login/login_after/ShareActivity";

}
-------------------------------------------------------------------------------------
//Fragment路由路径定义
@Route(path = ARouterManager.HomeFragment) //定义路由路径
public class HomeFragment extends BaseFragment implements View.OnClickListener {

}
// Fragment实例获取
Fragment fragmet = (Fragment) ARouter.getInstance().build(ARouterManager.HomeFragment).navigation()

-------------------------------------------------------------------------------------
//Activty
@Route(path = ARouterManager.GoodsDetailActivity)
@SuppressWarnings("all")
public class GoodsDetailActivity extends BaseActivity {

}
//启动
  ARouter.getInstance().build(ARouterManager.GoodsDetailActivity).navigation();

页面跳转拦截器,比如某些页面操作必须登录,我们可以先获取当前是否有登录,然后根据页面路由路径进行判断拦截页面跳转

/**
 * 页面跳转拦截器
 * 应用场景:如某些页面需要登录才可操作,可通过拦截器来统一处理跳转页面
 */
@Interceptor(priority = 7)
public class ARouterInterceptor implements IInterceptor {
    Context mContext;

    /**
     * The operation of this interceptor.
     *
     * @param postcard meta
     * @param callback cb
     */
    @Override
    public void process(final Postcard postcard, final InterceptorCallback callback) {
        boolean isLogin = SpUtils.getBoolean(mContext, SpUtils.LOGIN_KEY);
        String path = postcard.getPath();
        if (!isLogin&&path.contains(ARouterManager.LOGIN_AFTER)){
            //未登录
            ARouter.getInstance().build(ARouterManager.LoginActivity).navigation();
            callback.onInterrupt(null);
        }else {
            callback.onContinue(postcard);
        }
    }

    /**
     * Do your init work in this method, it well be call when processor has been load.
     * 在路由初始化时会加载拦截器
     * @param context ctx
     */
    @Override
    public void init(Context context) {
        mContext = context;
        Log.e("testService", ARouterInterceptor.class.getName() + " has init.");
    }
}

上面是ARouter的一些简单用法,详细可以查看官方文档。

总结

组件化并没有一个放之四海皆准的通用方案,在我认为,只要实现各个业务模块、基础模块间解耦就是一个好方案,最起码相对之前单一工程来说,已经改善了很多了,效率肯定会有提升,只有根据自己项目实际情况,进行不断改造找到适合自己项目的设计方案。如果在现有的项目中进行组件化拆分,建议先把基础组件库进行剥离,紧接着再抽离一些共享数据组件(比如登录、分享组件等等),最后才对核心业务组件下刀拆分,在拆分的过程中千万别指望一口气全部拆分完,否则一不小心就会出现项目满堂红的情况,要做到边拆分边备份,避免代码丢失的危险。

猜你喜欢

转载自blog.csdn.net/hzw2017/article/details/83214753