1. 程式人生 > >元件化開發之路由器模組詳解(ActivityRouter原始碼詳解)

元件化開發之路由器模組詳解(ActivityRouter原始碼詳解)

    路由器的作用是什麼?通俗的講,路由器的作用就是一根網線滿足多人上網的需求。而在開發中路由器模組的作用就是實現中轉分發,也就是說將原來有關係的模組(有依賴的模組分開),產生一箇中間的模組,讓原來依賴的兩個模組都去和路由模組互動,從而將原來兩個有關係的模組拆分開,利如我現在在開發一個app,根據業務需求這裡需要開發一個便民中心模組,但是進入這個模組肯定得先從主模組點選進去,這裡如果有資料之間的傳遞的話,就產生了互動。而互動產生了的話,就會產生依賴,此時便民中心模組依賴主模組,而此時如果我們搭建路由器模組的話,那麼主模組就和便民中心模組解耦了,主模組通過路由器模組將資料分發給便民中心模組,而不像以前我們在android端跳轉的轉的話直接通過Intent跳轉,傳值也通過Intent傳,這裡有個弊端就如定義傳值的key值,在接受這個key隨確定value值得時候,你必須得知道這個key是什麼,後期維護你需要一個一個Activity的去找,跳轉到那個Activity需要傳什麼值,甚是頭疼,那麼路由器的另一個優點來了,我們從一個模組跳到另一個模組需要傳的值的key完全可以通過註解標記出來,一目瞭然,而不用那麼費力的找了,而且具體怎麼傳的完全隔離出去,使用者完全不用考慮實現的細節。

   通俗一點的講,這裡我們先不討論模組之間的隔離,只簡單的討論一下從Activity(A)跳轉到Activity(B)的場景,首先我們第一點得確定是從哪一個Activity跳到哪一個Activity,最初的跳轉是你要麼直接顯示跳轉、要麼隱式跳轉,但是不管怎麼跳轉都需要知道具體是那個Activity(顯示)、隱式(知道action什麼的等),而採用路由器模式的時候,你完全不用關心他的Activity的具體名字是什麼,或者他的action等是什麼,你只需要給它造一個匹配規則,讓路由器自己找到你想傳值和跳轉的Activity到底是哪一個,就好比我定義一個IP地址,這個Ip地址就是指向B(Activity)的,那麼A(Activity)通過將ip地址傳給路由器然後路由器幫你分析你想跳轉到哪一個Activity中,最終鎖定到B,,日後你想修改跳轉規則或傳值是怎麼傳的,只要檢視路由表就好了,這就是路由模組的好處了。

  如果你想自己實現一個路由框架的話,得做哪些準備呢?首先你得知道路由器到底是幹嘛使的,通過上面的分析,你大體應該知道路由器在Android端的作用了,那麼我們首先需要做的就是將所有的模組的主Activity制定他們匹配的ip,也就是說為A模組主(Activity)標記一個唯一的ip地址,也就是加個域名,例如A對應http://feiyu/,B模組對應http://zp/

  然後將對應關係儲存到快取中,儲存到快取中後,我們通過路由器呼叫之後,路由器從路由表中取出對應關係,但是路由表必須知道這個關係的規則是什麼,他需要根據規則將路由表中資料解析出來,然後實現跳轉,那麼在寫路由器模組的時候我們必須為路由器寫上解析器模組,那麼這裡我們已經想到要用至少到兩個設計模式了,單利模式、直譯器模式(專門用來解析規則例如正則表示式就是用的這種設計模式)。

  如果我們要儲存對映關係,是否將它固定,也就是說每寫一個模組的話,將它手動新增到路由表中,很顯然這是不科學的,因為我們寫的路由器模組是給其他小夥伴用的,他不一定願意看你的程式碼,那麼怎麼樣讓他寫的模組映射出路由表裡的資料,這裡就用到了註解,我們在路由器模組中寫出註解規則,和你合作的小夥伴只要按照你的註解規則,為他的模組主activity標記上註解,我們就能動態的將註解解析出來,然後將它放到路由表中。這樣路由器解析的時候就會找到需要跳轉的模組。

  但是這裡又有一個問題,該採用執行時註解還是編譯器時註解呢,執行時註解,就是你動態通過反射將註解解析出來放在集合中,但是那需要在執行時解析,比較耗費點時間,那麼採用編譯器註解呢,就是說在編譯的時候先檢查有沒有編譯註解,如果有的話先通過註解生成java檔案,然後才將新生成的java檔案和你寫的專案java檔案一起編譯成class檔案,但是這麼做的話會多出java檔案,從而使apk包增大,還有可能遇到android的65536的限制,但是一點不影響執行時的速度,綜合考慮還是採用編譯時註解(apt技術)。

看了這麼多,是不是有點累了,先欣賞下美女休息一下


好了,進入正題,這裡我們來一起分析一下ActivityRouter原始碼,參觀一下別人是怎麼實現的,ActivityRouter原始碼 至於為什麼要通過這個框架分析,因為它雖然有點缺點,但是它小巧並且已經能將路由框架的原理思想大體的表現出來。

   前面提到編譯時期的註解,那麼先從這個框架的apt部分開始說起,如果想在android studio中實現apt功能只需要在app下build.gradle配置檔案中加入

compile 'com.google.auto.service:auto-service:1.0-rc3'

然後在你的編譯處理類上加入這個註解

@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor 
先來看下一下實現這個編譯處理類需要實現哪些方法:
public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
    }

初始化方法,在這裡你需要獲得你需要使用的工具類,例如Messager(日誌相關的輔助類),Filter(檔案相關的輔助類(用它輔助生成新的java檔案))
具體的其它輔助類,小夥伴們請查閱相關文件。
 public Set<String> getSupportedAnnotationTypes() {
        Set<String> ret = new HashSet<>();
        ret.add(Modules.class.getCanonicalName());
        ret.add(Module.class.getCanonicalName());
        ret.add(Router.class.getCanonicalName());
        return ret;
    }
這個方法用來告訴註解處理器那些註解需要處理。這裡Module、Modules和Router處理註解需要處理,也就是說這個框架只聲明瞭這
三種註解
@Retention(RetentionPolicy.CLASS)
public @interface Module {
    String value();
}
@Retention(RetentionPolicy.CLASS)
public @interface Modules {
    String[] value();
}
@Retention(RetentionPolicy.CLASS)這個註解用來標記是在編譯時的註解,沒有標記要註解的型別的話,預設為類註解
Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface Router {

    String[] value();

    String[] stringParams() default "";

    String[] intParams() default "";

    String[] longParams() default "";

    String[] booleanParams() default "";

    String[] shortParams() default "";

    String[] floatParams() default "";

    String[] doubleParams() default "";

    String[] byteParams() default "";

    String[] charParams() default "";

    String[] transfer() default "";
}

這個註解即可以標記類,又可以標記方法,其中帶params是傳遞的引數

接下來是下面這個方法
public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

返回支援的java版本
最重要的是實現下面這個方法,只要捕捉到你設定的註解最終就會回撥這個方法供你生成java檔案,如下:
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        debug("process apt with " + annotations.toString());
        if (annotations.isEmpty()) {
            return false;
        }
        boolean hasModule = false;
        boolean hasModules = false;
        // module
        String moduleName = "RouterMapping";
        Set<? extends Element> moduleList = roundEnv.getElementsAnnotatedWith(Module.class);
        if (moduleList != null && moduleList.size() > 0) {
            Module annotation = moduleList.iterator().next().getAnnotation(Module.class);
            moduleName = moduleName + "_" + annotation.value();
            hasModule = true;
        }
        // modules
        String[] moduleNames = null;
        Set<? extends Element> modulesList = roundEnv.getElementsAnnotatedWith(Modules.class);
        if (modulesList != null && modulesList.size() > 0) {
            Element modules = modulesList.iterator().next();
            moduleNames = modules.getAnnotation(Modules.class).value();
            hasModules = true;
        }
        // RouterInit
        if (hasModules) {
            debug("generate modules RouterInit");
            generateModulesRouterInit(moduleNames);
        } else if (!hasModule) {
            debug("generate default RouterInit");
            generateDefaultRouterInit();
        }
        // RouterMapping
        return handleRouter(moduleName, roundEnv);
    }

在這個方法裡面處理所有被註解了的元素,這裡需要弄懂元素型別總共有多少種,如下:
ackage com.example;    // PackageElement

public class Foo {        // TypeElement 型別元素

    private int a;      // VariableElement代表成員變數
    private Foo other;  // VariableElement

    public Foo () {}    // ExecuteableElement 匹配方法元素

    public void setA (  // ExecuteableElement
                     int newA   // TypeElement 引數也代表TypeElement 
                     ) {}
}

這個方法首先獲得Module和Modules註解的元素,然後建立RouterInit這個類,來看一下建立的這個類中有哪些方法和變數
MethodSpec.Builder initMethod = MethodSpec.methodBuilder("init")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
        for (String module : moduleNames) {
			
            initMethod.addStatement("RouterMapping_" + module + ".map()");
        }
		
        TypeSpec routerInit = TypeSpec.classBuilder("RouterInit")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(initMethod.build())
                .build();
        try {
            JavaFile.builder("com.github.mzule.activityrouter.router", routerInit)
                    .build()
                    .writeTo(filer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
這個方法用了javaPoet來生成java檔案,javaPoet是啥?javaPoet是大神編寫的用於輔助生成java檔案的框架。
javaPoet
這個方法的意思就是建立包名為com.github.mzule.activityrouter.router的類,並在這個類中建立靜態init方法,並在init方法中呼叫
RouterMapping_module(註解module的名字被添加註解的activity)類的map方法,當然RouterMapping_module類也是動態生成的,來看一下
它生成的程式碼
private boolean handleRouter(String genClassName, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Router.class);
        MethodSpec.Builder mapMethod = MethodSpec.methodBuilder("map")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
			
                .addStatement("java.util.Map<String,String> transfer = null")
			
                .addStatement("com.github.mzule.activityrouter.router.ExtraTypes extraTypes")
					
                .addCode("\n")
        for (Element element : elements) {
            Router router = element.getAnnotation(Router.class);
			
            String[] transfer = router.transfer();
            if (transfer.length > 0 && !"".equals(transfer[0])) {
                mapMethod.addStatement("transfer = new java.util.HashMap<String, String>()");
                for (String s : transfer) {
                    String[] components = s.split("=>");
                    if (components.length != 2) {
                        error("transfer `" + s + "` not match a=>b format");
                        break;
                    }
					
                    mapMethod.addStatement("transfer.put($S, $S)", components[0], components[1]);
                }
            } else {
                mapMethod.addStatement("transfer = null");
            }

            mapMethod.addStatement("extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes()");
            mapMethod.addStatement("extraTypes.setTransfer(transfer)");

            addStatement(mapMethod, int.class, router.intParams());
            addStatement(mapMethod, long.class, router.longParams());
            addStatement(mapMethod, boolean.class, router.booleanParams());
            addStatement(mapMethod, short.class, router.shortParams());
            addStatement(mapMethod, float.class, router.floatParams());
            addStatement(mapMethod, double.class, router.doubleParams());
            addStatement(mapMethod, byte.class, router.byteParams());
            addStatement(mapMethod, char.class, router.charParams());
      
            for (String format : router.value()) {
                ClassName className;
                Name methodName = null;
                if (element.getKind() == ElementKind.CLASS) {
                    className = ClassName.get((TypeElement) element);
                } else if (element.getKind() == ElementKind.METHOD) {
                    className = ClassName.get((TypeElement) element.getEnclosingElement());
                    methodName = element.getSimpleName();
                } else {
                    throw new IllegalArgumentException("unknow type");
                }
                if (format.startsWith("/")) {
                    error("Router#value can not start with '/'. at [" + className + "]@Router(\"" + format + "\")");
                    return false;
                }
                if (format.endsWith("/")) {
                    error("Router#value can not end with '/'. at [" + className + "]@Router(\"" + format + "\")");
                    return false;
                }
                if (element.getKind() == ElementKind.CLASS) {
                    mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, $T.class, null, extraTypes)", format, className);
                } else {
                    mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, null, " +
                            "new MethodInvoker() {\n" +
                            "   public void invoke(android.content.Context context, android.os.Bundle bundle) {\n" +
                            "       $T.$N(context, bundle);\n" +
                            "   }\n" +
                            "}, " +
                            "extraTypes)", format, className, methodName);
                }
            }
            mapMethod.addCode("\n");
        }
        TypeSpec routerMapping = TypeSpec.classBuilder(genClassName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(mapMethod.build())
                .build();
        try {
            JavaFile.builder("com.github.mzule.activityrouter.router", routerMapping)
                    .build()
                    .writeTo(filer);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return true;
    }

這個方法就是生成RouterMapping_module類,並生成靜態的map()方法,那麼在map方法裡都添加了哪些操作呢?
java.util.Map<String,String> transfer = null
新增轉化的物件(顧名思義就是將一個名字轉化為另一個名字),介紹完生成java的物件的時候會詳細討論
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes()
建立傳值型別的類,用來標記所傳的值是什麼型別的
 mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, $T.class, null, extraTypes)", format, className);
呼叫Routers類的map方法,將對映存到路由表中
討論到這裡,大體的可以看出這個框架利用apt技術動態的生成兩個java類,這兩個java類的主要作用就是將註解交給路由器的
解析類解析對映關係,最終將,對映關係存到路由器快取中。只要在app啟動時呼叫這兩個java類,就可以將Activity和ip的對映
關係儲存到路由器表中,在路由器中轉中,就可以找到合適的模組的主Activity進行分發跳轉了。
  現在來簡單的運用這個框架,如果想把某個模組的主Activity加入到路由表中,直接在這個Activity上新增這個註解:
@Router("user/collection")
public class UserCollectionActivity extends DumpExtrasActivity {
這個註解相當於為這個Activity在路由器表中添加了user/collection這個域名對映記錄,接下來在想要跳轉的地方加上這麼一句話:
Routers.open(context, "router://user/collection")
只是跳轉到該Activity,該Activity結束的時候不需要傳值給上一個Activity
  Routers.openForResult(context,"router://user/collection" ,requestCode);
該Activity結束的時候需要傳值給上一個Activity
用起來確實很簡單,那麼如果要傳值的話,需要加上

Router(value = {"main", "home"},
        longParams = {"id", "updateTime"},
        booleanParams = "web")

這裡聲明瞭域名是main或者home,定義了三個引數long型別:id,updateTime,boolean型別:web,那麼再呼叫這個Activity的時候,
完整的url為router://main?id=1103&updateTime=537896&web=true,是不是類似於get方式傳值,其它的方式小夥伴們請看作者的介紹
好,繼續看跳轉的流程,當呼叫open或者openForResult的方法時都會走到下面這個方法

private static boolean open(Context context, Uri uri, int requestCode, RouterCallback callback) {
        boolean success = false;
        if (callback != null) {
            if (callback.beforeOpen(context, uri)) {
                return false;
            }
        }

        try {
            success = doOpen(context, uri, requestCode);
        } catch (Throwable e) {
            e.printStackTrace();
            if (callback != null) {
                callback.error(context, uri, e);
            }
        }

        if (callback != null) {
            if (success) {
                callback.afterOpen(context, uri);
            } else {
                callback.notFound(context, uri);
            }
        }
        return success;
    }

這裡有一個回撥函式,使用者在自己定義的時候可以監聽跳轉之前和跳轉之後(可以做一些事情,比如攔截、列印等等,自定義預設跳轉等等),如下:
public interface RouterCallback {
    void notFound(Context context, Uri uri);

    boolean beforeOpen(Context context, Uri uri);

    void afterOpen(Context context, Uri uri);

    void error(Context context, Uri uri, Throwable e);
接下來進入下面這個方法實現真正的跳轉,

 private static boolean doOpen(Context context, Uri uri, int requestCode) {
        initIfNeed();
        Path path = Path.create(uri);
        for (Mapping mapping : mappings) {
            if (mapping.match(path)) {
                if (mapping.getActivity() == null) {
                    mapping.getMethod().invoke(context, mapping.parseExtras(uri));
                    return true;
                }
                Intent intent = new Intent(context, mapping.getActivity());
                intent.putExtras(mapping.parseExtras(uri));
                intent.putExtra(KEY_RAW_URL, uri.toString());
                if (!(context instanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                if (requestCode >= 0) {
                    if (context instanceof Activity) {
                        ((Activity) context).startActivityForResult(intent, requestCode);
                    } else {
                        throw new RuntimeException("can not startActivityForResult context " + context);
                    }
                } else {
                    context.startActivity(intent);
                }
                return true;
            }
        }
        return false;
    }

可以看到最終還是通過startActivity或StartActivityForResult實現跳轉,但是在此之前需要從路由表中查詢是否能找到用跳轉的Activity的路由資訊,找到的話將引數都解析出來,用intent進行傳值,這個方法的第一行有initIfNeed()這個方法,這個方法是用來呼叫apt產生的java物件的的方法的,也就是說將有註解標記的java類收集起來註冊進路由表中。

  先來看一看這個框架是怎麼將註解資訊註冊到路由表中的?

 private static void initIfNeed() {
        if (!mappings.isEmpty()) {
            return;
        }
        RouterInit.init();
        sort();
    }

這裡的路由表就是一個private staticList<Mapping> mappings= newArrayList<>()(List集合),如果這個集合有元素的話,則表明已經註冊了,否則這個呼叫RouterInit.init()j進行註冊,RouterInit類是用apt生成的,如果在沒有編譯之前,這個引用肯定會報錯,作者這裡採用了取巧的方式,就是先寫死兩個類
public class RouterInit {
    public static void init() {
    }
}
public final class RouterMapping {

    public static final void map() {

    }
起到騙過編譯器檢查的效果,正常的使用還沒有被編譯註解處理器生成的java類的時候是利用反射,這個欺騙反而增加了那麼點速度,編譯器完成之後,新生成的java類將覆蓋掉那兩個寫死的java類(佔坑java類),在前面的APT生成java類時已經提到過,最終生成的類最後是呼叫了這個框架已經存在的類方法,也就是Routers.map的方法

 來看一下這個方法,如下:
 mappings.add(new Mapping(format, activity, method, extraTypes));
直接向表中裝填mapping物件,這裡注意一下第三個引數,如果Router註解標記的是類,那麼第三個引數為null,如果標記的是方法,那麼第三個引數為MethodInvoker引用,第二個引數為null。

下面是路由表資訊物件Mapping的建構函式:
 public Mapping(String format, Class<? extends Activity> activity, MethodInvoker method, ExtraTypes extraTypes) {
        if (format == null) {
            throw new NullPointerException("format can not be null");
        }
        this.format = format;
        this.activity = activity;
        this.method = method;
        this.extraTypes = extraTypes;
        if (format.toLowerCase().startsWith("http://") || format.toLowerCase().startsWith("https://")) {
            this.formatPath = Path.create(Uri.parse(format));
        } else {
            this.formatPath = Path.create(Uri.parse("helper://".concat(format)));
        }
    }

這個建構函式主要做的工作就是將域名解析為Path類,如果域名以http或https開頭那表示是在配置檔案中配置了下面這些東東
<action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="mzule.com"
                    android:scheme="http" />
接下來看一下 Path.create的方法幹了些什麼?如下:
 public static Path create(Uri uri) {
        //鍒涘緩helper
        Path path = new Path(uri.getScheme().concat("://"));
        //得到路徑
        String urlPath = uri.getPath();
        if (urlPath == null) {
            urlPath = "";
        }
        //擷取掉最後一個/
        if (urlPath.endsWith("/")) {
            urlPath = urlPath.substring(0, urlPath.length() - 1);
        }
        parse(path, uri.getHost() + urlPath);
        return path;
    }
     //有多少種路徑可以找到它就用多少種path
    private static void parse(Path scheme, String s) {
        String[] components = s.split("/");
        Path curPath = scheme;
        for (String component : components) {
            Path temp = new Path(component);
            curPath.next = temp;
            curPath = temp;
        }
    }

這個方法根據host和path將當前的path建立了一個連結串列,表頭的path持有Scheme,然後依次連結host和path以"/"分開的字串,打個比方,如果Uri是zp://www.zp.com/path/zp的話那麼表頭的Path為value為zp://,而後一個Path的value為www.zp.com,第三個Path的value為path,第四個Path的value為zp,也就是說總共產生了四個path的連結串列。

 ok,將Activity的對映資訊註冊到路由表後,那麼又回到doOpen方法,遍歷路由表資訊看看有沒有匹配的Path有的話跳轉,沒有的話回撥notFound方法,接下來假設要跳轉的url為router://main?id=1103&updateTime=537896&web=true,這裡會建立一個擁有兩個Path的連結串列,然後遍歷Mapping,呼叫下面這個方法看Path是否匹配

 public boolean match(Path fullLink) {
        if (formatPath.isHttp()) {
            return Path.match(formatPath, fullLink);
        } else {
            // fullLink without host
            boolean match = Path.match(formatPath.next(), fullLink.next());
            if (!match && fullLink.next() != null) {
                // fullLink with host
                match = Path.match(formatPath.next(), fullLink.next().next());
            }
            return match;
        }
    }
這個方法也很簡單,迴圈判斷連結串列下面是否所有的Path的value都相等(這裡要排除掉帶:的引數),如果相等那就說明匹配到了,注意這裡去掉表頭的Scheme的比較
public static boolean match(final Path format, final Path link) {
        if (format == null || link == null) {
            return false;
        }
        if (format.length() != link.length()) {
            return false;
        }
        Path x = format;
        Path y = link;
        while (x != null) {
            if (!x.match(y)) {
                return false;
            }
            x = x.next;
            y = y.next;
        }
        return true;
    }

假設此時路由表中已經找到匹配的Activity的對映資訊了,那麼接下來就需要將Uri裡面的傳的引數截取出來,將引數傳遞轉化為bundle傳遞,如下方法所示:

public Bundle parseExtras(Uri uri) {
        Bundle bundle = new Bundle();
        // path segments // ignore scheme
        Path p = formatPath.next();
        Path y = Path.create(uri).next();
        while (p != null) {
            if (p.isArgument()) {
                put(bundle, p.argument(), y.value());
            }
            p = p.next();
            y = y.next();
        }
        // parameter
        Set<String> names = UriCompact.getQueryParameterNames(uri);
        for (String name : names) {
            String value = uri.getQueryParameter(name);
            put(bundle, name, value);
        }
        return bundle;
    }

這裡的router://main?id=1103&updateTime=537896&web=true引數為id=1103&updateTime=537896&web=true,三個引數,這裡需要做的就是將這些字串取出來,按引數的key名字和value截取出來分別存在集合中,如下所示:
 public static Set<String> getQueryParameterNames(Uri uri) {
        String query = uri.getEncodedQuery();
        if (query == null) {
            return Collections.emptySet();
        }

        Set<String> names = new LinkedHashSet<String>();
        int start = 0;
        do {
            int next = query.indexOf('&', start);
            int end = (next == -1) ? query.length() : next;

            int separator = query.indexOf('=', start);
            if (separator > end || separator == -1) {
                separator = end;
            }

            String name = query.substring(start, separator);
            names.add(Uri.decode(name));
            // Move start to end of name.
            start = end + 1;
        } while (start < query.length());

        return Collections.unmodifiableSet(names);
    }
最後一步就是將引數轉化為不同的型別

 private void put(Bundle bundle, String name, String value) {
        int type = extraTypes.getType(name);
        name = extraTypes.transfer(name);
        if (type == ExtraTypes.STRING) {
            type = extraTypes.getType(name);
        }
        switch (type) {
            case ExtraTypes.INT:
                bundle.putInt(name, Integer.parseInt(value));
                break;
            case ExtraTypes.LONG:
                bundle.putLong(name, Long.parseLong(value));
                break;
            case ExtraTypes.BOOL:
                bundle.putBoolean(name, Boolean.parseBoolean(value));
                break;
            case ExtraTypes.SHORT:
                bundle.putShort(name, Short.parseShort(value));
                break;
            case ExtraTypes.FLOAT:
                bundle.putFloat(name, Float.parseFloat(value));
                break;
            case ExtraTypes.DOUBLE:
                bundle.putDouble(name, Double.parseDouble(value));
                break;
            case ExtraTypes.BYTE:
                bundle.putByte(name, Byte.parseByte(value));
                break;
            case ExtraTypes.CHAR:
                bundle.putChar(name, value.charAt(0));
                break;
            default:
                bundle.putString(name, value);
                break;
        }
    }
在APT中解析引數型別的時候,每一個mapping都有一個唯一的ExtraTypes類來儲存不同的引數型別,以引數的名字標記之
 public int getType(String name) {
        if (arrayContain(intExtra, name)) {
            return INT;
        }
        if (arrayContain(longExtra, name)) {
            return LONG;
        }
        if (arrayContain(booleanExtra, name)) {
            return BOOL;
        }
        if (arrayContain(shortExtra, name)) {
            return SHORT;
        }
        if (arrayContain(floatExtra, name)) {
            return FLOAT;
        }
        if (arrayContain(doubleExtra, name)) {
            return DOUBLE;
        }
        if (arrayContain(byteExtra, name)) {
            return BYTE;
        }
        if (arrayContain(charExtra, name)) {
            return CHAR;
        }
        return STRING;
    }

其實這個方法就是直接根據引數的名字去ExtraTypes集合中去找有沒有這個名字的儲存,如果有這個名字的話,那麼將型別提出出來,也就是已經確定型別了
  private String[] intExtra;
    private String[] longExtra;
    private String[] booleanExtra;
    private String[] shortExtra;
    private String[] floatExtra;
    private String[] doubleExtra;
    private String[] byteExtra;
    private String[] charExtra;
ExtraTypes類總共有這麼多集合,在編譯期已經將引數名字儲存到ExtraTypes中,最後通過新生成的java檔案,呼叫RouterInit.init()方法將ExtraTypes和mapping繫結,從而達到在傳引數時的動態轉化。好了,在android的實現一個路由架構的原理基本就介紹完了,歡迎小夥伴點贊和留言。