组件化方案:JIMU之UI路由(二)

前言

在上一篇组件化方案:JIMU之UI路由(一)中,我们简单介绍了JIMU中用于UI跳转的UIRouter,较为扼要的阐述了它存在的原因,如何集成,如何使用基础功能以及一些常见的排错。

这一篇中,会较为细致的阐述其原理,功能特性。

原理

如果不使用HOOK技术,那么此类路由最终都会回归到系统API
-- Leobert

在展开讨论之前,一定要记住我上面这句话,无论是谁给出的UI路由方案,在Activity跳转上,最终都会体现为

Context#startActivity(Intent intent)

JIMU中的实情

仔细了解过JIMU的朋友们都知道:JIMU是编译期隔离、运行期不隔离的。这一点决定了我们不需要使用ClassLoader,我们需要做的是采用一种映射技术:在编译期自动创建映射、按照映射编写可以通过编译的代码,在运行期遵循映射执行相应的逻辑。这样就满足了组件化的核心:“隔离与发现”

严格的数学上的映射:两个非空集合A与B间存在着对应关系f,而且对于A中的每一个元素x,B中总有有唯一的一个元素y与它对应,就这种对应为从A到B的映射,记作f:A→B。

而且假如B到A也满足该条件,就是一种特殊的情况:一一映射(或称双射)

而我设计的UIRouter是一种一一映射的场景:Enum(host+path) <--> Enum(Activity)。

为什么废弃了UIRouter中priority的设计:我们在编码阶段所知的是host和path,需要根据它找到Activity,所以是Enum(host+path) --> Enum(Activity),而加入priority,则升维成:Enum(host+path+priority) --> Enum(Activity)。这并不是无法实现,而是增加了集成和使用的成本,加入priority之后,按照设计常理,不适合做成双射。一般而言会按照Chain of Responsibility进行设计,对priority做范围划分,这就破坏了设计的初衷

实现host+path到Activity的映射

在UIRouter中,我们使用RouteNode注解来为Activity指定他的Path,而Host根据Module环境确定,一个Module中,最终生成一张路由表,例如:

public class AppUiRouter extends BaseCompRouter {
  @Override
  public String getHost() {
    return "app";
  }

  @Override
  public void initMap() {
    super.initMap();
    routeMapper.put("/main",MainActivity.class);
    routeMapper.put("/uirouter/demo/3",Demo3Activity.class);
    paramsMapper.put(Demo3Activity.class,new java.util.HashMap<String, Integer>(){{put("foo", 8); put("EXTRA_STR_BAR", 8); }});
    routeMapper.put("/uirouter/demo/4",Demo4Activity.class);
    paramsMapper.put(Demo4Activity.class,new java.util.HashMap<String, Integer>(){{put("foo", 8); put("EXTRA_STR_BAR", 8); }});
    routeMapper.put("/uirouter/demo/5",Demo5Activity.class);
    paramsMapper.put(Demo5Activity.class,new java.util.HashMap<String, Integer>(){{put("foo", 9); }});
    routeMapper.put("/uirouter/demo/2",Demo2Activity.class);
    paramsMapper.put(Demo2Activity.class,new java.util.HashMap<String, Integer>(){{put("foo", 8); put("EXTRA_STR_BAR", 8); }});
    routeMapper.put("/uirouter/demo/8",Demo8Activity.class);
    paramsMapper.put(Demo8Activity.class,new java.util.HashMap<String, Integer>(){{put("foo", 8); }});
    routeMapper.put("/uirouter/demo/7",Demo7Activity.class);
    paramsMapper.put(Demo7Activity.class,new java.util.HashMap<String, Integer>(){{put("foo", 8); }});
    routeMapper.put("/uirouter/demo/6",Demo6Activity.class);
    paramsMapper.put(Demo6Activity.class,new java.util.HashMap<String, Integer>(){{put("EXTRA_OBJ_FOO", 10); }});
    routeMapper.put("/uirouter/demo/1",Demo1Activity.class);
    routeMapper.put("/uirouter/demo",UiRouterDemoActivity.class);
  }
}

我们将路由信息存入了routeMapper:

protected Map<String, Class> routeMapper = new HashMap<>();

最终回归到系统API:

 if (routeMapper.containsKey(path)) {
            Class target = routeMapper.get(path);
            if (bundle == null) {
                bundle = new Bundle();
            }
            Map<String, String> params = UriUtils.parseParams(uri);
            Map<String, Integer> paramsType = paramsMapper.get(target);
            UriUtils.setBundleValue(bundle, params, paramsType);
            Intent intent = new Intent(context, target);
            intent.putExtras(bundle);
            if (requestCode > 0 && context instanceof Activity) {
                ((Activity) context).startActivityForResult(intent, requestCode);
                return true;
            }
            context.startActivity(intent);
            return true;
        }

如何处理参数?

再回忆一下我开始说的内容,一定会回归到系统API,通过Intent跳转Activity时,我们携带参数是利用了Bundle,这里也不会例外。
JIMU中,可以直接使用Bundle携带参数以及像往常一样从Intent中获取参数。但是这还不够。

实际情景中,为了扩大APP体量以及利用社交享受社交红利,会利用社交媒体分享mobile-web站点页面,为了给用户更沉浸的体验以及引导转化,会要求从mobile-web站点回流到APP,也就是我们常说的web唤醒

而可行的技术就是通过向系统注册APP可以处理某类数据(特定的uri)然后从web中发出处理相应uri的请求。

从UX的角度来说,当然希望用户继续按照刚才的页面操作下去,这时候一个问题摆在眼前:如何通过Uri传递参数给相应的Activity?

从之前文章中的讨论以及一些常识,我们知道外部唤醒使用的是一个特定协议的Url。传递参数的问题就变为:
如何使用Url包装入参?
我们知道,一个Url可以拆解为以下部分

[schema]//[host]:[port][path][queryString][#hash]

可以用于包装参数的无非:

  • path
  • queryString
    而框架中采用的是queryString。

理由:
path已经用于映射,在path中采用参数表达式不够自由且容易出bug,例如PHP的一款支持restful风格的框架laravel就在其路由匹配上出现过各种bug;
qs足够清晰明了。

从queryString到Bundle

在回忆一下前文提到的:“一定会回归到系统API”,Activity想要获取Bundle参数,一定会牵涉到类型,向Bundle写入时,也会牵涉到类型。故而:一定需要知道需要的参数的类型。Autowired注解应运而生。

所以利用注解生成了如下的参数关系:

 paramsMapper.put(Demo6Activity.class,new java.util.HashMap<String, Integer>(){{put("EXTRA_OBJ_FOO", 10); }});

"EXTRA_OBJ_FOO"是Bundle的key,10代表了参数类型。过多的细节不做深入。需要注意的是,JIMU目前支持以下类型:

  • 基本类型
  • 基本类型对应的装箱类型
  • String
  • Parcelable接口实现类
  • Object,通过Gson(最老版本使用了fastjson)对Object进行序列化和反序列化。

注意,不支持其他的Serializable的实现类,例如,Foo类就是不可以的:

public class Foo implement Serializable {
    public String bar;
}

对比以下两个Bundle类的API,就会理解原因

  public <T extends Parcelable> T getParcelable(@Nullable String key) 
 public Serializable getSerializable(@Nullable String key) 

而JIMU中取参数赋值用的是对成员变量直接赋值:

  substitute.foo = substitute.getIntent().getStringExtra("foo");
  substitute.bar = substitute.getIntent().getStringExtra("EXTRA_STR_BAR");

注入代码生成的路径在一个包内:

package com.luojilab.componentdemo.router.cases;

/**
 * Auto generated by AutowiredProcessor */
public class Demo2Activity$$Router$$Autowired implements ISyringe {}

package com.luojilab.componentdemo.router.cases;
@RouteNode(path = "/uirouter/demo/2", desc = "使用bundle传递参数")
public class Demo2Activity extends TestActivity {}

故而:Field需要声明为public或者default

这里扯开一句,其实在去年我编写了一个完全由兴趣驱动的项目:MagicBox,用来简化以及规范项目中对InstanceState的处理,因为后续的商业项目都不是从零开始的,就没有考虑投入商业实战。在这个库中,几乎实现了Bundle支持的所有的参数的读写,默认值填充,不限制可见性修饰符,处理父类中的参数,处理引用对象内的参数,处理引用对象父类中声明的参数。但是我也不鼓励大家去研究这个项目,当时未考虑将其加入JIMU的原因有几点:JIMU中的方法已经足够了;MagicBox采用的反射,一定程度影响效率;MagicBox的使用更复杂。

UIRouter的一些特性

其实在前文中已经零零碎碎说了不少了。新版本中对Log输出的支持进行了大力改进。新版本的Demo代码也提供了相应的sample,如果本周没有合并到master分支的话,请关注dev分支

sample:

  • Demo1Activity 无参数跳转
  • Demo2Activity 使用bundle传递参数
  • Demo3Activity 使用Url传参
  • Demo4Activity Url和Bundle同时包含参数,以url为准
  • Demo5Activity Parcelable和Serializable
  • Demo6Activity 使用json字符串传参
  • Demo7Activity 必须参数,必须参数缺失,不使用抛出功能,可以看到log输出的信息。
  • Demo8Activity 必须参数2,必须参数缺失,使用抛出异常功能,以及使用安全模式。

这里就不给大家详细展开了,Demo中也进行了一定的解释,大家也可以进行一些额外的测试,例如:url的path不匹配(错误的path),未加载mapping的情况下进行跳转,错误的参数值(无法parse,Json结构异常等)

关于外部唤醒

此次Demo中包含了一种特殊情况的处理策略,注意它并不是一个规范,只是一种特殊情况的可行性策略。不要引起误会。外部唤醒,需要实际情况实际分析,如有必要将通过issue进行讨论,并最终进行总结,不在本文展开。

PS:如果继续收到大家对UIRouter使用的困惑,再更新本文做更详细的展开

--

之前放了讨论群的链接居然被禁了。。尴尬。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 背景介绍: 张明庆老哥之前在得到工作时,开源了DDAndroidComponent项目,演示组件化思路及实现,本人...
    leobert阅读 4,052评论 10 8
  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情况下的生命周期:在用户参与的情况下...
    AndroidMaster阅读 3,018评论 0 8
  • 第一次接触东野圭吾的作品,这是一篇情节紧凑的推理小说,也是一个温暖真挚的童话故事。 小说采用倒叙的方式,三个青年小...
    绿豆鲨_juju阅读 2,505评论 1 3
  • 晚上八点半,我们终于抵达泸沽湖,路上整整花了12个小时。一路上我睡了又醒、醒了又睡,想集中精神看点东西可...
    七月生吾阅读 403评论 0 0