WMRouter使用和源碼分析

前言

項目組件化過程當中使用了WMRouter,爲了更好的理解並使用。花了一週的時間研究了一下WMRouter(v1.2.0版本)。下面從四個方面說下本身的理解,但願能給你們提供幫助。html

  1. what it is? what can it do?
  2. how to use it ?
  3. how it works ?
  4. why design like that?

一,WMRouter是什麼,能解決什麼問題?

1.1,什麼是路由框架?

路由框架,也就是路由的做用。路由是起什麼做用呢?就像送快遞,從小縣城到省會,再從省會發到北京分撥中心,而後再從北京分撥中心發到回龍觀,再從回龍觀發到具體的小區。路由框架解決的就是如何從A頁面跳轉到B頁面的問題,其會在A和B之間創建數個節點。經過這些節點依次進行轉發,最終達到目的地。java

1.2,爲何須要路由框架?

Android原生已經支持AndroidManifest去管理App跳轉,爲何要有路由庫?android

  • 顯示Intent:項目龐大之後,類依賴耦合太大,不適合組件化拆分
  • 隱式Intent:協做困難,調用時候不知道調什麼參數。每一個註冊了Scheme的Activity均可以直接打開,有安全風險
  • AndroidMainfest集中式管理比較臃腫
  • 沒法動態修改路由,若是頁面出錯,沒法動態降級
  • 沒法動態攔截跳轉,譬如未登陸的狀況下,打開登陸頁面,登陸成功後接着打開剛纔想打開的頁面
  • H五、Android、iOS地址不同,不利於統一跳轉

1.3,WMRouter的特色。

WMRouter是一款Android路由框架,主要提供URI分發、ServiceLoader兩大功能(後面會看到,URI分發功能也是用ServiceLoader實現的)git

URI分發功能可用於跨module的頁面跳轉、動態下發URI連接的跳轉等場景,特色以下:github

  1. 跳轉的頁面支持配置scheme、host、path。
  2. 支持URI正則匹配。
  3. 支持頁面Exported控制,特定頁面不容許外部跳轉
  4. 默認使用註解配置自動註冊,也支持Java代碼動態註冊。
  5. 某些頁面須要登陸等條件才能進入的時候,能夠配置攔截器,可在跳轉前執行同步/異步操做。
  6. 支持單次跳轉特殊操做:Intent設置Extra/Flags、設置跳轉動畫、自定義StartActivity操做等
  7. 支持配置單次和全局跳轉監聽(能夠實現降級策略,也能夠自定義處理邏輯)
  8. 徹底組件化設計,核心組件都可擴展、按需組合,實現靈活強大的功能

WMRouter提供了ServiceLoader模塊,相似Java中的 java.util.ServiceLoader,但功能更加完善。經過ServiceLoader能夠在一個App的多個模塊之間經過接口調用代碼,實現模塊解耦,便於實現組件化、模塊間通訊,以及和依賴注入相似的功能等。其特色以下:正則表達式

  1. 使用註解自動配置
  2. 支持獲取接口的全部實現,或根據Key獲取特定實現
  3. 支持獲取Class或獲取實例
  4. 支持無參構造、Context構造,或自定義Factory、Provider構造
  5. 支持單例管理
  6. 支持方法調用

二,WMRouter怎麼用?

2.1,URI分發功能基本使用

詳細使用參見WMRouter設計與使用文檔,這裏就大概說下整體的流程。設計模式

第一步,添加依賴

  • 根目錄的build.gradle配置插件
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // Android Gradle插件
        classpath 'com.android.tools.build:gradle:3.2.1'
        // 添加WMRouter插件
        classpath "com.sankuai.waimai.router:plugin:1.x"
    }
}
複製代碼
  • Application模塊中的build.gradle:
apply plugin: 'com.android.application'
// 應用WMRouter插件
apply plugin: 'WMRouter'
複製代碼
  • 添加對wmrouter的依賴。若是有基礎依賴庫,能夠添加到基礎依賴庫。這樣不用每一個module都添加。
compile 'com.sankuai.waimai.router:router:1.x'
複製代碼
  • 在使用了註解的每一個模塊中配置註解生成器。注意是每一個使用了註解的模塊都要配置。
annotationProcessor 'com.sankuai.waimai.router:compiler:1.x'
複製代碼

第二步,Proguard配置

  • WMRouter已經內置了Proguard配置。(詳見源碼router/proguard-rules.pro),而且在router的build.gradle中已經配置了consumerProguardFiles屬性。因此使用AAR依賴時通常不須要重複配置。這一點挺好的,若是咱們本身寫SDK的話,也建議這樣作。
# 保留ServiceLoaderInit類,須要反射調用
-keep class com.sankuai.waimai.router.generated.ServiceLoaderInit { *; }
# 避免註解在shrink階段就被移除,致使obfuscate階段註解失效、實現類仍然被混淆
-keep @interface com.sankuai.waimai.router.annotation.RouterService
複製代碼

第三步,初始化SDK

在Application.onCreate中初始化:最簡單的方式初始化方式就兩行代碼。數組

// 建立RootHandler
DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);
// 初始化,必須在主線程調用
Router.init(rootHandler);
複製代碼

第四步,配置跳轉activity

跳轉的目標Activity,添加註解@RouterUri瀏覽器

@RouterUri(path = "/test/schemehost", scheme = "test", host = "test.demo.com")
public class AdvancedDemoActivity extends BaseActivity {
    ...
}
複製代碼

第五步, 發起URI跳轉

發起跳轉有好幾種方式,經常使用的有如下三種。其實最經常使用的是方式三。緩存

// 方式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();
複製代碼

2.2, URI分發功能的高級配置

上面5步只是最基本的URI分發功能使用,SDK還提供了不少設置,方便在實際項目中使用。另外,在使用過程當中,還有一些比較容易遺漏的點。下面進行詳細說明。

2.2.1,依賴和混淆配置

  • 注意:若是項目配置的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 * { *; }
    複製代碼
    • 這裏的實際狀況指的是?到底什麼狀況下才必須在項目中配置呢?查看源碼後發現,用到反射的地方有:DefaultFactory,ProviderPool,以及自定義的IFactory。 默認是使用DefaultFactory進初始化,最終是使用clazz.newInstance()進行實例化對象的。若是沒有使用自定義工廠、@RouterProvider、或者非默認參數構造函數以外的其餘構造函數,就不用添加上面的配置。可是我的感受,安全起見,最好一開始就將全部混淆都配置上,防止後面更改了邏輯以後遺漏了。
    // 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");
            }
        });
    複製代碼

2.2.2,經常使用的設置

  • com.sankuai.waimai.router.core.RootUriHandler#setGlobalOnCompleteListener:設置全局跳轉完成的監聽,能夠在其中跳轉失敗時執行全局降級邏輯。
  • com.sankuai.waimai.router.common.DefaultRootUriHandler#lazyInit:提早初始化(我的感受,初始化指的是,掃描全部註解生成的註冊代碼、反射獲取class,建立接口的實例、執行註冊,從而生成路由表的過程)。該方法最好放到子線程執行,防止啓動過慢。若是沒有提早執行該方法,也會在路由分發的過程當中,執行註冊。
  • 配置檢查與Debugger配置。使用註解進行配置,註解每每分散在一個工程的不一樣代碼文件甚至不一樣的工程中。若是沒有很好的文檔或代碼約束,很容易出現多個頁面配置了相同的URI或Service致使衝突的問題。 所以WMRouter在註解生成階段、APK打包階段,使用註解生成器和Gradle插件進行檢查,檢查到配置衝突或錯誤會拋異常,中斷編譯。WMRouter中的Debugger用於調試和Log輸出,運行時也會對一些配置進行檢查,若是出現配置用法錯誤或其餘嚴重問題會調用Debugger.fatal()拋出。 Debugger建議配置使用DefaultLogger:
    • 測試環境下開啓Debug模式,fatal錯誤會拋出異常及時暴漏問題;
    • 線上環境關閉Debug模式,發生問題不拋異常;能夠經過覆寫DefaultLogger上報Error和Fatal級別的問題。
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();

複製代碼
  • 跳轉來源與Exported控制
    • com.sankuai.waimai.router.common.DefaultUriRequest#from:設置跳轉來源參數,包括內部跳轉、外部跳轉、來自WebView的跳轉、來自Push通知的跳轉等,也能夠自定義跳轉來源,具體實現參考UriSourceTools。
    • 跳轉來源能夠用於實現Exported控制(SDK中註解裏面的屬性)、埋點統計、特殊業務邏輯等。其中Exported控制相似Android中Activity原生的Exported屬性,默認爲false,表示不容許來自外部的跳轉,從而避免一些安全問題或功能異常。外部跳轉由UriProxyActivity統一接收,而後調用WMRouter跳轉並設置from爲UriSourceTools.FROM_EXTERNAL,以後UriHandler經過跳轉來源和頁面的Exported配置便可判斷是否容許跳轉。
    • 經過UriSourceTools.setDisableExportedControl能夠開啓或關閉Exported控制。
/** 無效來源 */
    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;
複製代碼

2.3,URI分發功能的註解

2.3.1,RouterUri註解

最經常使用,基本只用這個註解就能夠知足URI分發需求。根據URI的scheme+host,尋找並分發給對應的PathHandler,以後PathHandler再根據path匹配RouterUri註解配置的節點。可用於Activity或UriHandler的非抽象子類(Activity也會被轉化成UriHandler,在Activity中能夠經過Intent.getData()獲取到URI)

參數以下:

  • path:跳轉URI要用的path,必填。path應該以"/"開頭,支持配置多個path。
  • scheme、host:跳轉URI的scheme和host,可選。
  • exported:是否容許外部跳轉,可選,默認爲false。
  • interceptors:要添加的Interceptor,可選,支持配置多個。

說明:

  1. WMRouter支持多scheme+host+path的跳轉,也支持只有path的跳轉。若是RouterUri中配置了scheme、host、path,則跳轉時應使用scheme+host+path的完整路徑;若是RouterUri中只配置了path,則跳轉應直接使用path。

  2. 因爲多數場景下每每只須要一個固定的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");
複製代碼

2.3.2,RouterRegex註解

RouterRegex註解也能夠用於Activity和UriHandler,經過正則進行URI匹配。

參數以下:

  • regex:正則表達式,必填。用於匹配完整的URI字符串。
  • priority:優先級,數字越大越先匹配,可選,默認爲0。優先級相同時,不保證前後順序。
  • exported:是否容許外部跳轉,可選,默認爲false。
  • interceptors:要添加的Interceptor,可選,支持配置多個。
舉例

一、對於指定域名的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);
        }
    }
}
複製代碼

2.3.3,RouterPage註解

RouterPage註解用於指定內部頁面跳轉,和RouterUri註解相比,RouterPage註解對應的scheme和host爲固定的wm_router://page,不可配置,exported爲false也不可配置。感受這個是因爲歷史緣由存在的一個註解。本質和RouterUri註解是同樣的。咱們本身的項目不會用到這個。因此不詳細介紹了。有興趣的小夥伴能夠自行查看WMRouter設計與使用文檔

2.4,核心組件的擴展

2.4.1,自定義UriHandler

  • 上面2.3.1中的HomeABTestHandler就屬於自定義UriHandler,只不過繼承了AbsActivityHandler而不是UriHandler。實際上,AbsActivityHandler也是UriHandler的子類(後面分析源碼的時候會講到)。更多狀況下,是直接繼承UriHandler來實現自定義的需求。
  • 這是另外一個例子:注意,直接繼承UriHandler的實現類,要重寫shouldHandle方法和handleInternal方法。
/** 跳轉到系統自帶瀏覽器 */
@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);
        }
    }
}

複製代碼
  • 還須要注意:若是自定義的UriHandler上面有@RouterUri、@RouterRegex或者@RouterPage註解,註解會自動將自定義UriHandler註冊到路由表。若是沒有這三個註解,那麼須要在Router.init以前,將自定義的UriHandler添加到RootUriHandler的實例中(DefaultRootUriHandler是最經常使用的RootUriHandler實例)。
// 建立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);
複製代碼

2.4.2,自定義UriInterceptor

  • UriInterceptor爲攔截器,不作最終的URI跳轉操做,但能夠在最終的跳轉前進行各類同步/異步操做,常見操做舉例以下:

    • URI跳轉攔截,禁止特定的URI跳轉,直接返回403(例如禁止跳轉非meituan域名的HTTP連接)
    • URI參數修改(例如在HTTP連接末尾添加query參數)
    • 各類中間處理(例如打開登陸頁登陸、獲取定位、髮網絡請求)
    • ……
  • 每一個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);
        }
    }
}
複製代碼

須要注意的是:

  • 每一個UriHandler均可以添加若干UriInterceptor(經過com.sankuai.waimai.router.core.UriHandler#addInterceptor方法添加),若是添加多個攔截器。則會安裝添加的順序封裝成一個攔截器鏈,依次執行。熟悉責任鏈模式的小夥伴應該很容易就能看明白。感興趣的小夥伴能夠查看源碼,com.sankuai.waimai.router.core.UriHandler#handle和com.sankuai.waimai.router.core.ChainedInterceptor。 - 若是當前攔截器的intercept方法中執行了com.sankuai.waimai.router.core.UriCallback#onNext方法,就會接着執行下一個攔截器。 - 若是當前攔截器的intercept方法中執行了com.sankuai.waimai.router.core.UriCallback#onComplete方法,就會結束攔截器鏈的調用。開始執行UriHandler的handleInternal方法。 - 若是當攔截器鏈全部的攔截器都遍歷完畢了。開始執行UriHandler的handleInternal方法。
  • 路由分發可能會通過好幾層UriHandler(好比從DefaultRootUriHandler分發到UriAnnotationHandler,而後再從UriAnnotationHandler分發到PathHandler,再從PathHandler分發到ActivityHandler),而每個UriHandler的實例都有本身的一串攔截器鏈。由於SDK是經過RootUriHandlerstartUri開始分發的,因此,若是要添加全局的攔截器,就能夠經過給RootUriHandler的子類實例對象,好比DefaultRootUriHandler對象添加攔截器的方式實現。
  • 攔截器的添加方式有兩種,一種是寫在上述三個註解裏面(最終經過UriTargetTools類的parse方法添加到目標UriHandler的攔截器鏈中),一種是直接經過UriHandler的addInterceptor方法添加。
  • 攔截器只能添加到UriHandler的實例對象中(能夠是activity或者其餘UriHandler實現類),並不能添加到某個方法上,這有什麼影響呢?好比商品詳情頁activity有個加購物車按鈕,點擊聊天按鈕,會判斷是否登錄,若是已經登錄了,直接執行加購物車代碼邏輯。若是沒有登錄,會先跳轉到登錄頁面,而後登錄成功以後再繼續執行加購物車代碼邏輯。這個加購物車方法,是沒有辦法經過添加攔截器的方式解決的。除非是某個activity,必須登錄才能進入,這種狀況下才能夠經過給這個activity添加登錄攔截器。感興趣的小夥伴能夠參考github的issue:關於登陸攔截器的問題

2.4.3,自定義RootUriHandler

根據實際狀況,能夠自定義具備各類功能的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);
複製代碼
  • 我的感受,自定義RootUriHandler通常沒有必要,若是真的有特殊需求,建議看懂源碼的執行邏輯以後纔開始動手。要知道,RootUriHandler的startUri方法是整個router的開始路由分發的入口。DefaultRootUriHandler構造方法中添加了SDK默認支持的各類子節點(UriAnnotationHandler,RegexAnnotationHandler,PageAnnotationHandler,StartUriHandler)。自定義的RootUriHandler最好也要加上這些子節點,不然會影響SDK的基本功能。
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);
    }
複製代碼

2.4.4,自定義ActivityLauncher

經過查看源碼發現,全部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;
        }
    }
    //...省略代碼
}
複製代碼

2.5,ServiceLoader的使用

2.5.1,什麼是ServiceLoader?有什麼做用?

簡單來講,ServiceLoader的核心做用就是:根據接口(或抽象類)名,找到接口(或抽象類)的具體實例,若是一個接口對應多個實例,那麼再根據不一樣實例的key找到具體的接口實例。

在實現組件化的項目中,極可能多個業務module之間是沒有依賴關係的可是確實有可能不一樣的業務module之間仍是有業務邏輯的耦合的。好比:

  • 跨module跳轉頁面,經過WMRouter的URI分發功能解決;
  • 業務層,業務moduleA須要用到業務moduleB的某些代碼邏輯。這個時候就輪到ServiceLoader發揮做用了。能夠把這部分邏輯抽象成一個接口(抽象類)。將接口類下沉到基礎module中,而後接口的實現放到業務moduleB,接口的實現類上面添加@RouterService註解,這樣業務moduleA就可以經過WMRouter獲取業務moduleB的接口實現了。

2.5.2,怎麼使用?

ServiceLoader模塊使用主要分三步:

  1. 定義Java接口或抽象類。若是須要跨module調用接口實現,要把接口下沉,確保不一樣module都能獲取接口抽象類。
  2. 實現接口或抽象類,而後添加@RotuerService註解(接口抽象類,key,是否單例)。
  3. 經過Router的一系列getService方法獲取接口的實現類的Class或者實例對象。

RouterService註解

經過RouterService註解聲明實現類所實現的接口(或繼承的父類,例如Activity、Fragment、Object等,後文再也不重複說明),一個接口能夠有多個實現類,一個類也能夠同時實現多個接口。RouterService註解的參數以下:

  • interfaces:必選參數。聲明實現的接口,可配置多個。
  • key:可選參數。同一接口的不一樣實現類,經過惟一的key進行區分。
  • singleton:可選參數。聲明實現類是否爲單例,默認爲false。

示例以下:

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進行頁面跳轉。

  • 指定接口和Key,獲取某個實現類的Class(要求註解聲明時指定了Key)
Class<IService> clazz = Router.getServiceClass(IService.class, "key1");
複製代碼
  • 指定接口,獲取註解聲明的全部實現類的Class
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參數構造
// 使用Context參數構造
IService service = Router.getService(IService.class, context);
List<IService> list = Router.getAllServices(IService.class, context);
複製代碼
  • 自定義Factory經過反射構造

對於實現類有特殊構造函數的狀況,能夠經過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);
複製代碼
  • 使用Provider提供實例

在聲明實現類時,能夠在類中定義一個返回值類型爲該實現類且無參數的靜態方法,並使用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參數說明

註解聲明爲singleton的單例實現類,在調用getService()/getAllServices()方式獲取實例時,實例會由單例緩存池管理,WMRouter中不會重複構造,且線程安全。

注意:當經過ServiceLoader獲取Class、直接調用等其餘方式使用實現類時,應避免重複建立對象,不然會致使單例失效。能夠結合Provider確保實例不會重複建立。

三,爲了解決這些問題,WMRouter內部是如何實現的。分析源碼

WMRouter的核心原理大概就是,經過註解標註路由信息,在編譯期動態掃描路由信息,生成加載路由表信息的java類。並利用 gradle transform和asm生成加載所有路由信息的class文件。在app運行時,路由框架反射調用這個class文件,從而完成了路由表的裝載。

3.1,總體流程

3.1.1,路由關係生成

編譯時註解生成ServiceInit_*類,UriAnnotationInit_*類等輔助註冊代碼。

  • 首先,編譯的時候,根據@RouterUri,@RouterRegex,@RouterPage,@RouterService註解,生成輔助代碼。詳細的文件見下圖。具體的生成原理參見路由節點的動態生成

  • 這裏須要注意,@RouterUri,@RouterRegex,@RouterPage這三個註解,會同時生成兩個文件。以@RouterUri爲例進行說明:

    • 文件1,UriAnnotationInit_**類,其init方法中的每一行,都是本module中使用@RouterUri註解的類的註冊到UriAnnotationHandler的執行代碼。所謂註冊,其實質就是創建映射關係。須要注意UriAnnotationHandler這個註冊過程,一旦執行,就會開啓整個UriAnnotationHandler這個分支的全部註冊過程。
    • 文件2,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);
  }
}

複製代碼

gradle插件,生成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();
  }
}

複製代碼

3.1.2,分發過程

  • 經過Router#startUri(com.sankuai.waimai.router.core.UriRequest)開啓跳轉
  • 其中調用了getRootHandler().startUri(request);其中getRootHandler()方法獲取RootUriHandler的實例,默認是DefaultRootUriHandler對象。
  • DefaultRootUriHandler是RootUriHandler的子類。其startUri方法調用的是父類的。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的子類中都會調用(責任鏈模式)。
  • 分析handle方法的時候,必定要注意當前的UriHandler的具體實例是什麼
    • 第一次執行UriHandler#handle方法,執行第一句代碼shouldHandle。
    • 當前實例是DefaultRootUriHandler對象,其父類是RootUriHandler,其父類的父類是ChainedHandler。由於DefaultRootUriHandler和RootUriHandler都沒有實現shouldHandle方法而ChainedHandler實現了。
    • 因此,第一次執行UriHandler#handle方法的第一句shouldHandle,執行的是ChainedHandler的shouldHandle方法。該方法會判斷mHandlers是否爲空,在DefaultRootUriHandler的構造函數裏面,已經添加了UriAnnotationHandler,PageAnnotationHandler,RegexAnnotationHandler,StartUriHandler等UriHandler對象。因此,shouldHandle返回true,繼續向下執行。
    • 而後判斷攔截器,這塊不影響主體流程,咱們先略過,直接看handleInternal(request, callback)這行代碼。而後和shouldHandle方法同樣,DefaultRootUriHandler實例的handleInternal方法,也是用的ChainedHandler的實現。其handleInternal方法中,調用了next方法。開始了對DefaultRootUriHandler的mHandlers集合的遍歷。咱們查看next方法能夠發現,在遍歷過程當中,若是某個UriHandler執行過程當中,調用了onNext回調(表明本身不負責這個request),會繼續執行next方法,交給下一個UriHandler執行。若是若是某個UriHandler執行過程當中,調用了onComplete回調,表明這個request是其負責的,就結束遍歷。
    • 舉個例子。上面說過了,在DefaultRootUriHandler的構造函數裏面給mHandlers添加了各類UriHandler對象。好比ChainedHandler的next方法輪詢過程當中,正在獲取的UriHandler實例是UriAnnotationHandler,而後調用UriAnnotationHandler的handle方法。
    • UriAnnotationHandler的handle中,首先會調用LazyInitHelper的ensureInit方法。實際上調用的是UriAnnotationHandler的initAnnotationConfig()方法。在該方法中,調用com.sankuai.waimai.router.components.DefaultAnnotationLoader#load方法。最終調用的是ServiceLoader.load(clazz).getAll(),來獲取全部UriAnnotationInit接口的實現(經過註解生成的),而後調用這些實現的init方法。完成了UriAnnotationHandler分支的路由關係的註冊。詳情可參看下面ServiceLoader的講解。
    • 而後調用super.handle(),又回到了UriHandler的handle()中。最終調用了UriAnnotationHandler的shouldHandle和handleInternal方法。在handleInternal方法中,經過request找到PathHandler,而後交給PathHandler執行其handle方法。而後PathHandler的handleInternal方法中,又根據request找到其內部註冊的UriHandler,若是能找到,就交給這個子UriHandler去執行,若是找不到,就本身執行。這樣就完成了分發過程。是否是感受跟事件分發很像,只不過是這裏分發的是一個包裝了地址的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);
    }
     @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);
  }
}

複製代碼

3.2,核心類

3.2.1,UriHandler及其子類

UriHandler就是咱們所說的節點。UriHandler用於處理URI跳轉請求,能夠嵌套從而逐層分發和處理請求。UriHandler是異步結構,接收到UriRequest後處理(例如跳轉Activity等),若是處理完成,則調用callback.onComplete()並傳入ResultCode;若是沒有處理,則調用callback.onNext()繼續分發。

UriHandler及其實現類的類圖整理以下(簡單起見,省略了部分實現):

  • UriHandler是抽象類,其核心在於handle()方法,另外,shouldHandle()handleInternal()方法是抽象方法,由子類去實現。shouldHandle()方法判斷是否應該在當前UriHandler執行分發。handleInternal()是當前UriHandler執行分發的具體邏輯。
  • RootUriHandlerstartUri()方法,是全部分發跳轉的入口。DefaultRootUriHandlerRootUriHandler的默認實現,其構造函數中添加了PageAnnotationHandler,UriAnnotationHandler,RegexAnnotationHandler,StartUriHandler的實例。而後分發跳轉請求的時候,依次交給這四個UriHandler處理,若是某個UriHandler不可以處理該請求,則交給下一個執行。若是可以處理該請求,則交給該UriHandler的子節點繼續分發。好比,若是UriAnnotationHandler可以處理該請求,會交給其子節點PathHandler處理,而後PathHandler交給ActivityHandler處理。
  • 只有PageAnnotationHandler,UriAnnotationHandler,RegexAnnotationHandler這三個子類複寫了UriHandlerhandle()方法。其複寫的目的是爲了在真正開始分發以前,調用mInitHelper.ensureInit(),確保該分支的路由表已經生成。
  • PageAnnotationHandler寫死了SCHEME和HOST,因此只處理全部格式爲 wm_router://page/* 的URI,根據path匹配。
  • UriAnnotationHandler分發到PathHandler,PathHandler分發到ActivityClassNameHandlerActivityHandler是最經常使用的分發路徑。
  • 若是PageAnnotationHandler,UriAnnotationHandler,RegexAnnotationHandler及其子節點,都沒有處理某個路由請求(處理的意思是:執行了UriCallback的onComplete回調),則交給StartUriHandler處理。StartUriHandler會從路由請求UriRequest中獲取uri等參數,直接經過intent跳轉。

3.2.2,UriRequest及其子類

UriRequest中包含Context、URI和Fields,其中Fields爲HashMap<String, Object>,能夠經過Key存聽任意數據。簡單起見,UriRequest類同時承擔了Response的功能,跳轉請求的結果,也會被保存到Fields中。

  • Intent的Extra參數,Bundle類型
  • 用於startActivityForResult的RequestCode,int類型
  • 用於overridePendingTransition方法的頁面切換動畫資源,int[]類型
  • 本次跳轉結果的監聽器,OnCompleteListener類型

每次URI跳轉請求會有一個ResultCode(相似HTTP請求的ResponseCode),表示跳轉結果,也存放在Fields中。常見Code以下,用戶也能夠自定義Code,爲了不衝突,自定義Code應使用負數值

  • 200:跳轉成功
  • 301:重定向到其餘URI,會再次跳轉
  • 400:請求錯誤,一般是Context或URI爲空
  • 403:禁止跳轉,例如跳轉白名單之外的HTTP連接、Activity的exported爲false等
  • 404:找不到目標(Activity或UriHandler)
  • 500:發生錯誤

總結來講,UriRequest用於實現一次URI跳轉中全部組件之間的通訊功能。SDK默認提供了DefaultUriRequest,通常用其就能夠完成平常需求。

3.2.3,AnnotationInit及其子類

AnnotationInit的做用是,提供統一的,調用生成的輔助類的init方法。方便註冊路由表。DefaultAnnotationLoader的load方法,經過ServiceLoader獲取這些AnnotationInit的全部實現,而後調用其init方法。

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);
        }
    }
}
複製代碼

3.2.4,ServiceLoader

上面說過,ServiceLoader的原理是首先經過接口名獲取ServiceLoader實例,而後再從ServiceLoader實例中,根據key找到對應的接口實現類。那麼具體過程呢?

保存映射關係
  1. 調用put方法(見下圖),參數有:接口的Class對象,接口實現類中的key,接口實現類的Class對象,以及接口實現類是否要求單例。在put方法中,調用SERVICES.put(interfaceClass, loader),存入接口Class對象和ServiceLoader實例的關係。而後調用loader.putImpl(key, implementClass, singleton)
  2. 在ServiceLoader實例的putImpl方法中,調用mMap.put(key, new ServiceImpl(key, implementClass, singleton)),在ServiceLoader實例中存入key和接口實例class對象的關係。注意這裏建立了一個ServiceImpl對象。那麼ServiceImpl是幹什麼的呢?
獲取映射關係
  1. 經過getService等方法獲取接口的實例。該方法就一行代碼ServiceLoader.load(clazz).get(key)
  2. load方法中,首先調用sInitHelper.ensureInit()。經過反射,調用com.sankuai.waimai.router.generated.ServiceLoaderInit類的init方法(ServiceLoaderInit類是經過gradle插件生成的)。在init方法中調用各個ServiceInit_36ed390bf4b81a8381d45028b37cc645init方法(見下圖)。那麼ServiceLoaderInit類中的init方法中的這些ServiceInit_36ed390bf4b81a8381d45028b37cc645類是什麼呢?這些類中的init方法裏面又是什麼呢?
  3. 而後繼續執行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);
    }
}
複製代碼

3.3,用到的設計模式

3.3.1,外觀模式

外觀模式提供一個統一的接口,用來訪問子系統中的一羣接口,外觀定義了一個高層接口,讓子系統更容易使用。

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);
    }
    ...
}
複製代碼

3.3.2,單例模式

單例模式:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點

單例模式常見的有 七種寫法,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;
        }
    }
}
複製代碼

3.3.3,工廠方法模式

工廠方法模式(Factory Method Pattern),在實際開發過程當中咱們都習慣於直接使用 new 關鍵字用來建立一個對象,但是有時候對象的創造須要一系列的步驟:你可能須要計算或取得對象的初始設置;選擇生成哪一個子對象實例;或在生成你須要的對象以前必須先生成一些輔助功能的對象,這個時候就須要瞭解該對象建立的細節,也就是說使用的地方與該對象的實現耦合在了一塊兒,不利於擴展,爲了解決這個問題就須要用到咱們的工廠方法模式,它適合那些建立複雜的對象的場景,工廠方法模式也是一個使用頻率很高的設計模式

WMRouter中,IFactory是抽象工廠。工廠方法是create()方法,生產的產品是泛型T。ContextFactory是具體工廠,其create方法,經過獲取包含context的構造函數,建立T的實例。EmptyArgsFactory是另一個具體工廠,其create方法,經過clazz.newInstance()無參構造函數建立實例。

/** * 從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;
    }
}
複製代碼

3.3.4,責任鏈模式

責任鏈模式是一種對象的行爲模式。在責任鏈模式裏,不少對象由每個對象對其下家的引用而鏈接起來造成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪個對象最終處理這個請求,這使得系統能夠在不影響客戶端的狀況下動態地從新組織和分配責任。

UML圖中的succeesor表示的是責任鏈中的下一個Handler

WMRouter路由分發時序圖

  • 上圖中的ChainedHandler其實是DefaultRootUriHandler,由於DefaultRootUriHandlerChainedHandler的子類,並且沒有實現handleInternal方法。因此調用的是ChainedHandler的handleInternal方法,最終調用的是next(Iterator<UriHandler> iterator,UriRequest request,UriCallback callback)方法。而該方法中,在某個節點的onNext回調裏面又遞歸調用了其本身,這樣就創建了鏈式關係。
  • WMRouter中,UriHandler的若干子類構成了一個責任鏈,UriInterceptor的若干子類構成了另外一個責任鏈。
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();
        }
    }
}
複製代碼

四,爲何要這麼設計?

寫到這裏,有點心虛,畢竟不是本身寫的框架,也不知道當時別人開發的時候的思路。只能說大致上猜想一下。

  • 首先要結合本身的業務背景和需求,看看有哪些問題須要解決?
  • 查看如今有哪些比較成熟的輪子,能不能解決本身的問題,有沒有什麼坑?
    • github上的開源框架
    • 不少大廠,有本身的基礎技術部門,有不少內部的工具框架。
  • 針對本身的特殊業務需求,需求的緊急和重要程度,以及人力和排期,怎麼解決這些問題?
    • 資源緊張,需求等級不高,就直接用現有的輪子
    • 若是資源充足,並且現有輪子不可以很好知足需求,能夠本身研究一下實現原理,根據本身的需求造一個新輪子

關於這個框架誕生的需求背景,感受美團外賣Android平臺化架構演進實踐WMRouter:美團外賣Android開源路由框架說的很詳細,你們能夠學習一下,在遇到問題的時候怎麼選擇解決方案,怎樣設計架構。

關於通用的路由需求,感受這篇文章說的挺好,你們能夠學習一下Android 組件化 —— 路由設計最佳實踐

五,拓展學習

  • WMRouter,美團外賣團隊出品,目前已有1.3K-star
  • ARouter,阿里,目前已有9.5K-star
  • Andromeda,愛奇藝,目前已有1.8K-star
  • ActivityRouter,我的項目,目前已有2.7K-star
  • Router,我的項目,目前已有1.3K-star

參考文章:

WMRouter:美團外賣Android開源路由框架

github.com/meituan/WMR…

WMRouter設計與使用文檔

WMRouter源碼分析(1)-基本結構分析

Android 組件化 —— 路由設計最佳實踐

一文了解Android中路由(Router)的實現

美團外賣開源路由框架 WMRouter 源碼分析

生成帶混淆配置的aar庫

寫給 Android 開發者的混淆使用手冊

Android混淆——瞭解這些就夠了

細說反射,Java 和 Android 開發者必須跨越的坎

java/android 設計模式學習筆記目錄

相關文章
相關標籤/搜索