您好,登錄后才能下訂單哦!
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.1 路由框架
考拉路由框架主要分為三個模塊:路由收集、路由初始化以及頁面路由。路由收集階段,定義了基于Activity類的注解,通過Android Processing Tool(以下簡稱“APT”)收集路由信息并生成路由表類;路由初始化階段,根據生成的路由表信息注入路由字典;頁面路由階段,則通過路由字典查找路由信息,并根據查找結果定制不同的路由策略略。
2.2 路由設計思路
總的來說,考拉路由設計追求的是功能模塊的解耦,能夠實現基本路由功能,以及開發者使用上足夠簡單。考拉路由的前兩個階段對于路由使用者幾乎是無成本的,只需要在使用路由的頁面定義一個類注解@Router即可,頁面路由的使用也相當簡單,后面會詳細介紹。
功能設計
路由在一定程度上和網絡請求是類似的,可以分為請求、處理以及響應三個階段。這三個階段對使用者來說既可以是透明的,也可以在路由過程中進行攔截處理。考拉路由框架目前支持的功能有:
1.支持基本Activity的啟動,以及startActivityForResult回調;?
2.支持不同協議執行不同跳轉;(kaola://、http(s)://、native://等)? 3.支持多個SCHEME/HOST/PATH跳轉至同一個頁面;((pre.).kaola.com(.hk))?
4.支持路由的正則匹配;?
5.支持Activity啟動使用不同的Flag;?
6.支持路由的優先級配置;?
7.支持對路由的動態攔截、監聽以及降級;?
以上功能保證了考拉業務模塊間的解耦,也能夠滿足目前產品和運營的需求。
接口設計
一個好的模塊或框架,需要事先設計好接口,預留足夠的權限供調用者支配,才能滿足各種各樣的需求。考拉路由框架在設計過程中使用了常見的設計模式,如Builder模式、Factory模式、Wrapper模式等,并遵循了一些設計原則。(最近在看第二遍Effective Java,對以下原則深有體會,推薦看一下)
針對接口編程,而不是針對實現編程
這條規則排在最前面的原因是,針對接口編程,不管是對開發者還是對使用者,真的是百利而無一害。在路由版本迭代的過程中,底層對接口無論實現怎樣的修改,也不會影響到上層調用。對于業務來說,路由的使用是無感知的。
考拉路由框架在設計過程中并未完全遵循這條原則,下一個版本的迭代會盡量按照這條原則來實現。但在路由過程中的關鍵步驟都預留了接口,具體有:
RouterRequestCallback
public interface RouterRequestCallback { void onFound(RouterRequest request, RouterResponse response); boolean onLost(RouterRequest request); }
路由表中是否能夠匹配到路由信息的回調,如果能夠匹配,則回調onFound(),如果不能夠匹配,則返回onLost()。onLost()的結果由開發來定義,如果返回的結果是true,則認為開發者處理了這次路由不匹配的結果,最終返回RouterResult的結果是成功路由。
RouterHandler
public interface RouterHandler extends RouterStarter { RouterResponse findResponse(RouterRequest request); }
路由處理與啟動接口,根據給定的路由請求,查找路由信息,根據路由響應結果,分發給相應的啟動器執行后續頁面跳轉。這個接口的設計不太合理,功能上不完善,后續會重新設計這個接口,讓調用方有權限干預查找路由的過程。
RouterResultCallback
public 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()方法,這時候路由中斷。
ResponseInvoker
public interface ResponseInvoker { void invoke(Context context, Intent intent, Object... args); }
路由執行者。如果開發需要執行路由前進行一些全局操作,例如添加額外的信息傳入到下一個Activity,則可以自己實現這個接口。路由框架提供默認的實現:ActivityInvoker。開發也可以繼承ActivityInvoker,重寫invoke()方法,先實現自己的業務邏輯,再調用super.invoke()方法。
OnActivityResultListener
public 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()); } });
RouterResult
public interface RouterResult { boolean isSuccess(); RouterRequest getRouterRequest(); RouterResponse getRouterResponse(); }
告知路由的結果,路由結果可以被干預,例如RouterRequestCallback.onLost(),返回true的時候,路由也是成功的。這個接口不管路由的成功或失敗都會返回。
不隨意暴露不必要的API
“要區別設計良好的模塊與設計不好的模塊,最重要的因素在于,這個模塊對于外部的其他模塊而?言,是否隱藏其內部數據和其他實現細節。設計良好的模塊會隱藏所有的實現細節,把它的API與它的實現清晰地隔離開來。然后,模塊之間只通過它們的API進行通信,一個模塊不需要知道其他模塊的內部工作情況。這被稱為封裝(encapsulation)。”(摘自Effective Java, 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的方法,public abstract boolean process(Set annotations, RoundEnvironment roundEnv)是抽象方法,需要子類實現。
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://com.kaola.modules.comment.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, null); } Context context = objectWeakReference.get(); if (context == null) { reportRouterResultError(null, null, RouterError.ROUTER_CONTEXT_NULL, null); return getRouterResult(false, mRouterRequestWrapper, null); } // 判斷路由請求是否有效 if (!checkRequest(context)) { return getRouterResult(false, mRouterRequestWrapper, null); } // 遍歷查找路路由結果 RouterResponse response = findResponse(mRouterRequestWrapper); // 判斷路由結果,執行路由結果為空時的攔截 if (null == response) { boolean handledByCallback = reportLostRequestCallback(mRouterRequestWrapper); if (!handledByCallback) { reportRouterResultError(context, null, RouterError.ROUTER_RESPONSE_NULL, mRouterRequestWrapper.getRouterRequest()); } return getRouterResult(handledByCallback, mRouterRequestWrapper, null); } // 獲取路由結果執行的接口 ResponseInvoker responseInvoker = getResponseInvoker(context, response); if (responseInvoker == null) { return getRouterResult(false, mRouterRequestWrapper, response); } Intent intent; try { intent = RouterUtils.generateResponseIntent(context, response, mRouterRequestWrapper); } catch (Exception e) { reportRouterResultError(context, null, RouterError.ROUTER_GENERATE_INTENT_ERROR, e); return getRouterResult(false, mRouterRequestWrapper, response); } // 生成相應的Intent if (null == intent) { reportRouterResultError(context, null, RouterError.ROUTER_GENERATE_INTENT_NULL, response); return getRouterResult(false, mRouterRequestWrapper, response); } // 獲取路由結果回調接口,如果為空,則使用默認提供的實現 RouterResultCallback routerResultCallback = getRouterResultCallback(); // 由使用者處理 if (routerResultCallback.beforeRoute(context, intent)) { return getRouterResult(true, mRouterRequestWrapper, response); } try { responseInvoker.invoke(context, intent, mRouterRequestWrapper.getRequestCode(), mRouterRequestWrapper.getOnActivityResultListener()); routerResultCallback.doRoute(context, intent, null); return getRouterResult(true, mRouterRequestWrapper, response); } catch (Exception e) { reportRouterResultError(context, intent, RouterError.ROUTER_INVOKER_ERROR, e); return getRouterResult(false, mRouterRequestWrapper, response); } }
最終會調用ResponseInvoker.invoke()方法執行路由。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。