美文网首页
Android组件化方案实践与思考

Android组件化方案实践与思考

作者: HZWei7 | 来源:发表于2018-10-21 23:25 被阅读10次

    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的一些简单用法,详细可以查看官方文档。

    总结

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

    相关文章

      网友评论

          本文标题:Android组件化方案实践与思考

          本文链接:https://www.haomeiwen.com/subject/ivoizftx.html