java路由总线_网易考拉Android客户端路由总线设计

1.前言

$ e7 |  ~% L) i7 @7 B& t3 T5 h* P/ e2 s

当前,Android路由框架已经有很多了,如雨后春笋般出现,大概是因为去年提出了Android组件化的概念。当一个产品的业务规模上升到一定程度,或者是跨团队开发时,团队/模块间的合作问题就会暴露出来。如何保持团队间业务的往来?如何互不影响或干涉对方的开发进度?如何调用业务方的功能?组件化给上述问题提供了一个答案。组件化所要解决的核心问题是解耦,路由正是为了解决模块间的解耦而出现的。本文阐述了考拉Android端的路由设计方案,尽管与市面上的方案大同小异,但更多的倾向于与考拉业务进行一定程度的结合。

, o6 H, e5 F* L: J. w/ W1.1 传统的页面跳转

' j* K' U4 f8 a- F+ Q9 g0 O页面跳转主要分为三种,App页面间跳转、H5跳转回App页面以及App跳转至H5。

/ g3 z4 e% Z# r: I6 VApp页面间跳转

) x# t8 E& s9 D9 d9 AApp页面间的跳转,对于新手来说一般会在跳转的页面使用如下代码:

1 V* {7 M. p2 {  _8 h; a, l4 q  hIntent intent = new Intent(this, MainActivity.class);intent.putExtra("dataKey", "dataValue");startActivity(intent);

93e6db929858c474367770951d60ed9d.gif对于有一定经验的程序员,会在跳转的类生成自己的跳转方法:: w0 e+ \. J/ ^6 ]' m5 y* [4 w

public class OrderManagerActivity extends BaseActivity {    public static void launch(Context context, int startTab) {        Intent i = new Intent(context, OrderManagerActivity.class);        i.putExtra(INTENT_IN_INT_START_TAB, startTab);        context.startActivity(i);    }}

5946085ee664056490886f75d0f16059.gif无论使用哪种方式,本质都是生成一个Intent,然后再通过Context.startActivity(Intent)/Activity.startActivityForResult(Intent, int)实现页面跳转。这种方式的不足之处是当包含多个模块,但模块间没有相互依赖时,这时候的跳转会变得相当困难。如果已知其他模块的类名以及对应的路径,可以通过Intent.setComponent(Component)方法启动其他模块的页面,但往往模块的类名是有可能变化的,一旦业务方把模块换个名字,这种隐藏的Bug对于开发的内心来说是崩溃的。另一方面,这种重复的模板代码,每次至少写两行才能实现页面跳转,代码存在冗余。5 P* E2 R2 s" F8 \

H5-App页面跳转; I" j: C" y3 @1 @

对于考拉这种电商应用,活动页面具有时效性和即时性,这两种特性在任何时候都需要得到保障。运营随时有可能更改活动页面,也有可能要求点击某个链接就能跳转到一个App页面。传统的做法是对WebViewClient.shouldOverrideUrlLoading(WebView, String)进行拦截,判断url是否有对应的App页面可以跳转,然后取出url中的params封装成一个Intent传递并启动App页面。0 k' w$ L, i) y; ]$ d  |7 \

感受一下在考拉App工程中曾经出现过的下面这段代码:

: U! z4 o( V& j8 w9 u/ U; h% vpublic static Intent startActivityByUrl(Context context, String url, boolean fromWeb, boolean outer) {    if (StringUtils.isNotBlank(url) && url.startsWith(StringConstants.REDIRECT_URL)) {          try {            String realUrl = Uri.parse(url).getQueryParameter("target");            if (StringUtils.isNotBlank(realUrl)) {                url = URLDecoder.decode(realUrl, "UTF-8");            }        } catch (Exception e) {            e.printStackTrace();        }    }    Intent intent = null;    try {        Uri uri = Uri.parse(url);        String host = uri.getHost();        List pathSegments = uri.getPathSegments();        String path = uri.getPath();        int segmentsLength = (pathSegments == null ? 0 : pathSegments.size());        if (!host.contains(StringConstants.KAO_LA)) {            return null;        }        if((StringUtils.isBlank(path))){            do something...            return intent;        }        if (segmentsLength == 2 && path.startsWith(StringConstants.JUMP_TO_GOODS_DETAIL)) {            do something...        } else if (path.startsWith(StringConstants.JUMP_TO_SPRING_ACTIVITY_TAB)) {              do something...        } else if (path.startsWith(StringConstants.JUMP_TO_SPRING_ACTIVITY_DETAIL) && segmentsLength == 3) {             do something...        } else if (path.startsWith(StringConstants.START_CART) && segmentsLength == 1) {             do something...        } else if (path.startsWith(StringConstants.JUMP_TO_COUPON_DETAIL)                || (path.startsWith(StringConstants.JUMP_TO_COUPON) && segmentsLength == 2)) {            do something...        } else if (canOpenMainPage(host, uri.getPath())) {             do something...        } else if (path.startsWith(StringConstants.START_ORDER)) {             if (!UserInfo.isLogin(context)) {                do something...            } else {                do something...            }        } else if (path.startsWith(StringConstants.START_SAVE)) {             do something...        } else if (path.startsWith(StringConstants.JUMP_TO_NEW_DISCOVERY)) {              do something...        } else if (path.startsWith(StringConstants.JUMP_TO_NEW_DISCOVERY_2) && segmentsLength == 3) {             do something...        } else if (path.startsWith(StringConstants.START_BRAND_INTRODUCE)                || path.startsWith(StringConstants.START_BRAND_INTRODUCE2)) {              do something...        } else if (path.startsWith(StringConstants.START_BRAND_DETAIL) && segmentsLength == 2) {              do something...        } else if (path.startsWith(StringConstants.JUMP_TO_ORDER_DETAIL)) {              if (!UserInfo.isLogin(context) && outer) {                do something...            } else {                do something...            }        } else if (path.startsWith("/cps/user/certify.html")) {               do something...        } else if (path.startsWith(StringConstants.IDENTIFY)) {             do something...        } else if (path.startsWith("/album/share.html")) {              do something...        } else if (path.startsWith("/album/tag/share.html")) {              do something...        } else if (path.startsWith("/live/roomDetail.html")) {               do something...        } else if (path.startsWith(StringConstants.JUMP_TO_ORDER_COMMENT)) {             if (!UserInfo.isLogin(context) && outer) {                do something...            } else {                do something...            }        } else if (openOrderDetail(url, path)) {            if (!UserInfo.isLogin(context) && outer) {                do something...            } else {                do something...            }        } else if (path.startsWith(StringConstants.JUMP_TO_SINGLE_COMMENT)) {              do something...        } else if (path.startsWith("/member/activity/vip_help.html")) {            do something...        } else if (path.startsWith("/goods/search.html")) {            do something...        } else if(path.startsWith("/afterSale/progress.html")){              do something...        } else if(path.startsWith("/afterSale/apply.html")){              do something...        } else if(path.startsWith("/order/track.html")) {             do something...        }    } catch (Exception e) {        e.printStackTrace();    }    if (intent != null && !(context instanceof Activity)) {        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);    }    return intent;}

122121e83e114832f007dbca06800d9d.gif这段代码整整260行,看到代码时我的内心是崩溃的。这种做法的弊端在于:" I3 h9 k# j1 H$ R+ d

3 g1 J' |! g, s* ~9 Q  X

判断不合理。上述代码仅判断了HOST是否包含StringConstants.KAO_LA,然后根据PATH区分跳转到哪个页面,PATH也只判断了起始部分,当URL越来越多的时候很有可能造成误判。

% J. [/ @! Y9 I

耦合性太强。已知拦截的所有页面的引用都必须能够拿到,否则无法跳转;+ W# ^& [8 P/ O. r

代码混乱。PATH非常多,从众多的PATH中匹配多个已知的App页面,想必要判断匹配规则就要写很多函数解决;% o1 K# h! S* C# ^8 z; a0 S( ~

拦截过程不透明。开发者很难在URL拦截的过程中加入自己的业务逻辑,如打点、启动Activity前添加特定的Flag等;# B2 l. Z- C& ~3 Q% u, I# V8 t

没有优先级概念,也无法降级处理。同一个URL,只要第一个匹配到App页面,就只能打开这个页面,无法通过调整优先级跳转到别的页面或者使用H5打开。

I; f% h, n0 IApp页面-H5跳转

% v4 i; Q2 B3 t8 h) |5 h& z这种情况不必多说,启动一个WebViewActivity即可。) v- f* p: j0 W% A

1.2 页面路由的意义& s  c3 z# {. n0 o* D6 l1 ~* r" Z$ S

路由最先被应用于网络中,路由的定义是通过互联的网络把信息从源地址传输到目的地址的活动。页面跳转也是相当于从源页面跳转到目标页面的过程,每个页面可以定义为一个统一资源标识符(URI),在网络当中能够被别人访问,也可以访问已经被定义了的页面。路由常见的使用场景有以下几种:

' ~. ^/ b: G, M8 F# S8 o8 @: i3 Q2 l% [( k- I

App接收到一个通知,点击通知打开App的某个页面(OuterStartActivity)

3 V, y5 e- U0 P8 f+ ?

浏览器App中点击某个链接打开App的某个页面(OuterStartActivity)

/ J# _* O. [- ~2 P' b( R+ K

App的H5活动页面打开一个链接,可能是H5跳转,也可能是跳转到某一个native页面(WebViewActivity)

* {% v* ~3 q! q& g8 T# |2 m

打开页面需要某些条件,先验证完条件,再去打开那个页面(需要登录)

c# b- z  {) @4 ^* x. I

App内的跳转,可以减少手动构建Intent的成本,同时可以统一携带部分参数到下一个页面(打点)' S) c9 m2 D3 l! M& F9 i* S2 A. v

除此之外,使用路由可以避免上述弊端,能够降低开发者页面跳转的成本。: z2 E3 e4 z. X$ l( c2.考拉路由总线

& X* J3 S( C8 D9 u, @9 a" Y- L) I

! d8 i2 t. ~, V( G9 Y- J2.1 路由框架

8 Z; l; n, I! v$ [" W/ k, P- l

694d273ff71b9b5c56523f6dc4e8121e.png

09cbd123390a9a18cda45871d8349f23.gif( n6 |1 J; i6 C8 A  [* b8 X

考拉路由框架主要分为三个模块:路由收集、路由初始化以及页面路由。路由收集阶段,定义了基于Activity类的注解,通过Android Processing Tool(以下简称“APT”)收集路由信息并生成路由表类;路由初始化阶段,根据生成的路由表信息注入路由字典;页面路由阶段,则通过路由字典查找路由信息,并根据查找结果定制不同的路由策略略。. E1 B9 g# H" {6 n& e

9 {+ C* }' k& x

2.2 路由设计思路

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值