項目組件化過程當中使用了WMRouter,爲了更好的理解並使用。花了一週的時間研究了一下WMRouter(v1.2.0版本)。下面從四個方面說下本身的理解,但願能給你們提供幫助。html
路由框架,也就是路由的做用。路由是起什麼做用呢?就像送快遞,從小縣城到省會,再從省會發到北京分撥中心,而後再從北京分撥中心發到回龍觀,再從回龍觀發到具體的小區。路由框架解決的就是如何從A頁面跳轉到B頁面的問題,其會在A和B之間創建數個節點。經過這些節點依次進行轉發,最終達到目的地。java
Android原生已經支持AndroidManifest去管理App跳轉,爲何要有路由庫?android
WMRouter是一款Android路由框架,主要提供URI分發、ServiceLoader兩大功能(後面會看到,URI分發功能也是用ServiceLoader實現的)git
URI分發功能可用於跨module的頁面跳轉、動態下發URI連接的跳轉等場景,特色以下:github
WMRouter提供了ServiceLoader模塊,相似Java中的
java.util.ServiceLoader
,但功能更加完善。經過ServiceLoader能夠在一個App的多個模塊之間經過接口調用代碼,實現模塊解耦,便於實現組件化、模塊間通訊,以及和依賴注入相似的功能等。其特色以下:正則表達式
詳細使用參見WMRouter設計與使用文檔,這裏就大概說下整體的流程。設計模式
buildscript {
repositories {
jcenter()
}
dependencies {
// Android Gradle插件
classpath 'com.android.tools.build:gradle:3.2.1'
// 添加WMRouter插件
classpath "com.sankuai.waimai.router:plugin:1.x"
}
}
複製代碼
apply plugin: 'com.android.application'
// 應用WMRouter插件
apply plugin: 'WMRouter'
複製代碼
compile 'com.sankuai.waimai.router:router:1.x'
複製代碼
annotationProcessor 'com.sankuai.waimai.router:compiler:1.x'
複製代碼
# 保留ServiceLoaderInit類,須要反射調用
-keep class com.sankuai.waimai.router.generated.ServiceLoaderInit { *; }
# 避免註解在shrink階段就被移除,致使obfuscate階段註解失效、實現類仍然被混淆
-keep @interface com.sankuai.waimai.router.annotation.RouterService
複製代碼
在Application.onCreate中初始化:最簡單的方式初始化方式就兩行代碼。數組
// 建立RootHandler
DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);
// 初始化,必須在主線程調用
Router.init(rootHandler);
複製代碼
跳轉的目標Activity,添加註解@RouterUri瀏覽器
@RouterUri(path = "/test/schemehost", scheme = "test", host = "test.demo.com")
public class AdvancedDemoActivity extends BaseActivity {
...
}
複製代碼
發起跳轉有好幾種方式,經常使用的有如下三種。其實最經常使用的是方式三。緩存
// 方式1,直接傳context和URI
Router.startUri(context, "/account");
// 方式2,或構造一個UriRequest
Router.startUri(new UriRequest(context, "/account"));
// 方式3,使用DefaultUriRequest,最經常使用
new DefaultUriRequest(context, uri)//傳入context和目標uri
// startActivityForResult使用的RequestCode
.activityRequestCode(100)
// 設置跳轉來源,默認爲內部跳轉,還能夠是來自WebView、來自Push通知等。
// 目標Activity可經過UriSourceTools區分跳轉來源。
.from(UriSourceTools.FROM_INTERNAL)
// Intent加參數
.putIntentExtra("test-int", 1)
.putIntentExtra("test-string", "str")
// 設置Activity跳轉動畫
.overridePendingTransition(R.anim.enter_activity, R.anim.exit_activity)
// 監聽跳轉完成事件
.onComplete(new OnCompleteListener() {
@Override
public void onSuccess(@NonNull UriRequest request) {
ToastUtils.showToast(request.getContext(), "跳轉成功");
}
@Override
public void onError(@NonNull UriRequest request, int resultCode) {
}
})
// 這裏的start實際也是調用了Router.startUri方法
.start();
複製代碼
上面5步只是最基本的URI分發功能使用,SDK還提供了不少設置,方便在實際項目中使用。另外,在使用過程當中,還有一些比較容易遺漏的點。下面進行詳細說明。
注意:若是項目配置的Android Gradle插件版本比WMRouter依賴的版本低,默認會覆蓋爲高版本(可經過./gradlew buildEnvironment
命令查看classpath的依賴關係)。若是不但願被覆蓋,能夠嘗試把配置改爲:
classpath("com.sankuai.waimai.router:plugin:1.x") {
exclude group: 'com.android.tools.build'
}
複製代碼
若是使用了@RouterService註解和ServiceLoader加載實例的功能,會反射調用構造方法,應根據實際狀況配置Proguard,避免實現類中的構造方法被移除,示例以下。
# 使用了RouterService註解的實現類,須要避免Proguard把構造方法、方法等成員移除(shrink)或混淆(obfuscate),致使沒法反射調用。實現類的類名能夠混淆。
-keepclassmembers @com.sankuai.waimai.router.annotation.RouterService class * { *; }
複製代碼
// CustomFactory.java--自定義工廠
IFactoryService service4 = Router.getService(IFactoryService.class, "/factory", new IFactory() {
@NonNull
@Override
public <T> T create(@NonNull Class<T> clazz) throws Exception {
return clazz.getConstructor(String.class).newInstance("CreateByCustomFactory");
}
});
複製代碼
DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);
//設置全局跳轉完成的監聽,能夠在其中跳轉失敗時執行全局降級邏輯。
//在DefaultRootUriHandler中默認配置的GlobalOnCompleteListener會在跳轉失敗時彈Toast提示用戶
rootHandler.setGlobalOnCompleteListener();
// 自定義Logger
DefaultLogger logger = new DefaultLogger() {
@Override
protected void handleError(Throwable t) {
super.handleError(t);
// 此處上報Fatal級別的異常
}
};
// 設置Logger
Debugger.setLogger(logger);
// Log開關,建議測試環境下開啓,方便排查問題。
Debugger.setEnableLog(true);
// 調試開關,建議測試環境下開啓。調試模式下,嚴重問題直接拋異常,及時暴漏出來。
Debugger.setEnableDebug(true);
Router.init(rootHandler);
// 後臺線程懶加載
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void[] objects) {
Router.lazyInit();
return null;
}
}.execute();
複製代碼
/** 無效來源 */
public static final int FROM_INVALID = 0;
/** 外部跳轉 */
public static final int FROM_EXTERNAL = FROM_INVALID + 1;
/** 內部跳轉*/
public static final int FROM_INTERNAL = FROM_EXTERNAL + 1;
/** 從WebView跳轉 */
public static final int FROM_WEBVIEW = FROM_INTERNAL + 1;
/** 從Push跳轉 */
public static final int FROM_PUSH = FROM_WEBVIEW + 1;
複製代碼
最經常使用,基本只用這個註解就能夠知足URI分發需求。根據URI的scheme+host,尋找並分發給對應的PathHandler,以後PathHandler再根據path匹配RouterUri註解配置的節點。可用於Activity或UriHandler的非抽象子類(Activity也會被轉化成UriHandler,在Activity中能夠經過Intent.getData()
獲取到URI)
參數以下:
說明:
WMRouter支持多scheme+host+path的跳轉,也支持只有path的跳轉。若是RouterUri中配置了scheme、host、path,則跳轉時應使用scheme+host+path的完整路徑;若是RouterUri中只配置了path,則跳轉應直接使用path。
因爲多數場景下每每只須要一個固定的scheme+host,不想在每一個RouterUri註解上都寫一遍scheme、host,這種場景能夠在初始化時用new DefaultRootUriHandler("scheme", "host")
指定默認的scheme、host,RouterUri沒有配置的字段會使用這個默認值。
一、用戶帳戶頁面只配置path;跳轉前要先登陸,所以添加了一個LoginInterceptor。
@RouterUri(path = "/account", interceptors = LoginInterceptor.class)
public class UserAccountActivity extends Activity {
}
複製代碼
Router.startUri(context, "/account");
複製代碼
二、一個頁面配置多個path。
@RouterUri(scheme = "demo_scheme", host = "demo_host", path = {"/path1", "/path2"})
public class TestActivity extends Activity {
}
複製代碼
Router.startUri(context, "demo_scheme://demo_host/path1");
Router.startUri(context, "demo_scheme://demo_host/path2");
複製代碼
三、根據後臺下發的ABTest策略,同一個連接跳轉不一樣的Activity。其中AbsActivityHandler是WMRouter提供的用於跳轉Activity的UriHandler通用基類。
@RouterUri(path = "/home")
public class HomeABTestHandler extends AbsActivityHandler {
@NonNull
@Override
protected Intent createIntent(@NonNull UriRequest request) {
if (FakeABTestService.getHomeABStrategy().equals("A")) {
return new Intent(request.getContext(), HomeActivityA.class);
} else {
return new Intent(request.getContext(), HomeActivityB.class);
}
}
}
複製代碼
Router.startUri(context, "/home");
複製代碼
RouterRegex註解也能夠用於Activity和UriHandler,經過正則進行URI匹配。
參數以下:
一、對於指定域名的http(s)連接,使用特定的WebViewActivity打開。
@RouterRegex(regex = "http(s)?://(.*\\.)?(meituan|sankuai|dianping)\\.(com|info|cn).*", priority = 2)
public class WebViewActivity extends BaseActivity {
}
複製代碼
二、對於其餘http(s)連接,使用系統瀏覽器打開。
@RouterRegex(regex = "http(s)?://.*", priority = 1)
public class SystemBrowserHandler extends UriHandler {
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
return true;
}
@Override
protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
try {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(request.getUri());
request.getContext().startActivity(intent);
callback.onComplete(UriResult.CODE_SUCCESS);
} catch (Exception e) {
callback.onComplete(UriResult.CODE_ERROR);
}
}
}
複製代碼
RouterPage註解用於指定內部頁面跳轉,和RouterUri註解相比,RouterPage註解對應的scheme和host爲固定的wm_router://page
,不可配置,exported爲false也不可配置。感受這個是因爲歷史緣由存在的一個註解。本質和RouterUri註解是同樣的。咱們本身的項目不會用到這個。因此不詳細介紹了。有興趣的小夥伴能夠自行查看WMRouter設計與使用文檔
/** 跳轉到系統自帶瀏覽器 */
@RouterRegex(regex = DemoConstant.HTTP_URL_REGEX)
public class SystemBrowserHandler extends UriHandler {
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
return true;
}
@Override
protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
try {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(request.getUri());
request.getContext().startActivity(intent);
callback.onComplete(UriResult.CODE_SUCCESS);
} catch (Exception e) {
callback.onComplete(UriResult.CODE_ERROR);
}
}
}
複製代碼
// 建立RootHandler
DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);
rootHandler.addChildHandler(new UriHandler() {
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
return false;
}
@Override
protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
}
});
// 初始化,必須在主線程調用
Router.init(rootHandler);
複製代碼
UriInterceptor爲攔截器,不作最終的URI跳轉操做,但能夠在最終的跳轉前進行各類同步/異步操做,常見操做舉例以下:
每一個UriHandler均可以添加若干UriInterceptor。在UriHandler基類中,handle()方法先調用抽象方法shouldHandle()
判斷是否要處理UriRequest,若是須要處理,則逐個執行Interceptor,最後再調用handleInternal()
方法進行跳轉操做。 舉例來講,跳轉某些頁面須要先登陸,能夠實現一個LoginInterceptor以下。
public class LoginInterceptor implements UriInterceptor {
@Override
public void intercept(@NonNull UriRequest request, @NonNull final UriCallback callback) {
final FakeAccountService accountService = FakeAccountService.getInstance();
if (accountService.isLogin()) {
// 已經登陸,不需處理,繼續跳轉流程
callback.onNext();
} else {
// 沒登陸,提示登陸並啓動登陸頁
Toast.makeText(request.getContext(), "請先登陸~", Toast.LENGTH_SHORT).show();
accountService.registerObserver(new FakeAccountService.Observer() {
@Override
public void onLoginSuccess() {
accountService.unregisterObserver(this);
// 登陸成功,繼續跳轉
callback.onNext();
}
@Override
public void onLoginFailure() {
accountService.unregisterObserver(this);
// 登陸失敗,終止流程,返回錯誤ResultCode
callback.onComplete(CustomUriResult.CODE_LOGIN_FAILURE);
}
});
// 啓動登陸頁
startActivity(request.getContext(), LoginActivity.class);
}
}
}
複製代碼
RootUriHandler
的startUri
開始分發的,因此,若是要添加全局的攔截器,就能夠經過給RootUriHandler
的子類實例對象,好比DefaultRootUriHandler
對象添加攔截器的方式實現。根據實際狀況,能夠自定義具備各類功能的UriHandler和UriInterceptor,前面已經提到,再也不贅述。通常使用DefaultRootHandler和DefaultUriRequest,以及少許自定義的UriHandler已經能夠知足絕大多數需求。若是有更復雜的場景須要,WMRouter中的核心組件能夠經過繼承、組合等方式實現更靈活的定製。例如自定義RootUriHandler示例以下:
// 自定義RootUriHandler
public class CustomRootUriHandler extends RootUriHandler {
// ...
public CustomRootUriHandler() {
// 添加Uri註解支持
addHandler(new UriAnnotationHandler());
// 添加一個自定義的HttpHandler
addHandler(new CustomHttpHandler());
}
}
// 自定義UriRequest
public class CustomUriRequest extends UriRequest {
// ...
public CustomUriRequest setCustomProperties(String s) {
putField("custom_properties", s);
return this;
}
}
// 初始化
Router.init(new CustomRootUriHandler());
// 啓動Uri
CustomUriRequest request = new CustomUriRequest(mContext, url)
.setCustomProperties("xxx");
Router.startUri(request);
複製代碼
public DefaultRootUriHandler(Context context, @Nullable String defaultScheme, @Nullable String defaultHost) {
super(context);
mPageAnnotationHandler = createPageAnnotationHandler();
mUriAnnotationHandler = createUriAnnotationHandler(defaultScheme, defaultHost);
mRegexAnnotationHandler = createRegexAnnotationHandler();
// 按優先級排序,數字越大越先執行
// 處理RouterPage註解定義的內部頁面跳轉,若是註解沒定義,直接結束分發
addChildHandler(mPageAnnotationHandler, 300);
// 處理RouterUri註解定義的URI跳轉,若是註解沒定義,繼續分發到後面的Handler
addChildHandler(mUriAnnotationHandler, 200);
// 處理RouterRegex註解定義的正則匹配
addChildHandler(mRegexAnnotationHandler, 100);
// 添加其餘用戶自定義Handler...
// 都沒有處理,則嘗試使用默認的StartUriHandler直接啓動Uri
addChildHandler(new StartUriHandler(), -100);
// 全局OnCompleteListener,用於輸出跳轉失敗提示信息
setGlobalOnCompleteListener(DefaultOnCompleteListener.INSTANCE);
}
複製代碼
經過查看源碼發現,全部Activity類型的UriHandler(就是經過在Activity類名上面添加註解,從而經過UriTargetTools的toHandler方法,生成的UriHandler實例),路由分發的最後一步(跳轉該activity),都是經過ActivityLauncher接口的startActivity方法執行的。而SDK提供了ActivityLauncher接口的默認實現類DefaultActivityLauncher。咱們能夠在這裏hook一些核心的方法,執行本身的跳轉邏輯。好比下面的例子,跳轉到Activity以前,判斷intent中的context是否是Activity類型的,若是不是,那麼加上Intent.FLAG_ACTIVITY_NEW_TASK
。
public class XinActivityLauncher extends DefaultActivityLauncher {
//...省略代碼
@Override
protected int startActivityByDefault(UriRequest request, Context context, Intent intent, Integer requestCode, boolean internal) {
try {
Bundle options = (Bundle)request.getField(Bundle.class, FIELD_START_ACTIVITY_OPTIONS);
if (requestCode != null && context instanceof Activity) {
ActivityCompat.startActivityForResult((Activity)context, intent, requestCode, options);
} else {
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
ActivityCompat.startActivity(context, intent, options);
}
this.doAnimation(request);
if (internal) {
request.putField(FIELD_STARTED_ACTIVITY, 1);
Debugger.i(" internal activity started, request = %s", new Object[]{request});
} else {
request.putField(FIELD_STARTED_ACTIVITY, 2);
Debugger.i(" external activity started, request = %s", new Object[]{request});
}
return 200;
} catch (ActivityNotFoundException var7) {
Debugger.w(var7);
return 404;
} catch (SecurityException var8) {
Debugger.w(var8);
return 403;
}
}
//...省略代碼
}
複製代碼
簡單來講,ServiceLoader的核心做用就是:根據接口(或抽象類)名,找到接口(或抽象類)的具體實例,若是一個接口對應多個實例,那麼再根據不一樣實例的key找到具體的接口實例。
在實現組件化的項目中,極可能多個業務module之間是沒有依賴關係的可是確實有可能不一樣的業務module之間仍是有業務邏輯的耦合的。好比:
ServiceLoader模塊使用主要分三步:
經過RouterService註解聲明實現類所實現的接口(或繼承的父類,例如Activity、Fragment、Object等,後文再也不重複說明),一個接口能夠有多個實現類,一個類也能夠同時實現多個接口。RouterService註解的參數以下:
示例以下:
public interface IService {
}
@RouterService(interfaces = IService.class, key = 'key1')
public static class ServiceImpl1 implements IService {
}
@RouterService(interfaces = IService.class, key = 'key2', singleton = true)
public static class ServiceImpl2 implements IService {
}
複製代碼
能夠直接獲取實現類的Class,例如獲取Activity的Class進行頁面跳轉。
Class<IService> clazz = Router.getServiceClass(IService.class, "key1");
複製代碼
List<Class<IService>> classes = Router.getAllServiceClasses(IService.class);
複製代碼
ServiceLoader更常見的使用場景,是獲取實現類的實例而不是Class。實現類的構造在ServiceLoader中最終由Factory實現,構造失敗會返回null或空數組。
// 使用無參構造函數
IService service = Router.getService(IService.class, "key1");
List<IService> list = Router.getAllServices(IService.class);
複製代碼
// 使用Context參數構造
IService service = Router.getService(IService.class, context);
List<IService> list = Router.getAllServices(IService.class, context);
複製代碼
對於實現類有特殊構造函數的狀況,能夠經過Factory自行從class獲取構造方法進行構造,示例以下:
// 使用自定義Factory
IFactory factory = new IFactory() {
public Object create(Class clazz) {
return clazz.getConstructor().newInstance();
}
};
IService service = Router.getService(IService.class, factory);
List<IService> list = Router.getAllServices(IService.class, factory);
複製代碼
在聲明實現類時,能夠在類中定義一個返回值類型爲該實現類且無參數的靜態方法,並使用RouterProvider註解標註。當調用Router獲取實例時,若是沒有指定Factory,則優先調用Provider方法獲取實例,找不到Provider再使用無參數構造。使用示例以下:
@RouterService(interfaces = IService.class, key = 'key', singleton = true)
public static class ServiceImpl implements IService {
public static final ServiceImpl INSTANCE = new ServiceImpl();
// 使用註解聲明該方法是一個Provider
@RouterProvider
public static ServiceImpl provideInstance() {
return INSTANCE;
}
}
// 調用時不傳Factory,優先找Provider,找不到再使用無參數構造
IService service = Router.getService(IService.class, "key");
List<IService> list = Router.getAllServices(IService.class);
複製代碼
註解聲明爲singleton的單例實現類,在調用getService()/getAllServices()
方式獲取實例時,實例會由單例緩存池管理,WMRouter中不會重複構造,且線程安全。
注意:當經過ServiceLoader獲取Class、直接調用等其餘方式使用實現類時,應避免重複建立對象,不然會致使單例失效。能夠結合Provider確保實例不會重複建立。
WMRouter的核心原理大概就是,經過註解標註路由信息,在編譯期動態掃描路由信息,生成加載路由表信息的java類。並利用 gradle transform和asm生成加載所有路由信息的class文件。在app運行時,路由框架反射調用這個class文件,從而完成了路由表的裝載。
ServiceInit_*
類,UriAnnotationInit_*
類等輔助註冊代碼。首先,編譯的時候,根據@RouterUri
,@RouterRegex
,@RouterPage
,@RouterService
註解,生成輔助代碼。詳細的文件見下圖。具體的生成原理參見路由節點的動態生成
這裏須要注意,@RouterUri
,@RouterRegex
,@RouterPage
這三個註解,會同時生成兩個文件。以@RouterUri
爲例進行說明:
UriAnnotationInit_**
類,其init
方法中的每一行,都是本module中使用@RouterUri
註解的類的註冊到UriAnnotationHandler
的執行代碼。所謂註冊,其實質就是創建映射關係。須要注意UriAnnotationHandler這個註冊過程,一旦執行,就會開啓整個UriAnnotationHandler
這個分支的全部註冊過程。ServiceInit_**
類,其init
方法中,經過ServiceLoader.put()
方法,創建了接口抽象類(IUriAnnotationInit.class
),接口實現類(com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d.class
),接口實現類的key(com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d
),這三者之間的映射關係。還須要注意。編譯過程只是生成了可以進行註冊的代碼。可是代碼並無執行。只有等到開啓提早加載(執行Router.lazyInit()
),或者開啓跳轉(Router#startUri(com.sankuai.waimai.router.core.UriRequest)
)的時候,纔開始註冊。
public class UriAnnotationInit_72565413b8384a4bebb02d352762d60d implements IUriAnnotationInit {
public void init(UriAnnotationHandler handler) {
handler.register("", "", "/advanced_demo", "com.sankuai.waimai.router.demo.advanced.AdvancedDemoActivity", false);
}
}
複製代碼
public class ServiceInit_eb71854fbd69455ef4e0aa026c2e9881 {
public static void init() {
ServiceLoader.put(IUriAnnotationInit.class, "com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d", com.sankuai.waimai.router.generated.UriAnnotationInit_72565413b8384a4bebb02d352762d60d.class, false);
}
}
複製代碼
ServiceLoaderInit
類ServiceLoaderInit
類,只有一個init
方法。其中包含了整個項目全部module的ServiceInit_**
輔助類的init
執行代碼。ServiceInit_**
輔助類是上面經過註解生成的。ServiceLoaderInit
類的init
方法。爲何呢?上面咱們說到,註解只是生成輔助代碼,可是並無執行。只有執行了ServiceLoaderInit
類的init
方法,纔會真正的執行路由表註冊代碼,創建真正的映射關係。另外須要注意,改方法執行後,只會創建第一層關係。只有等到開啓提早加載(執行Router.lazyInit()
),或者開啓跳轉(Router#startUri(com.sankuai.waimai.router.core.UriRequest)
)的時候,纔開始執行UriAnnotationHandler
等子節點分支的註冊代碼(initAnnotationConfig()
),創建映射關係。public class ServiceLoaderInit {
public static void init() {
ServiceInit_aea7f96d0419b507d9b0ef471913b2f5.init();
ServiceInit_f3649d9f5ff15a62b844e64ca8434259.init();
ServiceInit_eb71854fbd69455ef4e0aa026c2e9881.init();
ServiceInit_b57118238b4f9112ddd862e55789c834.init();
ServiceInit_f1e07218f6691f962a9f674eb5b4b8bd.init();
ServiceInit_e694d982fb5d7a3a8c6b7085829e74a6.init();
ServiceInit_ee5f6404731417fe1433da40fd3c9708.init();
ServiceInit_9482ef47a8cf887ff1dc4bf705d5fc0a.init();
ServiceInit_36ed390bf4b81a8381d45028b37cc645.init();
}
}
複製代碼
Router#startUri(com.sankuai.waimai.router.core.UriRequest)
開啓跳轉getRootHandler().startUri(request)
;其中getRootHandler()方法獲取RootUriHandler的實例,默認是DefaultRootUriHandler對象。RootUriHandler#startUri
方法。UriHandler#handle
方法開始分發。/** * 處理URI。一般不須要覆寫本方法。 * * @param request URI跳轉請求 * @param callback 處理完成後的回調 */
public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
if (shouldHandle(request)) {
Debugger.i("%s: handle request %s", this, request);
if (mInterceptor != null && !request.isSkipInterceptors()) {
mInterceptor.intercept(request, new UriCallback() {
@Override
public void onNext() {
handleInternal(request, callback);
}
@Override
public void onComplete(int result) {
callback.onComplete(result);
}
});
} else {
handleInternal(request, callback);
}
} else {
Debugger.i("%s: ignore request %s", this, request);
callback.onNext();
}
}
複製代碼
UriHandler#handle
方法中,首先調用UriHandler#shouldHandle
方法,判斷是否應該處理。這裏要注意,UriHandler#handle
在各個UriHanlder的子類中都會調用(責任鏈模式)。UriHandler
的具體實例是什麼
UriHandler#handle
方法,執行第一句代碼shouldHandle。UriHandler#handle
方法的第一句shouldHandle,執行的是ChainedHandler的shouldHandle方法。該方法會判斷mHandlers是否爲空,在DefaultRootUriHandler的構造函數裏面,已經添加了UriAnnotationHandler,PageAnnotationHandler,RegexAnnotationHandler,StartUriHandler等UriHandler對象。因此,shouldHandle返回true,繼續向下執行。public DefaultRootUriHandler(Context context, @Nullable String defaultScheme, @Nullable String defaultHost) {
super(context);
mPageAnnotationHandler = createPageAnnotationHandler();
mUriAnnotationHandler = createUriAnnotationHandler(defaultScheme, defaultHost);
mRegexAnnotationHandler = createRegexAnnotationHandler();
// 按優先級排序,數字越大越先執行
// 處理RouterPage註解定義的內部頁面跳轉,若是註解沒定義,直接結束分發
addChildHandler(mPageAnnotationHandler, 300);
// 處理RouterUri註解定義的URI跳轉,若是註解沒定義,繼續分發到後面的Handler
addChildHandler(mUriAnnotationHandler, 200);
// 處理RouterRegex註解定義的正則匹配
addChildHandler(mRegexAnnotationHandler, 100);
// 添加其餘用戶自定義Handler...
// 都沒有處理,則嘗試使用默認的StartUriHandler直接啓動Uri
addChildHandler(new StartUriHandler(), -100);
// 全局OnCompleteListener,用於輸出跳轉失敗提示信息
setGlobalOnCompleteListener(DefaultOnCompleteListener.INSTANCE);
}
@Override
public void handle(@NonNull UriRequest request, @NonNull UriCallback callback) {
mInitHelper.ensureInit();
super.handle(request, callback);
}
複製代碼
ChainedHandler{
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
return !mHandlers.isEmpty();
}
@Override
protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
next(mHandlers.iterator(), request, callback);
}
private void next(@NonNull final Iterator<UriHandler> iterator, @NonNull final UriRequest request, @NonNull final UriCallback callback) {
if (iterator.hasNext()) {
UriHandler t = iterator.next();
t.handle(request, new UriCallback() {
@Override
public void onNext() {
next(iterator, request, callback);
}
@Override
public void onComplete(int resultCode) {
callback.onComplete(resultCode);
}
});
} else {
callback.onNext();
}
}
}
複製代碼
public class UriAnnotationHandler extends UriHandler {
/** * 經過scheme+host找對應的PathHandler,找到了纔會處理 */
private PathHandler getChild(@NonNull UriRequest request) {
return mMap.get(request.schemeHost());
}
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
return getChild(request) != null;
}
@Override
protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
PathHandler pathHandler = getChild(request);
if (pathHandler != null) {
pathHandler.handle(request, callback);
} else {
// 沒找到的繼續分發
callback.onNext();
}
}
}
複製代碼
public class DefaultAnnotationLoader implements AnnotationLoader {
public static final AnnotationLoader INSTANCE = new DefaultAnnotationLoader();
@Override
public <T extends UriHandler> void load(T handler, Class<? extends AnnotationInit<T>> initClass) {
List<? extends AnnotationInit<T>> services = Router.getAllServices(initClass);
for (AnnotationInit<T> service : services) {
service.init(handler);
}
}
}
複製代碼
public class UriAnnotationInit_179aab35b2125f96c7b066a0a2eccf82 implements IUriAnnotationInit {
public void init(UriAnnotationHandler handler) {
handler.register("", "", "/service_loader", "com.sankuai.waimai.router.demo.lib2.advanced.ServiceLoaderActivity", false);
handler.register("", "", "/lib2", "com.sankuai.waimai.router.demo.lib2.basic.DemoLibActivity2", false);
}
}
複製代碼
UriHandler就是咱們所說的節點。UriHandler用於處理URI跳轉請求,能夠嵌套從而逐層分發和處理請求。UriHandler是異步結構,接收到UriRequest後處理(例如跳轉Activity等),若是處理完成,則調用
callback.onComplete()
並傳入ResultCode;若是沒有處理,則調用callback.onNext()
繼續分發。
UriHandler及其實現類的類圖整理以下(簡單起見,省略了部分實現):
UriHandler
是抽象類,其核心在於handle()
方法,另外,shouldHandle()
和handleInternal()
方法是抽象方法,由子類去實現。shouldHandle()
方法判斷是否應該在當前UriHandler執行分發。handleInternal()
是當前UriHandler執行分發的具體邏輯。RootUriHandler
的startUri()
方法,是全部分發跳轉的入口。DefaultRootUriHandler
是RootUriHandler
的默認實現,其構造函數中添加了PageAnnotationHandler
,UriAnnotationHandler
,RegexAnnotationHandler
,StartUriHandler
的實例。而後分發跳轉請求的時候,依次交給這四個UriHandler處理,若是某個UriHandler不可以處理該請求,則交給下一個執行。若是可以處理該請求,則交給該UriHandler的子節點繼續分發。好比,若是UriAnnotationHandler可以處理該請求,會交給其子節點PathHandler處理,而後PathHandler交給ActivityHandler處理。PageAnnotationHandler
,UriAnnotationHandler
,RegexAnnotationHandler
這三個子類複寫了UriHandler
的handle()
方法。其複寫的目的是爲了在真正開始分發以前,調用mInitHelper.ensureInit()
,確保該分支的路由表已經生成。PageAnnotationHandler
寫死了SCHEME和HOST,因此只處理全部格式爲 wm_router://page/*
的URI,根據path匹配。UriAnnotationHandler
分發到PathHandler
,PathHandler
分發到ActivityClassNameHandler
或ActivityHandler
是最經常使用的分發路徑。PageAnnotationHandler
,UriAnnotationHandler
,RegexAnnotationHandler
及其子節點,都沒有處理某個路由請求(處理的意思是:執行了UriCallback的onComplete回調),則交給StartUriHandler
處理。StartUriHandler
會從路由請求UriRequest中獲取uri等參數,直接經過intent跳轉。UriRequest中包含Context、URI和Fields,其中Fields爲HashMap<String, Object>,能夠經過Key存聽任意數據。簡單起見,UriRequest類同時承擔了Response的功能,跳轉請求的結果,也會被保存到Fields中。
每次URI跳轉請求會有一個ResultCode(相似HTTP請求的ResponseCode),表示跳轉結果,也存放在Fields中。常見Code以下,用戶也能夠自定義Code,爲了不衝突,自定義Code應使用負數值。
總結來講,UriRequest用於實現一次URI跳轉中全部組件之間的通訊功能。SDK默認提供了DefaultUriRequest,通常用其就能夠完成平常需求。
public class UriAnnotationInit_179aab35b2125f96c7b066a0a2eccf82 implements IUriAnnotationInit {
public void init(UriAnnotationHandler handler) {
handler.register("", "", "/service_loader", "com.sankuai.waimai.router.demo.lib2.advanced.ServiceLoaderActivity", false);
handler.register("", "", "/lib2", "com.sankuai.waimai.router.demo.lib2.basic.DemoLibActivity2", false);
}
}
複製代碼
/** * 使用ServiceLoader加載註解配置 * * Created by jzj on 2018/4/28. */
public class DefaultAnnotationLoader implements AnnotationLoader {
public static final AnnotationLoader INSTANCE = new DefaultAnnotationLoader();
@Override
public <T extends UriHandler> void load(T handler, Class<? extends AnnotationInit<T>> initClass) {
List<? extends AnnotationInit<T>> services = Router.getAllServices(initClass);
for (AnnotationInit<T> service : services) {
service.init(handler);
}
}
}
複製代碼
上面說過,ServiceLoader的原理是首先經過接口名獲取ServiceLoader實例,而後再從ServiceLoader實例中,根據key找到對應的接口實現類。那麼具體過程呢?
put
方法(見下圖),參數有:接口的Class對象,接口實現類中的key,接口實現類的Class對象,以及接口實現類是否要求單例。在put方法中,調用SERVICES.put(interfaceClass, loader)
,存入接口Class對象和ServiceLoader實例的關係。而後調用loader.putImpl(key, implementClass, singleton)
。putImpl
方法中,調用mMap.put(key, new ServiceImpl(key, implementClass, singleton))
,在ServiceLoader實例中存入key和接口實例class對象的關係。注意這裏建立了一個ServiceImpl對象。那麼ServiceImpl是幹什麼的呢?
getService
等方法獲取接口的實例。該方法就一行代碼ServiceLoader.load(clazz).get(key)
。load
方法中,首先調用sInitHelper.ensureInit()
。經過反射,調用com.sankuai.waimai.router.generated.ServiceLoaderInit
類的init
方法(ServiceLoaderInit
類是經過gradle插件生成的)。在init
方法中調用各個ServiceInit_36ed390bf4b81a8381d45028b37cc645
的init
方法(見下圖)。那麼ServiceLoaderInit類中的init方法中的這些ServiceInit_36ed390bf4b81a8381d45028b37cc645
類是什麼呢?這些類中的init方法裏面又是什麼呢?load
方法,經過SERVICES.get(interfaceClass)
獲取接口對應的ServiceLoader實例。public class ServiceLoader<I> {
//...省略部分代碼
//保存了接口類名和其對應的ServiceLoader實例的對應關係。
private static final Map<Class, ServiceLoader> SERVICES = new HashMap<>();
//保存了key和接口的實現類的對應關係。
private HashMap<String, ServiceImpl> mMap = new HashMap<>();
private static final LazyInitHelper sInitHelper = new LazyInitHelper("ServiceLoader") {
@Override
protected void doInit() {
try {
// 反射調用Init類,避免引用的類過多,致使main dex capacity exceeded問題
Class.forName(Const.SERVICE_LOADER_INIT)
.getMethod(Const.INIT_METHOD)
.invoke(null);
Debugger.i("[ServiceLoader] init class invoked");
} catch (Exception e) {
Debugger.fatal(e);
}
}
};
/** * 提供給InitClass使用的初始化接口 * * @param interfaceClass 接口類 * @param implementClass 實現類 */
public static void put(Class interfaceClass, String key, Class implementClass, boolean singleton) {
ServiceLoader loader = SERVICES.get(interfaceClass);
if (loader == null) {
loader = new ServiceLoader(interfaceClass);
SERVICES.put(interfaceClass, loader);
}
loader.putImpl(key, implementClass, singleton);
}
private void putImpl(String key, Class implementClass, boolean singleton) {
if (key != null && implementClass != null) {
mMap.put(key, new ServiceImpl(key, implementClass, singleton));
}
}
/** * 根據接口獲取 {@link ServiceLoader} */
@SuppressWarnings("unchecked")
public static <T> ServiceLoader<T> load(Class<T> interfaceClass) {
sInitHelper.ensureInit();
if (interfaceClass == null) {
Debugger.fatal(new NullPointerException("ServiceLoader.load的class參數不該爲空"));
return EmptyServiceLoader.INSTANCE;
}
ServiceLoader service = SERVICES.get(interfaceClass);
if (service == null) {
synchronized (SERVICES) {
service = SERVICES.get(interfaceClass);
if (service == null) {
service = new ServiceLoader(interfaceClass);
SERVICES.put(interfaceClass, service);
}
}
}
return service;
}
/** * 建立指定key的實現類實例,使用 {@link RouterProvider} 方法或無參數構造。對於聲明瞭singleton的實現類,不會重複建立實例。 * * @return 找不到或獲取、構造失敗,則返回null */
public static <I, T extends I> T getService(Class<I> clazz, String key) {
return ServiceLoader.load(clazz).get(key);
}
/** * 建立指定key的實現類實例,使用 {@link RouterProvider} 方法或無參數構造。對於聲明瞭singleton的實現類,不會重複建立實例。 * * @return 可能返回null */
public <T extends I> T get(String key) {
return createInstance(mMap.get(key), null);
}
}
複製代碼
外觀模式提供一個統一的接口,用來訪問子系統中的一羣接口,外觀定義了一個高層接口,讓子系統更容易使用。
com.sankuai.waimai.router.Router
這個類就使用了外觀模式。其
lazyInit()
,
startUri(UriRequest request)
,
getService(Class<I> clazz, String key)
等方法,都是經過調用SDK內部的其餘子系統提供的功能實現的。
public class Router {
/** * 此初始化方法的調用不是必須的。 * 使用時會按需初始化;但也能夠提早調用並初始化,使用時會等待初始化完成。 * 本方法線程安全。 */
public static void lazyInit() {
ServiceLoader.lazyInit();
getRootHandler().lazyInit();
}
public static void startUri(UriRequest request) {
getRootHandler().startUri(request);
}
/** * 建立指定key的實現類實例,使用 {@link RouterProvider} 方法或無參數構造。對於聲明瞭singleton的實現類,不會重複建立實例。 * @return 找不到或獲取、構造失敗,則返回null */
public static <I, T extends I> T getService(Class<I> clazz, String key) {
return ServiceLoader.load(clazz).get(key);
}
...
}
複製代碼
單例模式常見的有 七種寫法,WMRouter用到的有如下幾種:單例模式:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點
public class DefaultActivityLauncher implements ActivityLauncher {
public static final DefaultActivityLauncher INSTANCE = new DefaultActivityLauncher();
}
複製代碼
LazyInitHelper的performInit()方法,用到了雙重校驗鎖的思想。保證只初始化一遍。
public abstract class LazyInitHelper {
private void performInit() {
if (!mHasInit) {
synchronized (this) {
if (!mHasInit) {
mHasInit = true;
long ts = 0;
final boolean enableLog = Debugger.isEnableLog();
if (enableLog) {
ts = SystemClock.uptimeMillis();
}
try {
doInit();
} catch (Throwable t) {
Debugger.fatal(t);
}
if (enableLog) {
Debugger.i("%s init cost %s ms", mTag,
SystemClock.uptimeMillis() - ts);
}
}
}
}
}
}
複製代碼
/** * 單例緩存 * * Created by jzj on 2018/3/29. */
public class SingletonPool {
private static final Map<Class, Object> CACHE = new HashMap<>();
@SuppressWarnings("unchecked")
public static <I, T extends I> T get(Class<I> clazz, IFactory factory) throws Exception {
if (clazz == null) {
return null;
}
if (factory == null) {
factory = RouterComponents.getDefaultFactory();
}
Object instance = getInstance(clazz, factory);
Debugger.i("[SingletonPool] get instance of class = %s, result = %s", clazz, instance);
return (T) instance;
}
@NonNull
private static Object getInstance(@NonNull Class clazz, @NonNull IFactory factory) throws Exception {
Object t = CACHE.get(clazz);
if (t != null) {
return t;
} else {
synchronized (CACHE) {
t = CACHE.get(clazz);
if (t == null) {
Debugger.i("[SingletonPool] >>> create instance: %s", clazz);
t = factory.create(clazz);
//noinspection ConstantConditions
if (t != null) {
CACHE.put(clazz, t);
}
}
}
return t;
}
}
}
複製代碼
WMRouter中,IFactory是抽象工廠。工廠方法是create()方法,生產的產品是泛型T。ContextFactory是具體工廠,其create方法,經過獲取包含context的構造函數,建立T的實例。EmptyArgsFactory是另一個具體工廠,其create方法,經過clazz.newInstance()無參構造函數建立實例。工廠方法模式(Factory Method Pattern),在實際開發過程當中咱們都習慣於直接使用 new 關鍵字用來建立一個對象,但是有時候對象的創造須要一系列的步驟:你可能須要計算或取得對象的初始設置;選擇生成哪一個子對象實例;或在生成你須要的對象以前必須先生成一些輔助功能的對象,這個時候就須要瞭解該對象建立的細節,也就是說使用的地方與該對象的實現耦合在了一塊兒,不利於擴展,爲了解決這個問題就須要用到咱們的工廠方法模式,它適合那些建立複雜的對象的場景,工廠方法模式也是一個使用頻率很高的設計模式
/** * 從Class構造實例 */
public interface IFactory {
@NonNull
<T> T create(@NonNull Class<T> clazz) throws Exception;
}
複製代碼
public class ContextFactory implements IFactory {
private final Context mContext;
public ContextFactory(Context context) {
mContext = context;
}
@Override
public <T> T create(@NonNull Class<T> clazz) throws Exception {
return clazz.getConstructor(Context.class).newInstance(mContext);
}
}
複製代碼
public class EmptyArgsFactory implements IFactory {
public static final EmptyArgsFactory INSTANCE = new EmptyArgsFactory();
private EmptyArgsFactory() {
}
@Override
public <T> T create(@NonNull Class<T> clazz) throws Exception {
return clazz.newInstance();
}
}
複製代碼
經過工廠方法獲取實例
public class ServiceLoader<I> {
private <T extends I> T createInstance(@Nullable ServiceImpl impl, @Nullable IFactory factory) {
//...省略部分代碼
Class<T> clazz = (Class<T>) impl.getImplementationClazz();
//經過工廠方法獲取實例
T t = factory.create(clazz);
Debugger.i("[ServiceLoader] create instance: %s, result = %s", clazz, t);
return t;
}
}
複製代碼
UML圖中的責任鏈模式是一種對象的行爲模式。在責任鏈模式裏,不少對象由每個對象對其下家的引用而鏈接起來造成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪個對象最終處理這個請求,這使得系統能夠在不影響客戶端的狀況下動態地從新組織和分配責任。
succeesor
表示的是責任鏈中的下一個Handler
ChainedHandler
其實是DefaultRootUriHandler
,由於DefaultRootUriHandler
是ChainedHandler
的子類,並且沒有實現handleInternal
方法。因此調用的是ChainedHandler的handleInternal方法,最終調用的是next(Iterator<UriHandler> iterator,UriRequest request,UriCallback callback)
方法。而該方法中,在某個節點的onNext回調裏面又遞歸調用了其本身,這樣就創建了鏈式關係。public class ChainedHandler extends UriHandler {
@Override
protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
next(mHandlers.iterator(), request, callback);
}
private void next(@NonNull final Iterator<UriHandler> iterator, @NonNull final UriRequest request, @NonNull final UriCallback callback) {
if (iterator.hasNext()) {
UriHandler t = iterator.next();
t.handle(request, new UriCallback() {
@Override
public void onNext() {
next(iterator, request, callback);
}
@Override
public void onComplete(int resultCode) {
callback.onComplete(resultCode);
}
});
} else {
callback.onNext();
}
}
}
複製代碼
寫到這裏,有點心虛,畢竟不是本身寫的框架,也不知道當時別人開發的時候的思路。只能說大致上猜想一下。
關於這個框架誕生的需求背景,感受美團外賣Android平臺化架構演進實踐和WMRouter:美團外賣Android開源路由框架說的很詳細,你們能夠學習一下,在遇到問題的時候怎麼選擇解決方案,怎樣設計架構。
關於通用的路由需求,感受這篇文章說的挺好,你們能夠學習一下Android 組件化 —— 路由設計最佳實踐