基于Android平台的RouterSDK设计与实现

MilSoper 8年前
   <p>本文会详细介绍了RouterSDK框架的设计与实践,通过这篇文章不但可以知道Router框架的一些功能,而且还可以提供实现SDK的一些思路。</p>    <h3>背景</h3>    <p>在很多场景中,你可能会遇到以下这些需求:第三方AP启动本AP、网页调起AP、网页启动指定某个页面、某个页面需要带上参数、该参数可以指定类型等等。RouterSDK 很轻松的实现上面的功能,除此之外,RouterSDK还提供了一些实用的功能如动态路由配置、跳转动画、任务站、跳转前处理等等。</p>    <h3>设计</h3>    <p>Android系统正常的跳转方式 A页面 intent 到 B页面,B页面intent C页面,他们之间的联系是很亲密直接的。彼此间的跳转都是相互intent,如下图所示:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a774f97037b7645afbb356342d46c11f.png"></p>    <p>但是这样的通信方式太过直接,导致两个页面交集在一起,这时候想在他们之间做些事情是很难的,并且这样的方式其实不利于解耦。假设每个页面都是独立的个体,他们之间的通信通过一个中转器来处理,然后根据这个中转器来启动另外一个页面。这样我们就可以通过控制中转器按照我们的规则跳转。由于这个中转器类似于路由器功能,所以把它叫做Router。</p>    <p>如下图的模型所示:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/09417507df8fe675dc75882beebcf807.png"></p>    <p>RouterSDK 架构分为三层:核心层解析、业务层处理、API层调用。这种设计模型是比较经典的称盒子模型。底层为核心层(不轻易改变)、中间层为业务层(处理业务逻辑)、外层为API层(提供AP调用)。</p>    <p>核心层解析:解析、bundle组装、匹配路由</p>    <p>业务层处理:路由表操作、处理中断器、跳转、动画等等。</p>    <p>API掉用:三方调用的API</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/99d95d0b0d90f0d7b6029cff7427de98.png"></p>    <p style="text-align:center">洋葱结构图.png</p>    <p>为什么这么设计呢?首先是架构层次分明,通过这三个层次的隔离,从用户的角度来看是很容易使用的,也对外隐藏了业务逻辑层、核心层的实现细节。而核心层定义的子系统抽象,保证了整个微社区SDK的灵活性、扩展性。</p>    <p>第三方AP启动模型</p>    <p>应用内需要有个页面接收第三方的请求,然后通过RouterSDK进行解析uri,转换成相关的Intent,然后再跳转到相关的页面。如图所示。</p>    <p>A页面是程序的一个入口,跳转交给Router处理。这样我们就可以通过发命令来控制跳转的页面了。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a4cf4283d51296f0c5c0d62fb76c60bb.png"></p>    <p style="text-align:center">三方AP启动模型.png</p>    <h3>实现</h3>    <p>首先编写的是MatchParse类,最里面的一层核心层,就是我上面所说的解析和组装,这些我们认为是固定的不轻易改变的,这一层原则上调用者不需要知道具体怎么去做。RouterSDK 将解析uri、拆解参数、组装bundle,最终根据解析的结果以code形式返回上一层。然后在RouterEngine类处理业务相关的,如路由表的增删改查、interceptor的编写、跳转动画的实现等等。这一层可以根据自己需求增强SDK。这样维护和扩展起来就非常的清晰了。最后编写的是API,提供相应的API调用。三方的AP都是通过这层调用SDK的方法和规范入口。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/999cb731c6bc44eb265722deea7c6b4d.png"></p>    <p>Class Diagram.png</p>    <p>MatchParse 解析 uri,根据rule转化成不同的类型的参数,最终转成我们需要的产物bundle。这个规则是约定好的,RouterSDK的规则表如下:</p>    <table>     <thead>      <tr>       <th>key format</th>       <th>{i:ikey}</th>       <th>{f:key}</th>       <th>{l:key}</th>       <th>{d:key}</th>       <th>{s:key}</th>       <th>{b:key}</th>      </tr>     </thead>     <tbody>      <tr>       <td>type</td>       <td>integer</td>       <td>float</td>       <td>long</td>       <td>double</td>       <td>string</td>       <td>boolean</td>      </tr>     </tbody>    </table>    <p>如:jomeslu://www?{i:id}=168&{s:jomeslu}=jomeslu</p>    <ul>     <li>Scheme:通常定义为 应用某个路径</li>     <li>Host: 通常用于区分不同的页面,比如activity</li>     <li>path : 传递参数与参数类型</li>    </ul>    <p>举个例子,jomeslu://www?{i:id}=168&{s:jomeslu}=jomeslu ,SDK 会在路由表查找jomeslu://www,找到对应的Class文件,而{i:id}=168&{s:jomeslu}=jomeslu,根据路由规则转换换成 int id=168 ,String jomeslu = jomslu.然后将这些值换成bundle进行传值。接bundle值跟系统的写法是一样的, int id = getIntent().getIntExtra("id", -1);所以RouterSDK不用关心如何接受值。</p>    <pre>  <code class="language-java">private void setKeyValueBundle(String key, String value, Bundle bundle) throws Exception {          //符合自定义的规则 {s:te}          if (!TextUtils.isEmpty(key) && key.startsWith(RuleConstant.PARAM_SPIT_LEFT) && key.endsWith(RuleConstant.PARAM_SPIT_RIGHT) &&                  key.contains(RuleConstant.PARAM_SPIT_SIGN)) {              String realKey = key.substring(key.indexOf(RuleConstant.PARAM_SPIT_SIGN) + 1, key.lastIndexOf(RuleConstant.PARAM_SPIT_RIGHT));              String type = key.substring(1, key.indexOf(RuleConstant.PARAM_SPIT_SIGN));              switch (type.toUpperCase()) {                  case RuleConstant.PARAM_B:                      Boolean aBoolean = Boolean.valueOf(value);                      bundle.putBoolean(realKey, aBoolean);                      break;                  case RuleConstant.PARAM_D:                      bundle.putDouble(realKey, Double.valueOf(value));                      break;                  case RuleConstant.PARAM_F:                      bundle.putFloat(realKey, Float.valueOf(value));                      break;                  case RuleConstant.PARAM_I:                      bundle.putInt(realKey, Integer.valueOf(value));                      break;                  case RuleConstant.PARAM_L:                      bundle.putLong(realKey, Long.valueOf(value));                      break;                  case RuleConstant.PARAM_S:                      bundle.putString(realKey, value);                      break;              }            } else if (!TextUtils.isEmpty(key)) {              //默认是字符串              bundle.putString(key, value);          }      }</code></pre>    <p>RouterEngine 的 getIntent(Context context) 的方法,这个根据MatchParse解析结果 返回相关的intent。根据不同mathcode 我们就知道底层处理了的结果。以后扩展起来就比较方便了。</p>    <pre>  <code class="language-java">MatchParse mathchParse = new MatchParse(mRouteTableHandle, context);       int mathchCode = mathchParse.mathch(param);       switch (mathchCode) {                   .....                   .....              case RuleConstant.MATHCH_SUCCESS:                  if (param.getmRouterResultCallback() != null) {                      param.getmRouterResultCallback().succeed(param.getUri());                  }                  if (param.getIRouteInterceptor() != null && param.getIRouteInterceptor().interceptor()) {                      return null;                  }                  Intent intent = new Intent(context, param.getClazz());                  intent.putExtras(param.getmBundle());                  if (!(context instanceof Activity)) {                      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                  }                  return intent;                     ...</code></pre>    <p>不一一贴代码了,具体请查看代码。RouterSDK已经开源。在本文已经写出下载地址。</p>    <h3>总结</h3>    <p>本文讲解了RouterSDK 的设计与实现,以及编写SDK的技巧。同时讲解了Router框架的优势以及使用场景。如果有好像的想法请提Issues。</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/c7524b0125df</p>    <p> </p>