美文网首页
app组件化实战解析

app组件化实战解析

作者: 暴走的小青春 | 来源:发表于2020-12-15 17:25 被阅读0次

    1.背景

    公司当前的app都处于一个app中,并且由于大量使用kotlin,编译速度本来相对于纯java的代码会慢,全量编译一次相当的痛苦,所以要把项目向组件化的方向改进,本文主要阐述晓黑板的组件化思路以及实现

    2.为何要实现组件化

    相对于插件化的不稳定,组件化可以说是很多公司争相去做的方案了,那先来分析下组件化的优点:

    1.单独运行

    每个模块能够各自独立,不依赖于其他的模块,能做到独立运行,单独运行能大幅度减少编译的时间,从而大幅度提高开发效率

    2.解耦

    对代码的解偶性大幅提高,每个模块相当于一个小的app,做到了低耦合,高内聚,开发起来只需要关注当前模块的代码

    3.减少bug发生几率

    3.减少bug几率,不知道大家又没有发现,有时候你改动一个activity的layout的布局,并修改了此activity相应的代码,但其实这个layout也被其他activity使用,而你在另一个activity没有改相应的配置,这样很容易造成意想不到的奔溃,影响开发效率

    3.实践组件化遇到的问题及解决方案

    1.关于router跳转的兼容性问题

    经过对比分析各个组件化通讯的实现方案

    具体分析看此篇文章 [分析arouter实战]

    最终选取了arouter作为路由的通讯方案,但是衍生出几个我们要解决的问题

    1.1 原有跳转方式

    
    private final static String PARAMETER_KEY1 = "key1";�
    private final static String PARAMETER_KEY2 = "key2";�
    ...
    /**
     * @param context 上下文
     * @param par1    参数1 
     * @param par2    参数2(可选参数,缺损时写Optional.absent())
     */
    public static void startActivity(Context context, Object par1,� Optional<Object> par2) {
            Intent intent = new Intent(context, B_Activity.class);
            intent.putExtra(PARAMETER_KEY1, par1);
            intent.putExtra(PARAMETER_KEY2, par2);
            context.startActivity(intent);
    }
    

    1.2 aroute的跳转方式

    ARouter.getInstance().build("/test/activity1")
                        .withString("name", "老王")
                        .withInt("age", 18)
                        .withBoolean("boy", true)
                        .withLong("high", 180)
                        .withString("url", "https://a.b.c")
                        .withSerializable("ser", testSerializable)
                        .withParcelable("pac", testParcelable)
                        .withObject("obj", testObj)
                        .withObject("objList", objList)
                        .withObject("map", map)
                        .navigation();
    

    1.3 兼容的方式

    首先所有的page跳转都调用routeToPagerProvider,最后一个参数是可变参数,然后在相应模块接收相应的参数
    /**
     * 各个模块之间的action 默认不带返回值
     */
    fun routeToPagerProvider(context: Context, path: String, vararg params: Any) {
        ARouter.getInstance().build(path).navigation(context, object : NavCallback() {
            override fun onArrival(postcard: Postcard?) {
            }
    
            override fun onFound(postcard: Postcard?) {
                (postcard?.provider as PagerProvider).routerTo(context, params)
            }
        })
    }
    //相应模块接收的参数
    @Route(path = PAGER_IMAGE)
    class ImagePagerProvider : PagerProvider {
        override fun init(context: Context) {
    
        }
    
        override fun routerTo(context: Context, array: Array<*>) {
            var paramList = array.toMutableList()
            var params1 = paramList.getOrElse(0) { "" } as MutableList<Image>?
            var params2 = paramList.getOrElse(1) { 0 } as Int
            startImagePager(context, params1, params2, params3, params4)
        }
    }
    
    其实本质就是利用了arouter的provider进行的跳转,只是做了一层封装而已,那有人会问了,到底arouter里的provider的跳转方式和传统写法的跳转方式有何不同,两者效率如何?

    详细见如下文章,相信读完此文章会带你了解arouter的本质

    2.关于业务组件的拆分

    2.1 关于common-module的定义

    对于common-module的个人理解,应当是聚合了每个lib所封装的module + 基类 +工具类+每个上层module要用到的业务代码+要用到的公用的第三方的lib

    2.1.1 每个lib所封装的module的含义:

    比如一开始你用的图片加载框架是glide,你可能每个地方都这么写

    Glide.with(context)
                .load(url)
                .apply(options)
                .into(imageView);
    

    当这样的代码越来越多时你肯定会建一个utils类去精简你的代码

    比如这样

    GlideUtils.loadImage(context,url,imageView)
    

    当然有的时候为了对比图片加载的速度,加载时消耗的内存,可能用ab test的方案,可能在某个特定的情况下用到了别的图片加载框架,比如:fresco 这时候就需要一个lib去以接口的形式去封装,也就是以特定的lib(glide or fresco)封装成新的lib库的形式,以此封装好的lib库可能会上传每个公司自己的maven仓库,写法比如这样:

    ImageLoader.getInstance().loadImager(context,url,imageView)
    

    那就到此为止了么,当然不是!

    此时可能在业务层有对拿到的url做相关业务的逻辑性转换,从而调用不同的ImageLoader的方法

    可能这样

    // 某个在utils类
     public static void processImage(Context context, String imageUrl, ImageView view) {
            String url = convertUrl(imageUrl);
            ImageLoader.getInstance().loadImager(context,url,imageView)
        }
    //业务代码的转换
    public static String convertUrl(String url) {
         //url转换
        }
    

    比如有些需求可能对图片加载的url有个关于业务层代码的转化或者做一些逻辑性的操作

    此时项目里两处可以加载图片的方式分别是你封装过的utils类和ImageLoader的方式

    比较直接的办法就是common-module对上层提供一个用api的方式来引用新的module,对下层用implementation来引用你封装过的imageLoader库,也就起到了承上启下的作用,上层对于imageLoader库包括原生的加载苦并没有直接可调用的方式,只能调用common-moodule的方法来加载图片,起到了很好的隔离效果

    当然这对于网络框架和图片框架采用这种形式的比较多

    2.1.2 每个上层module要用到的业务代码的含义:

    这里的代码可能会比较的杂,当然如果到后面common-module过于膨胀的话,你可以另外启用一个module来做此功能

    2.13 其他

    当然其他的,包括基类 +工具类+公用的第三方的lib 毫无疑问是放入common-module中的,这里再提下module中事件通信所要拿的model类,应该放哪里,个人认为,还是那句话,如果觉得过于膨胀,直接依赖另外一个module来做此功能

    3 关于接口module通信的方案

    3.1 一对一的分发策略

    简单来说就是一个模块的action牵扯到另外一个module的代码,可能另外一个还需要携带另外一个module的返回值的情况,也就是所说的接口下沉,当然我们没有必要重复造轮子,arouter已经帮我们实现了,我们直接封装一层就可以了,这是对于action的封装,分别是有返回值和无返回值的

    /**
     * 各个模块之间的action 默认不带返回值
     */
    fun routeToActionProvider(path: String, vararg params: Any) {
        ARouter.getInstance().build(path).navigation(null, object : NavCallback() {
            override fun onArrival(postcard: Postcard?) {
            }
    
            override fun onFound(postcard: Postcard?) {
                (postcard?.provider as UnitActionProvider).routerTo(params)
            }
        })
    }
    
    /**
     * 各个模块之间的action 需要带有返回值的provider
     * type参数是需要返回的类型
     */
    fun <T : Any?> routeToReturnActionProvider(path: String, type: Class<T>?, vararg params: Any): T? {
        var postcard = ARouter.getInstance().build(path).navigation(null)
        if (postcard != null) {
            var result = (postcard as ActionProvider<T>)
            return result.routerTo(params)
        }
        return null
    }
    //这里在相应module的代码
    @Route(path = ACTION_LOGOUT)
    class LogoutProvider : UnitActionProvider() {
        override fun routerTo(array: Array<*>) {
           // do logout代码
        }
    
        override fun init(context: Context) {}
    }
    

    3.2 一对多或是多对多的分发策略

    1.比如一个module的action触发了多个模块的改变,也就是常说的消息总线的方案

    关于消息总线的方案具体可以看这篇文章:事件总线的分析

    最后我们选取用livedatabus来做消息总线,但其实鲜为人知的是,上述文章虽然很好提供了思路,但是有个大坑(上述文章没提到,不过在最新的提交里已解决)

    具体产生的原因和解决方案可以看如下关于如下文章 :[livedatabus解析和注意事项]

    4. 总结以及后续

    本文简单阐述了组件化的优点,以及原有项目改造成组件化遇到的相关的问题,但还有几个小问题还需要去解决

    1 事件接收和发送

    现在在采用livedatabus时不用去关注注册和取消注册,虽然事件的event是定义在一个类中,但难免有人直接用自定义的key采用发送和接收,需要一个约束去定义此

    相关文章

      网友评论

          本文标题:app组件化实战解析

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