![【移动应用开发技术】Android客户端网易考拉软件的路由设计方法_第1页](http://file4.renrendoc.com/view/e0cc1af47b0419d9e1401f9a136b4e7d/e0cc1af47b0419d9e1401f9a136b4e7d1.gif)
![【移动应用开发技术】Android客户端网易考拉软件的路由设计方法_第2页](http://file4.renrendoc.com/view/e0cc1af47b0419d9e1401f9a136b4e7d/e0cc1af47b0419d9e1401f9a136b4e7d2.gif)
![【移动应用开发技术】Android客户端网易考拉软件的路由设计方法_第3页](http://file4.renrendoc.com/view/e0cc1af47b0419d9e1401f9a136b4e7d/e0cc1af47b0419d9e1401f9a136b4e7d3.gif)
![【移动应用开发技术】Android客户端网易考拉软件的路由设计方法_第4页](http://file4.renrendoc.com/view/e0cc1af47b0419d9e1401f9a136b4e7d/e0cc1af47b0419d9e1401f9a136b4e7d4.gif)
![【移动应用开发技术】Android客户端网易考拉软件的路由设计方法_第5页](http://file4.renrendoc.com/view/e0cc1af47b0419d9e1401f9a136b4e7d/e0cc1af47b0419d9e1401f9a136b4e7d5.gif)
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】Android客户端网易考拉软件的路由设计方法
本文阐述了考拉Android端的路由设计方案,尽管与市面上的方案大同小异,但更多的倾向于与考拉业务进行一定程度的结合。1.1传统的页面跳转页面跳转主要分为三种,App页面间跳转、H5跳转回App页面以及App跳转至H5。App页面间跳转App页面间的跳转,对于新手来说一般会在跳转的页面使用如下代码:Intent
intent
=
new
Intent(this,
MainActivity.class);
intent.putExtra("dataKey",
"dataValue");
startActivity(intent);对于有一定经验的程序员,会在跳转的类生成自己的跳转方法: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);
}
}无论使用哪种方式,本质都是生成一个Intent,然后再通过Context.startActivity(Intent)/Activity.startActivityForResult(Intent,int)实现页面跳转。这种方式的不足之处是当包含多个模块,但模块间没有相互依赖时,这时候的跳转会变得相当困难。如果已知其他模块的类名以及对应的路径,可以通过Intent.setComponent(Component)方法启动其他模块的页面,但往往模块的类名是有可能变化的,一旦业务方把模块换个名字,这种隐藏的Bug对于开发的内心来说是崩溃的。另一方面,这种重复的模板代码,每次至少写两行才能实现页面跳转,代码存在冗余。H5-App页面跳转对于考拉这种电商应用,活动页面具有时效性和即时性,这两种特性在任何时候都需要得到保障。运营随时有可能更改活动页面,也有可能要求点击某个链接就能跳转到一个App页面。传统的做法是对WebViewClient.shouldOverrideUrlLoading(WebView,String)进行拦截,判断url是否有对应的App页面可以跳转,然后取出url中的params封装成一个Intent传递并启动App页面。感受一下在考拉App工程中曾经出现过的下面这段代码:public
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<String>
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;
}这段代码整整260行,看到代码时我的内心是崩溃的。这种做法的弊端在于:判断不合理。上述代码仅判断了HOST是否包含StringConstants.KAO_LA,然后根据PATH区分跳转到哪个页面,PATH也只判断了起始部分,当URL越来越多的时候很有可能造成误判。耦合性太强。已知拦截的所有页面的引用都必须能够拿到,否则无法跳转;代码混乱。PATH非常多,从众多的PATH中匹配多个已知的App页面,想必要判断匹配规则就要写很多函数解决;拦截过程不透明。开发者很难在URL拦截的过程中加入自己的业务逻辑,如打点、启动Activity前添加特定的Flag等;没有优先级概念,也无法降级处理。同一个URL,只要第一个匹配到App页面,就只能打开这个页面,无法通过调整优先级跳转到别的页面或者使用H5打开。App页面-H5跳转这种情况不必多说,启动一个WebViewActivity即可。1.2页面路由的意义路由最先被应用于网络中,路由的定义是通过互联的网络把信息从源地址传输到目的地址的活动。页面跳转也是相当于从源页面跳转到目标页面的过程,每个页面可以定义为一个统一资源标识符(URI),在网络当中能够被别人访问,也可以访问已经被定义了的页面。路由常见的使用场景有以下几种:App接收到一个通知,点击通知打开App的某个页面(OuterStartActivity)浏览器App中点击某个链接打开App的某个页面(OuterStartActivity)App的H5活动页面打开一个链接,可能是H5跳转,也可能是跳转到某一个native页面(WebViewActivity)打开页面需要某些条件,先验证完条件,再去打开那个页面(需要登录)App内的跳转,可以减少手动构建Intent的成本,同时可以统一携带部分参数到下一个页面(打点)除此之外,使用路由可以避免上述弊端,能够降低开发者页面跳转的成本。2.考拉路由总线2.1路由框架考拉路由框架主要分为三个模块:路由收集、路由初始化以及页面路由。路由收集阶段,定义了基于Activity类的注解,通过AndroidProcessingTool(以下简称“APT”)收集路由信息并生成路由表类;路由初始化阶段,根据生成的路由表信息注入路由字典;页面路由阶段,则通过路由字典查找路由信息,并根据查找结果定制不同的路由策略略。2.2路由设计思路总的来说,考拉路由设计追求的是功能模块的解耦,能够实现基本路由功能,以及开发者使用上足够简单。考拉路由的前两个阶段对于路由使用者几乎是无成本的,只需要在使用路由的页面定义一个类注解@Router即可,页面路由的使用也相当简单,后面会详细介绍。功能设计路由在一定程度上和网络请求是类似的,可以分为请求、处理以及响应三个阶段。这三个阶段对使用者来说既可以是透明的,也可以在路由过程中进行拦截处理。考拉路由框架目前支持的功能有:1.支持基本Activity的启动,以及startActivityForResult回调;✅2.支持不同协议执行不同跳转;(kaola://、http(s)://、native://等)✅3.支持多个SCHEME/HOST/PATH跳转至同一个页面;((pre.).(.hk))✅4.支持路由的正则匹配;✅5.支持Activity启动使用不同的Flag;✅6.支持路由的优先级配置;✅7.支持对路由的动态拦截、监听以及降级;✅以上功能保证了考拉业务模块间的解耦,也能够满足目前产品和运营的需求。接口设计一个好的模块或框架,需要事先设计好接口,预留足够的权限供调用者支配,才能满足各种各样的需求。考拉路由框架在设计过程中使用了常见的设计模式,如Builder模式、Factory模式、Wrapper模式等,并遵循了一些设计原则。(最近在看第二遍EffectiveJava,对以下原则深有体会,推荐看一下)针对接口编程,而不是针对实现编程这条规则排在最前面的原因是,针对接口编程,不管是对开发者还是对使用者,真的是百利而无一害。在路由版本迭代的过程中,底层对接口无论实现怎样的修改,也不会影响到上层调用。对于业务来说,路由的使用是无感知的。考拉路由框架在设计过程中并未完全遵循这条原则,下一个版本的迭代会尽量按照这条原则来实现。但在路由过程中的关键步骤都预留了接口,具体有:RouterRequestCallbackpublic
interface
RouterRequestCallback
{
void
onFound(RouterRequest
request,
RouterResponse
response);
boolean
onLost(RouterRequest
request);
}路由表中是否能够匹配到路由信息的回调,如果能够匹配,则回调onFound(),如果不能够匹配,则返回onLost()。onLost()的结果由开发来定义,如果返回的结果是true,则认为开发者处理了这次路由不匹配的结果,最终返回RouterResult的结果是成功路由。RouterHandlerpublic
interface
RouterHandler
extends
RouterStarter
{
RouterResponse
findResponse(RouterRequest
request);
}路由处理与启动接口,根据给定的路由请求,查找路由信息,根据路由响应结果,分发给相应的启动器执行后续页面跳转。这个接口的设计不太合理,功能上不完善,后续会重新设计这个接口,让调用方有权限干预查找路由的过程。RouterResultCallbackpublic
interface
RouterResultCallback
{
boolean
beforeRoute(Context
context,
Intent
intent);
void
doRoute(Context
context,
Intent
intent,
Object
extra);
void
errorRoute(Context
context,
Intent
intent,
String
errorCode,
Object
extra);
}匹配到路由信息后,真正执行路由过程的回调。beforeRoute()这个方法是在真正路由之前的回调,如果开发者返回true,则认为这条路由信息已被调用者拦截,不会再回调后面的doRoute()以及执行路由。在路由过程中发生的任何异常,都会回调errorRoute()方法,这时候路由中断。ResponseInvokerpublic
interface
ResponseInvoker
{
void
invoke(Context
context,
Intent
intent,
Object...
args);
}路由执行者。如果开发需要执行路由前进行一些全局操作,例如添加额外的信息传入到下一个Activity,则可以自己实现这个接口。路由框架提供默认的实现:ActivityInvoker。开发也可以继承ActivityInvoker,重写invoke()方法,先实现自己的业务逻辑,再调用super.invoke()方法。OnActivityResultListenerpublic
interface
OnActivityResultListener
{
void
onActivityResult(int
requestCode,
int
resultCode,
Intent
data);
}特别强调一下这个Listener。本来这个回调的作用是方便调用者在执行startActivityForResult的时候可以通过回调来告知结果,但由于不保留活动的限制,离开页面以后这个监听器是无法被系统保存(saveInstanceState)的,因此不推荐在Activity/Fragment中使用回调,而是在非Activity组件/模块里使用,如View/Window/Dialog。这个过程已经由core包里的CoreBaseActivity实现,开发使用的时候,可以直接调用CoreBaseActivity.startActivityForResult(intent,requestCode,onActivityResultListener),也可以通过KaolaRouter.with(context).url(url).startForResult(requestCode,onActivityResultListener)调用。例如,要启动订单管理页并回调:KaolaRouter.with(context)
.url(url)
.data("orderId",
"replace
url
param
key.")
.startForResult(1,
new
OnActivityResultListener()
{
@Override
public
void
onActivityResult(int
requestCode,
int
resultCode,
Intent
data)
{
DebugLog.e(requestCode
+
"
"
+
resultCode
+
"
"
+
data.toString());
}
});RouterResultpublic
interface
RouterResult
{
boolean
isSuccess();
RouterRequest
getRouterRequest();
RouterResponse
getRouterResponse();
}告知路由的结果,路由结果可以被干预,例如RouterRequestCallback.onLost(),返回true的时候,路由也是成功的。这个接口不管路由的成功或失败都会返回。不随意暴露不必要的API“要区别设计良好的模块与设计不好的模块,最重要的因素在于,这个模块对于外部的其他模块而⾔言,是否隐藏其内部数据和其他实现细节。设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。然后,模块之间只通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况。这被称为封装(encapsulation)。”(摘自EffectiveJava,P58)举个例子,考拉路由框架对路由调用的入参做了限制,一旦入参,则不能再做修改,调用者无需知道路由框架对使用这些参数怎么实现调用者想要的功能。实现上,由RouterRequestWrapper继承自RouterRequestBuilder,后者通过Builder模式给用户构造相关的参数,前者通过Wrapper模式装饰RouterRequestBuilder中的所有变量,并在RouterRequestWrapper类中提供所有参数的get函数,供路由框架使用。单一职责无论是类还是方法,均需要遵循单一职责原则。一个类实现一个功能,一个方法做一件事。例如,KaolaRouterHandler是考拉路由的处理器,实现了RouterHandler接口,实现路由的查找与转发;RouterRequestBuilder用于收集路由请求所需参数;RouterResponseFactory用于生成路由响应的结果。提供默认实现针对接口编程的好处是随时可以替换实现,考拉路由框架在路由过程中的所有监听、拦截以及路由过程都提供了默认的实现。使用者即可以不关心底层的实现逻辑,也可以根据需要替换相关的实现。2.3考拉路由实现原理考拉路由框架基于注解收集路由信息,通过APT实现路由表的动态生成,类似于ButterKnife的做法,在运行时导入路由表信息,并通过正则表达式查找路由,根据路由结果实现最终的页面跳转。收集路由信息首先定义一个注解@Router,注解里包含了路由协议、路由主机、路由路径以及路由优先级。@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public
@interface
Router
{
/**
*
URI协议,已经提供默认值,默认实现了四种协议:https、http、kaola、native
*/
String
scheme()
default
"(https|http|kaola|native)://";
/**
*
URI主机,已经提供默认值
*/
String
host()
default
"(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?";
/**
*
URI路径,选填,如果使用默认值,则只支持本地路由,不支持url拦截
*/
String
value()
default
"";
/**
*
路由优先级,默认为0。
*/
int
priority()
default
0;
}对于需要使用路由的页面,只需要在类的声明处加上这个注解,标明这个页面对应的路由路径即可。例如:@Router("/app/myQuestion.html")
public
class
MyQuestionAndAnswerActivity
extends
BaseActivity
{
……
}那么通过APT生成的标记这个页面的url则是一个正则表达式:(https|http|kaola|native)://(pre\.)?(\w+\.)?kaola\.com(\.hk)?/app/myQuestion\.html路由表则是由多条这样的正则表达式构成。生成路由表路由表的生成需要使用APT工具以及Square公司开源的javapoet类库,目的是根据我们定义的Router注解让机器帮我们“写代码”,生成一个Map类型的路由表,其中key根据Router注解的信息生成对应的正则表达式,value是这个注解对应的类的信息集合。首先定义一个RouterProcessor,继承自AbstractProcessor,public
class
RouterProcessor
extends
AbstractProcessor
{
@Override
public
synchronized
void
init(ProcessingEnvironment
processingEnv)
{
super.init(processingEnv);
//
初始化相关环境信息
mFiler
=
processingEnv.getFiler();
elementUtil
=
processingEnv.getElementUtils();
typeUtil
=
processingEnv.getTypeUtils();
Log.setLogger(processingEnv.getMessager());
}
@Override
public
Set<String>
getSupportedAnnotationTypes()
{
Set<String>
supportAnnotationTypes
=
new
HashSet<>();
//
获取需要处理的注解类型,目前只处理Router注解
supportAnnotationTypes.add(Router.class.getCanonicalName());
return
supportAnnotationTypes;
}
@Override
public
boolean
process(Set<?
extends
TypeElement>
annotations,
RoundEnvironment
roundEnv)
{
//
收集与Router相关的所有类信息,解析并生成路由表
Set<?
extends
Element>
routeElements
=
roundEnv.getElementsAnnotatedWith(Router.class);
try
{
return
parseRoutes(routeElements);
}
catch
(Exception
e)
{
Log.e(e.getMessage(),
e);
return
false;
}
}
}上述的三个方法属于AbstractProcessor的方法,publicabstractbooleanprocess(Setannotations,RoundEnvironmentroundEnv)是抽象方法,需要子类实现。private
boolean
parseRoutes(Set<?
extends
Element>
routeElements)
throws
IOException
{
if
(null
==
routeElements
||
routeElements.size()
==
0)
{
return
false;
}
//
获取Activity类的类型,后面用于判断是否是其子类
TypeElement
typeActivity
=
elementUtil.getTypeElement(ACTIVITY);
//
获取路由Builder类的标准类名
ClassName
routeBuilderCn
=
ClassName.get(RouteBuilder.class);
//
构建Map<String,
Route>集合
String
routerConstClassName
=
RouterProvider.ROUTER_CONST_NAME;
TypeSpec.Builder
typeSpec
=
TypeSpec.classBuilder(routerConstClassName).addJavadoc(WARNING_TIPS).addModifiers(PUBLIC);
/**
*
Map<String,
Route>
*/
ParameterizedTypeName
inputMapTypeName
=
ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(Route.class));
ParameterSpec
groupParamSpec
=
ParameterSpec.builder(inputMapTypeName,
ROUTER_MAP_NAME).build();
MethodSpec.Builder
loadIntoMethodOfGroupBuilder
=
MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
//
将路由信息放入Map<String,
Route>集合中
for
(Element
element
:
routeElements)
{
TypeMirror
tm
=
element.asType();
Router
route
=
element.getAnnotation(Router.class);
//
获取当前Activity的标准类名
if
(typeUtil.isSubtype(tm,
typeActivity.asType()))
{
ClassName
activityCn
=
ClassName.get((TypeElement)
element);
String
key
=
"key"
+
element.getSimpleName().toString();
String
routeString
=
RouteBuilder.assembleRouteUri(route.scheme(),
route.host(),
route.value());
if
(null
==
routeString)
{
//String
keyValue
=
RouteBuilder.generateUriFromClazz(Activity.class);
loadIntoMethodOfGroupBuilder.addStatement("String
$N=
$T.generateUriFromClazz($T.class)",
key,
routeBuilderCn,
activityCn);
}
else
{
//String
keyValue
=
"("
+
route.value()
+
")|("
+
RouteBuilder.generateUriFromClazz(Activity.class)
+
")";
loadIntoMethodOfGroupBuilder.addStatement(
"String
$N=$S
+
$S
+
$S+$T.generateUriFromClazz($T.class)+$S",
key,
"(",
routeString,
")|(",
routeBuilderCn,
activityCn,
")");
}
/**
*
routerMap.put(url,
RouteBuilder.build(String
url,
int
priority,
Class<?>
destination));
*/
loadIntoMethodOfGroupBuilder.addStatement("$N.put($N,
$T.build($N,
$N,
$T.class))",
ROUTER_MAP_NAME,
key,
routeBuilderCn,
key,
String.valueOf(route.priority()),
activityCn);
typeSpec.addField(generateRouteConsts(element));
}
}
//
Generate
RouterConst.java
JavaFile.builder(RouterProvider.OUTPUT_DIRECTORY,
typeSpec.build()).build().writeTo(mFiler);
//
Generate
RouterGenerator
JavaFile.builder(RouterProvider.OUTPUT_DIRECTORY,
TypeSpec.classBuilder(RouterProvider.ROUTER_GENERATOR_NAME)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(RouterProvider.class))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()).build().writeTo(mFiler);
return
true;
}最终生成的路由表如下:/**
*
DO
NOT
EDIT
THIS
FILE!!!
IT
WAS
GENERATED
BY
KAOLA
PROCESSOR.
*/public
class
RouterGenerator
implements
RouterProvider
{
@Override
public
void
loadRouter(Map<String,
Route>
routerMap)
{
String
keyActivityDetailActivity="("
+
"(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/activity/spring/\\w+"
+
")|("+RouteBuilder.generateUriFromClazz(ActivityDetailActivity.class)+")";
routerMap.put(keyActivityDetailActivity,
RouteBuilder.build(keyActivityDetailActivity,
0,
ActivityDetailActivity.class));
String
keyLabelDetailActivity="("
+
"(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/album/tag/share\\.html"
+
")|("+RouteBuilder.generateUriFromClazz(LabelDetailActivity.class)+")";
routerMap.put(keyLabelDetailActivity,
RouteBuilder.build(keyLabelDetailActivity,
0,
LabelDetailActivity.class));
String
keyMyQuestionAndAnswerActivity="("
+
"(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/myQuestion.html"
+
")|("+RouteBuilder.generateUriFromClazz(MyQuestionAndAnswerActivity.class)+")";
routerMap.put(keyMyQuestionAndAnswerActivity,
RouteBuilder.build(keyMyQuestionAndAnswerActivity,
0,
MyQuestionAndAnswerActivity.class));
……
}其中,RouteBuilder.generateUriFromClazz(Class)的实现如下,目的是生成一条默认的与标准类名相关的native跳转规则。public
static
final
String
SCHEME_NATIVE
=
"native://";public
static
String
generateUriFromClazz(Class<?>
destination)
{
String
rawUri
=
SCHEME_NATIVE
+
destination.getCanonicalName();
return
rawUri.replaceAll("\\.",
"\\\\.");
}可以看到,路由集合的key是一条正则表达式,包括了url拦截规则以及自定义的包含标准类名的native跳转规则。例如,keyMyQuestionAndAnswerActivity最终生成的key是((https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/myQuestion.html)|(native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity)这样,调用者不仅可以通过默认的拦截规则(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/myQuestion.html)跳转到对应的页面,也可以通过(native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity)。这样的好处是模块间的跳转也可以使用,不需要依赖引用类。而native跳转会专门生成一个类RouterConst来记录,如下:/**
*
DO
NOT
EDIT
THIS
FILE!!!
IT
WAS
GENERATED
BY
KAOLA
PROCESSOR.
*/public
class
RouterConst
{
public
static
final
String
ROUTE_TO_ActivityDetailActivity
=
"native://com.kaola.modules.activity.ActivityDetailActivity";
public
static
final
String
ROUTE_TO_LabelDetailActivity
=
"native://com.kaola.modules.albums.label.LabelDetailActivity";
public
static
final
String
ROUTE_TO_MyQuestionAndAnswerActivity
=
"native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity";
public
static
final
String
ROUTE_TO_CertificatedNameActivity
=
"native://com.kaola.modules.auth.activity.CertificatedNameActivity";
public
static
final
String
ROUTE_TO_CPSCertificationActivity
=
"native://com.kaola.modules.auth.activity.CPSCertificationActivity";
public
static
final
String
ROUTE_TO_BrandDetailActivity
=
"native://com.kaola.modules.brands.branddetail.ui.BrandDetailActivity";
public
static
final
String
ROUTE_TO_CartContainerActivity
=
"native://com.kaola.modules.cart.CartContainerActivity";
public
static
final
String
ROUTE_TO_SingleCommentShowActivity
=
"native://ment.detail.SingleCommentShowActivity";
public
static
final
String
ROUTE_TO_CouponGoodsActivity
=
"native://com.kaola.modules.coupon.activity.CouponGoodsActivity";
public
static
final
String
ROUTE_TO_CustomerAssistantActivity
=
"native://com.kaola.modules.customer.CustomerAssistantActivity";
……
}初始化路由路由初始化在Application的过程中以同步的方式进行。通过获取RouterGenerator的类直接生成实例,并将路由信息保存在sRouterMap变量中。public
static
void
init()
{
try
{
sRouterMap
=
new
HashMap<>();
((RouterProvider)
(Class.forName(ROUTER_CLASS_NAME).getConstructor().newInstance())).loadRouter(sRouterMap);
}
catch
(Exception
e)
{
e.printStackTrace();
}
}页面路由给定一个url以及上下文环境,即可使用路由。调用方式如下:KaolaRouter.with(context).url(url).start();页面路由分为路由请求生成,路由查找以及路由结果执行这几个步骤。路由请求目前较为简单,仅是封装了一个RouterRequest接口public
interface
RouterRequest
{
Uri
getUriRequest();
}路由的查找过程相对复杂,除了遍历路由初始化以后导入内存的路由表,还需要判断各种各样的前置条件。具体的条件判断代码中有相关注释。@Overridepublic
RouterResponse
findResponse(RouterRequest
request)
{
if
(null
==
sRouterMap)
{
return
null;
//throw
new
IllegalStateException(
//
String.format("Router
has
not
been
initialized,
please
call
%s.init()
first.",
//
KaolaRouter.class.getSimpleName()));
}
if
(mRouterRequestWrapper.getDestinationClass()
!=
null)
{
RouterResponse
response
=
RouterResponseFactory.buildRouterResponse(null,
mRouterRequestWrapper);
reportFoundRequestCallback(request,
response);
return
response;
}
Uri
uri
=
request.getUriRequest();
String
requestUrl
=
uri.toString();
if
(!TextUtils.isEmpty(requestUrl))
{
for
(Map.Entry<String,
Route>
entry
:
sRouterMap.entrySet())
{
if
(RouterUtils.matchUrl(requestUrl,
entry.getKey()))
{
Route
routerModel
=
entry.getValue();
if
(null
!=
routerModel)
{
RouterResponse
response
=
RouterResponseFactory.buildRouterResponse(routerModel,
mRouterRequestWrapper);
reportFoundRequestCallback(request,
response);
return
response;
}
}
}
}
return
null;
}@Overridepublic
RouterResult
start()
{
//
判断Context引用是否还存在
WeakReference<Context>
objectWeakReference
=
mContextWeakReference;
if
(null
==
objectWeakReference)
{
reportRouterResultError(null,
null,
RouterError.ROUTER_CONTEXT_REFERENCE_NULL,
null);
return
getRouterResult(false,
mRouterRequestWrapper
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 合同风险防范与项目管理
- 2025年总公司分公司技术提升与资源共享协议
- 2025年版权保护协议书样本
- 2025年企业长期汽车租赁协议综述
- 2025年住房公积金住房贷款发放协议
- 2025年二手房产抵押购买合同模板
- 2025年个人股权策划秘密转让合同范本
- 2025年标准婚前协议书策划范本(离婚条件适用)
- 2025年仪器使用协议书范文
- 2025年战略策划与子公司设立协议书
- 英语学术论文写作引言
- 汽车检具知识培训讲义
- 小学音乐课程与教学论(小学教育专业)PPT完整全套教学课件
- 驾考三力测试模拟题含答案
- 江门至珠海高速公路某段工程地质勘察报告
- 脊柱外科进修汇报
- 初中科学项目化学习促进学生高阶思维发展-基于《自制简易吸尘器》的项目教学
- 2023年中小学生天文知识竞赛备考复习题库(360题)
- 口腔颌面外科学 计算机辅助外科
- 《老年服务伦理与礼仪》课程标准
- 解析华为管理干部任职资格管理制度
评论
0/150
提交评论