一个Android路由框架的诞生之路

通过前面三篇文章,相信你们对组件化都有了必定程度的理解。bash

在这个过程当中一直强调了组件化的一个基础设施:路由!没有它组件化能够说是步履维艰,本篇文章咱们就来谈谈一个组件化路由框架诞生过程当中的那些思考微信

本文概述

一、为何须要路由

这个问题其实咱们以前谈到过,并且有过组件化实践或者尝试的同窗必定有切身感觉。明确一个前提:各个业务模块之间不会是相互隔离而是必然存在一些交互的;框架

  • 在Module A须要跳转到Module B某界面,而咱们通常都是使用强引用的Class显式的调用;
  • 在Module A须要调用Module B提供的方法,例如别的Module调用用户模块退出登陆的方法;

这两种调用形式你们很容易明白,正常开发中你们也是绝不犹豫的调用。可是咱们在组件化开发的时候却有很大的问题:ide

  • 模块B的Activity Class在本身的Module B,那Module A必然引用不到,显式跳转行不通;
  • 同理,直接去调用某个Module的方法也行不通;

由此:必然须要一种支持组件化需求的交互方式,提供UI跳转以及方法调用的能力。组件化

二、一个路由库须要知足什么

首先这个路由库也是一个技术组件,在总体组件化层次的设计中处于Lib层,做为一项基础库。那么路由库不只仅须要知足自身的能力,同时势必要知足一项基础库该有的条件:动画

  • Api友好,接入简单、低成本
  • 具有UI跳转和方法调用的能力
  • 功能稳定
  • 可定制化

三、淘汰过的方式

任何系统或框架,虽然在高版本中看起来都很完美,可是实际上一开始并不是就是如此,都是一步步实践、迭代改善到基于当前相对完美状态的。好比咱们以前就思考过以下方式:ui

3.一、基于隐式意图

各位老司机都知道,Android中打开一个Activity,能够有两种方式,显示意图和隐式意图。既然显式意图致使了强引用,那么咱们使用隐式意图,既能够打开Activity,同时也不会形成Module间的强引用。this

评价:这种方式确实能够完成路由的UI跳转功能,可是依赖于Manifest文件的修改,同时参数也存在不便传递的问题,所以不作推荐。url

3.二、基于事件,使用广播或EventBus

这种思路也很容易想到,既然不能直接交互,那么就隐式的来,在须要交互的地方发通知,而后接收方根据不一样的通知类型作出不一样的处理。spa

/**
     * EventBus的事件类
     */
    public class InteractEvent {
        public int type;
        public String param;// eg:String类型参数一
        public ParamObject paramObject;// eg:Object类型参数二
    }

    /**
     * 处理不一样的交互设置
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(InteractEvent event) {
        if(event.type == EventType.JUMP_LOGINACTIVITY){
            Intent intent = new Intent(mContext,LoginActivity.class);
            intent.putExtra("param",event.param);
            intent.putExtra("paramObject",event.paramObject);
            mContext.startActivity(intent);
        }
    }
复制代码

评价:这种交互的方式是可行的,可是能够明显看出,比较复杂,对于界面跳转比较多的场景,接入及维护成本较高。

3.三、调用一个固定的方法

咱们在须要交互的类中加上方法,方法签名固定,而后给交互类打上一个标签。这样在别的组件中咱们须要这个交互类的时候经过标签拿到,调用这个固定的方法。思路是这样,如下提供一种方式的伪代码,有不一样的实现。

public interface IDoAction{
        void doAction(HashMap hashMap);
    }
    
    public class LoginActivity extends Activity implements IDoAction{
        public void doAction(HashMap hashMap){
            // 1.获取参数、Type
            hashMap.get();
            // 2.跳转
        }
    }
    
    // 调用
    Dispatcher.get(url).doAction(hashMap);
复制代码

备注使用HashMap做为参数的缘由:每一个交互类须要的参数不同,而方法签名必须固定才能经过接口去调用,传递HashMap这个参数能够包含多个不一样类型的参数。

评价:最不推荐,使用繁琐,侵入性太强,改形成本极大。

四、一种好的路由实践

总结以上几种很差的实践方式,都在于侵入性强,接入及维护成本高等。那反过来推就是一个好的路由须要具有低侵入性、易接入、自动化等特性。 上述第三种方案咱们能够吸取一点的是每一个交互类打上一个标签,记录这个映射关系,方便在别的Module进行获取。

eg: easyrouter://main/Detail    ————》 MainDetailActivity
复制代码

顺着这个映射往下想,这个映射保存了标签和Activity,那么打开Activity只须要知道这个标签便可。举例:标签A和ActivityA对应,那么咱们只要遇到标签A就知道它想要打开的是ActivityA。同时若是咱们处理好了打开Activity须要的传参问题就离自动化迈出了一大步。

问题就简化成了两个:

  1. 映射关系,咱们可使用String字符串做为标签,既保证通用性又能够保证惟一性。利用一个Map保存这个字符串和Activity的映射关系,这样能够保证在别的Module能经过字符串获取到咱们须要的Activity
  2. 传参以及Activity各类特性(利用动画、onActivityResult生命周期)的支持

关于第二个问题实际上就是将这个字符串尽量多的解析到Android多须要的数据,好比参数传递、动画、生命周期等。关于这个解析能够有两种方式:

  1. 直接简单粗暴在String后面拼一个参数,这个参数的格式是Json,到达目标界面以后目标界面再去解析;
  2. 制定必定规则经过路由就解决好,到目标界面直接像正常Android开发同样去获取;
eg:     easyrouter://routertest?name=liuzhao&company=mycompany
复制代码

五、方法调用的实现

方法调用看起来均可以经过上述:基于事件及调用一个固定的方法等方式来实现,可是使用起来一定复杂无比,各个Module之间交互不只改造困难,维护成本也很高。

注意各个Module向外提供的方法一定不同:须要不一样的方法签名。并且从改造及维护成本考虑,最好能够像是在一个Module里同样直接调用,IDE能够自动提示出来方法参数。

那咱们就想把Module向外提供的方法内聚到一个类里,只向外暴露这个类,简称这个Module的交互服务类。这样别的Module调用的时候就能够想直接调用普通类的方法同样简单方便了。

那咱们就剩下一个问题:别的Module如何获取你的交互服务类呢?很容易想到上面提到的映射,可是此种场景下若是使用字符串作Key真的能够吗? 若是使用字符串作Key,别的Module拿到的Value只能肯定是一个Class,具体的Class类型却不清楚,调用具体的方法尤为是被IDE提示,更是不可能。问题又简化成了如何让咱们知道拿到的Class中有哪些方法呢?

通过屡次思考,终于想到了一个解决方案:Module须要向外暴露的方法,咱们经过一个Interface来定义,这个Interface定义在Lib层也就是说每一个Module均可以访问到,而保存映射关系的Key咱们也使用这个Interface。那么映射表里保存的就是:

private static Map<Module暴露接口Interface, Module暴露接口的实现类InterfaceImpl> moduleInteracts = new HashMap<>();
复制代码

那么别的Module在获取这个服务类时就能够直接经过在Lib层定义的Interface来获取,而后经过泛型转换成这个接口,然后直接调用相应方法便可,就像调用一个普通方法同样简单

public static <T extends IBaseModuleService> T getModuleService(Class<T> tClass) {
    if (moduleInteracts.containsKey(tClass)) {
        return (T) moduleInteracts.get(tClass);
    }
    return null;
}

调用:EasyRouter.getModuleService(BaseModuleService.ModuleInteractService.class).runModuleInteract(MainActivity.this);
               
复制代码

六、路由的最佳实践

6.一、编译时注解

通过4、五两节咱们知道了路由相对较好的实践,但同时咱们可否让这个过程自动化呢?其实能够借助编译时注解技术自动生成映射表,这样在接入的时候就更加简单方便,只须要在对应的Activity上打上一个注解,配置相应的字符串,这个映射表就自动生成。

@DisPatcher("easyrouter://routertest")
public class MainActivity extends Activity {
    ......
}

生成代码

@Override
public void initActivityMap(HashMap<String, Class> activityMap) {
    activityMap.put("easyrouter://routertest", MainActivity.class);
}

复制代码

6.二、拦截器

6.2.一、统一判断

在实际开发中,咱们常常会遇到些统一的操做,好比某些应用是须要用户先登录的,那么在用户浏览以后的下一步操做时用户进行各类点击都须要进行判断是否登录,未登陆则跳转到登录界面,登录以后则放行。

正常状况咱们须要在每个点击的地方进行判断,可是明显费时费力,既然咱们已经作了路由,全部的界面跳转都须要通过咱们,那咱们天然能够进行统一的判断,在路由进行分发时候进行判断,知足拦截器条件则进行拦截。

6.2.二、重定向

若是咱们须要对App的功能进行A/BTest,咱们该如何进行呢?方式确定有不少,可是不必定通用。注意咱们已经有了路由,结合路由来作A/BTest的话更加方便:

  • 首先咱们给路由加一个拦截器,每一条跳转都会通过这个拦截器的判断;
  • 经过路由实现界面跳转,在路由解析过程当中咱们识别到了须要跳转的是A模块;
  • 通过拦截器的判断,若是A/BTest实验命中的是B模块,则将这个路由进行重定向到B模块;

备注:重定向的好处还有更多,好比紧急状况下的热修复替换成H5界面等。

6.三、过程监听

就是监听打开Activity的过程,如

  • 打开前进行数据的准备;
  • 打开后的回调;
  • 未匹配到目标Activity的降级等;

本文主要介绍一个Android路由框架诞生过程当中的思考,在下篇文章将会具体推荐一个路由框架。

广告时间

今日头条各Android客户端团队招人火爆进行中,各个级别和应届实习生都须要,业务增加快、日活高、挑战大、待遇给力,各位大佬走过路过千万不要错过!

本科以上学历、非频繁跳槽(如两年两跳),欢迎加个人微信详聊:KOBE8242011

欢迎关注
相关文章
相关标签/搜索