Android 路由模塊分析筆記

為什么需要路由模塊

方便不同的模塊間相互跳轉,增加模塊間的獨立性,而顯式的 startActivity 方式顯然是做不到的,而且簡潔優雅,當然還有其他更多的優點

需要考慮實現的基本功能

  • 1.ActivityFragment 的跳轉

  • 2.跳轉間的傳參

  • 3.跳轉 ActivitylauncherFlag 處理?

  • 4.轉場動畫?

開源庫分析

ActivityRouter

特點:編譯時注解不支持 Fragment多組件支持

實現
  • 路由映射

    映射關系通過 Router#map 方法添加,Mapping 類記錄了某個 Page 的映射路徑

    初始化,會在第一使用的時候完成,使用 @Module 后的每個模塊內會生成一個 RouterMapping_xxx 的類來初始化當前模塊的路由表,主模塊還需要使用 @Modules 聲明,并會生成一個 RouterInit 類以實現所有模塊的

    主模塊初始化的時候,調用各個模塊初始化自己的路由表

    //APT 生成
    public final class RouterInit {
      public static final void init() {
        RouterMapping_app.map();
        RouterMapping_sdk.map();
      }
    }
    

    app 模塊的路由表初始化

    //APT 生成
    public final class RouterMapping_app {
      public static final void map() {
        //路由表初始化
        //...
        //@Router(value = "home/:homeName")
        com.github.mzule.activityrouter.router.Routers.map("home/:homeName", HomeActivity.class, null, extraTypes);
        //@Router("with_host")
        com.github.mzule.activityrouter.router.Routers.map("with_host", HostActivity.class, null, extraTypes);
        //@Router(value = {"http://mzule.com/main", "main", "home"})
        com.github.mzule.activityrouter.router.Routers.map("http://mzule.com/main", MainActivity.class, null, extraTypes);
        com.github.mzule.activityrouter.router.Routers.map("main", MainActivity.class, null, extraTypes);
        com.github.mzule.activityrouter.router.Routers.map("home", MainActivity.class, null, extraTypes);
        //...
        }
      }
    
    • 啟動頁面

    最后通過 Routers#doOpen 來啟動一個 Activity

    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());
              //...
              if (requestCode >= 0) {
                //...
              } else {
                  context.startActivity(intent);
              }
              return true;
          }
      }
      return false;
    }
    
    • 參數傳遞

    可以通過 url 來傳遞一些基本類型的參數,如果需要傳遞對象類型的數據或者添加 launchFlag,只能通過 Router#resolve 方法來返回一個 Intent對象,這無疑打破的代碼的簡潔和優雅性,可以考慮構造一個新的對象來負責跳轉和跳轉前額外參數的添加職責(后面說的 Rabbit 就更合理且優雅)

    • 具體流程

      ActivityRouter啟動頁面流程.png
    • 類圖

      ActivityRouter類圖

Rabbits

特點:編譯時注解攔截器mappings更新(似乎沒什么用?)多組件支持Fragment 支持(需要自己擴展)

實現
  • page 的映射關系

    根據 mappingsxxx.json 路由表文件(每次初始化的時候都需要讀取 json 文件,耗時會隨著頁面增多而增加),建立 uri = page 之間的聯系 ,模塊內使用了 @Page(name = xxx) 注解的 ActivityFragment,編譯后會生成 Router 類,記錄的模塊內所有 page 的跳轉所需要的目標參數( 目標 Fragment 的實例或者目標 Activityclass ),最后根據 uri 可以得出目標 page 名,再通過動態代理的方法來實現具體方法的反射調用

    mappings 表,指定了路徑、參數和對應 page 名:

    {
    ...
    "mappings": {
    "demo://rabbits.kyleduo.com/test": "TEST",  
    "demo://rabbits.kyleduo.com/test/{testing}": "TEST",  //String 類型參數
    "demo://rabbits.kyleduo.com/second/{id:l}": "SECOND", //long 類型,key 為 id
    "demo://rabbits.kyleduo.com/crazy/{name:s}/{age:i}/{finish:b}/end": "CRAZY"
    }
    }
    

    APT 生成的 Router 類,最后通過反射的方式調用

    public final class Router {
    //...
    public static final SecondFragment routeSecond() {
      return new SecondFragment();
    }
    
    public static final Class routeTest() {
      return TestActivity.class;
    }
    
  • uri 的匹配,先整個 uri 區配

    Mappings.java
    
    static Target match(Uri uri) {
    //...uri 在沒有 scheme 或者 host 的情況下會自動添加默認的
    uri = builder.build();
    Uri pureUri;
    //清除 fragment 和 query 片段
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
      pureUri = builder.fragment(null).query(null).build();
    } else {
      pureUri = builder.fragment(null).clearQuery().build();
    }
    
    // Try to completely match.
    String page = sMappingsGroup.getMappings().get(pureUri.toString());
    Bundle bundle = null;
    if (page == null) {
      // Deep match.
      bundle = new Bundle();
      page = deepMatch(pureUri, bundle);
    }
    bundle = parseParams(uri, bundle);  //query 片段的 key-value 存進 bundle
    //...
    if (page != null && !free) {
      // Match.
      Target target = new Target(uri);
      target.setPage(page);
      target.setExtras(bundle);
      target.setFlags(parseFlags(uri));
      return target;
    }
    }
    

    RESTful 風格的 uri 匹配

    private static String deepMatch(Uri pureUri, Bundle bundle) {
    Set<String> uris = sMappingsGroup.getMappings().keySet();
    String[] source = pureUri.toString().split("(://|/)");
    UriLoop:
    for (String uri : uris) {
      // Check match for each uri.
      String[] template = uri.split("(://|/)");
      if (!template[0].equals(source[0]) || template.length != source.length) {
          continue;
      }
      if (!template[1].equals(source[1]) && (sMappingsGroup.getAllowedHosts() == null || !sMappingsGroup.getAllowedHosts().contains(source[1]))) {
          continue;
      }
      // Compare each part, parse params.
      for (int i = 2; i < source.length; i++) {
          String s = source[i];
          String t = template[i];
          if (t.equals(s)) {
              continue;
          }
          // Check whether a param field.
          if (t.matches("\\{\\S+(:\\S+)?\\}")) {  // {id:l} or {testing}
              try {
                  formatParam(t, s, bundle);//基本參數類型的解析
              } catch (NumberFormatException e) {
                  continue UriLoop;
              }
              continue; //參數都能匹配上才使用該 uri
          }
          continue UriLoop;
      }
      return sMappingsGroup.getMappings().get(uri);
    }
    return null;
    }
    
  • 參數傳遞

    通過 url 來傳參只能支持基本數據類型顯然是不夠用的,還需要提供額外的方式來做更多的處理,包括 intentFlag 的添加,Rabbits#to 方法的產物 AbstractNavigator 對象就可以處理更多的需求

  • 具體流程

    Rabbit頁面啟動.png
  • 類圖

    Rabbit類圖

小結

Rabbit 相對 ActivityRouter 來說功能更多,更具有可擴展性,Rabbit 的路由表需要從 json 文件中解析,這種方式來管理雖然可能使得路由看起來更清晰且可以動態更新,但畢竟是耗時操作,還是有些小顧慮,作者也提供了一個異步的加載方法,但我覺得可以考慮使用 ActivityRouter 的方式來實現更好,純靠 APT 來生成路由映射關系

更多

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容