Android路由实现

vcrs0792 7年前
   <p>好了, 下面进入今天的主题, 前几个月有幸参加了CSDN组织的MDCC移动开发者大会, 一天下来我最大的收获就是了解到了模块化开发, 回来之后我就一直在思考模块化的一些优点, 不说别的, 提供一种可插拔的开发方式就足够我们兴奋一会了~ 接下来自己开始尝试了一些小demo, 发现在模块化开发中最大的问题就是组件间通讯, 例如: 在模块化架构中, 商城和个人中心分别是两个独立的模块, 在开发阶段, 个人中心如何想要跳转商城的某个页面咋办? 这里就需要引入一个路由的概念了. 做过web开发的都知道, 在大部分web框架中url路由也是框架中很重要的组成部分, 如果大家对路由的概念还不是很清楚, 可以先来看一下我这篇 go web开发之url路由设计来了解下路由的概念, 这里稍稍解释一下路由就是起到一个转发的作用.</p>    <p>一张图来体会一下路由的作用, 因为我本地没有UML工具, 新的还在下载中… 900M+, 我这网速有点受不了. 所以我选择KolourPaint手动绘制一张具有魔性的图片先来体会一下.</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/33a959b672bd2e761c69787e84cf7c55.png"></p>    <h2><strong>自己实现一个路由的动机</strong></h2>    <p>那到了我们Android开发中呢? 如果我们把项目模块化了, 那两个组件间进行通讯或者跳转, 我们一般构建Intent的方式就不再使用了, 很简单, 因为在模块A中根本找不到模块B中的C类, 这就需要我们自定义路由规则, 绕一道弯去进行跳转, 说白了就是给你的类起一个别名, 我们用别用去引用. 其实在我准备自己去实现一个路由的时候我是google了一些解决方案的, 这些方案大致可分为两种.</p>    <ol>     <li>完全自己实现路由, 完全封装跳转参数</li>     <li>利用隐式意图跳转</li>    </ol>    <p>对于这两种方式我总结了一下, 个人认为第一种方式封装的太多, 甚至有些框架是RESTFul like的, 这样的封装一是学习成本太高, 二是旧项目改动起来太麻烦. 那第二种方式呢? 利用隐式意图是一种不错的选择, 而且Android原生支持, 这也是大家在尝试模块化开发时的一个选择, 不过这种方式仅支持Activity, Service, BroadcastReceiver, 扩展性太差. 综上因素, 我还是决定自己实现一个路由, 参考自上面的局限性, 我们的路由具有一下2个特点.</p>    <ol>     <li>上手简单, 目标是与原生方式一行代码之差就能实现Activity, Service, BroadcastReceiver调用.</li>     <li>扩展性强, 开发者可以任意添加自己的路由实现, 不仅仅局限于Activity, Service, BroadcastReceiver.</li>    </ol>    <h2><strong>体验一下</strong></h2>    <p>在了解具体实现代码之前, 我们先来了解一下新的路由怎么使用, 使用起来是不是符合上面两点, 首先我们先建立三个moduler, 分别是壳app, 商城模块shoplib, bbs模块bbslib. app模块就是我们的壳了, 我们需要利用app模块去打包, 而且app也是依赖shoplib和bbslib的, 所以我们可以在app的application里进行路由的注册.</p>    <pre>  <code class="language-java">public class App extends Application {        @Override      public void onCreate() {          super.onCreate();          setupRouter();      }        private void setupRouter() {          Router.router(ActivityRule.ACTIVITY_SCHEME + "shop.main", ShopActivity.class);          Router.router(ActivityRule.ACTIVITY_SCHEME + "bbs.main", BBSActivity.class);      }  }</code></pre>    <p>这里注册了两个路由, 分别是商城模块的的ShopActivity和bbs模块的BBSActivity, 它们都是通过 Router 类的静态方法 router 方法进行注册的, 两个参数, 第一个参数是路由地址(也可以理解成别名), 第二个参数对应的类. 注册完了, 那接下来就是如何使用了, 我们来看看在商城模块如何跳转BBS模块吧.</p>    <pre>  <code class="language-java">public class ShopActivity extends AppCompatActivity {        @Override      protected void onCreate(@Nullable Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          TextView tv = new TextView(this);          tv.setTextSize(50);          tv.setText("SHOP!!!");          setContentView(tv);            tv.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  Intent it = Router.invoke(ShopActivity.this, ActivityRule.ACTIVITY_SCHEME + "bbs.main");                  startActivity(it);              }          });      }  }</code></pre>    <p>主要代码是在click事件里, 我们调用了 Router.invoke 方法, 第一个参数是当前Activity, 第二个参数就是我们前面注册的路由了, 这里都很好理解, 关键是看它的返回值, 这里直接返回了一个Intent, 这一点是最棒的~ 返回Intent也就是说明下面的代码和我们使用原生方式没有任何区别! 这一点符合上面我们说到的 <strong>上手简单</strong> 的目的.</p>    <p>至于第二点目标, <strong>高扩展性</strong> , 大家可以实现 Rule 接口自定义路由Rule, 然后调用 Router.addRule(String scheme, Rule rule) 方法进行路由规则的注册. Rule接口的定义如下,</p>    <pre>  <code class="language-java">/**   * 路由规则接口<br/>   * Created by qibin on 2016/10/8.   */    public interface Rule<T, V> {      /**       * 添加路由       * @param pattern 路由uri       * @param klass 路由class       */      void router(String pattern, Class<T> klass);        /**       * 路由调用       * @param ctx Context       * @param pattern 路由uri       * @return {@code V} 返回对应的返回值       */      V invoke(Context ctx, String pattern);  }</code></pre>    <p>解释一下, 首先是Rule接口的两个范型, 第一个T是我们注册的路由类型, 例如前面使用的Activity类型, 第二个V是 invoke 方法的返回值类型, 例如前面使用的Intent类型.至于自定义的代码, 这里我赖了~, 没有提供demo~~~ 大家可以尝试自定义一下.</p>    <h2><strong>路由实现代码</strong></h2>    <p>接下来我们开始进入实现代码环节~ 在来是代码之前, 还是先来一张图了解下这个 Router 的结构.</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f96e6ec3df72613193ce10f91bac1a95.png"></p>    <p>带着上面的图片, 我们来看代码, 首先我们来看看Router类, 毕竟我们在使用的时候都是在和Router打交道.</p>    <pre>  <code class="language-java">/**   * Usage: <br />   * <pre>   * step 1. 调用Router.router方法添加路由   * step 2. 调用Router.invoke方法根据pattern调用路由   * </pre>   * Created by qibin on 2016/10/9.   */    public class Router {        /**       * 添加自定义路由规则       * @param scheme 路由scheme       * @param rule 路由规则       * @return {@code RouterInternal} Router真实调用类       */      public static RouterInternal addRule(String scheme, Rule rule) {          RouterInternal router = RouterInternal.get();          router.addRule(scheme, rule);          return router;      }        /**       * 添加路由       * @param pattern 路由uri       * @param klass 路由class       * @return {@code RouterInternal} Router真实调用类       */      public static <T> RouterInternal router(String pattern, Class<T> klass) {          return RouterInternal.get().router(pattern, klass);      }        /**       * 路由调用       * @param ctx Context       * @param pattern 路由uri       * @return {@code V} 返回对应的返回值       */      public static <V> V invoke(Context ctx, String pattern) {          return RouterInternal.get().invoke(ctx, pattern);      }  }</code></pre>    <p>哈, Router的代码很简单, 主要就是起到一个类似静态代理的作用, 主要的代码还是在 RouterInternal 里, 那来看看 RouterInternal 的结构吧.</p>    <pre>  <code class="language-java">public class RouterInternal {        private static RouterInternal sInstance;        /** scheme->路由规则 */      private HashMap<String, Rule> mRules;        private RouterInternal() {          mRules = new HashMap<>();          initDefaultRouter();      }        /**       * 添加默认的Activity,Service,Receiver路由       */      private void initDefaultRouter() {          addRule(ActivityRule.ACTIVITY_SCHEME, new ActivityRule());          addRule(ServiceRule.SERVICE_SCHEME, new ServiceRule());          addRule(ReceiverRule.RECEIVER_SCHEME, new ReceiverRule());      }        /*package */ static RouterInternal get() {          if (sInstance == null) {              synchronized (RouterInternal.class) {                  if (sInstance == null) {                      sInstance = new RouterInternal();                  }              }          }            return sInstance;      }  }</code></pre>    <p>首先 RouterInternal 是一个单例, 一个 mRules 变量用来保存我们的路由规则, 在构造中我们注册了三个默认的路由规则, 这三个路由规则想都不用想就知道是Activity, Service和BroadcastReceiver的. 接下来看看其他的方法.</p>    <pre>  <code class="language-java">/**   * 添加自定义路由规则   * @param scheme 路由scheme   * @param rule 路由规则   * @return {@code RouterInternal} Router真实调用类   */  public final RouterInternal addRule(String scheme, Rule rule) {      mRules.put(scheme, rule);      return this;  }</code></pre>    <p>addRule 方法是添加路由规则的实现, 这里我们是直接向 mRules 这个 HashMap 中添加的.</p>    <pre>  <code class="language-java">private <T, V> Rule<T, V> getRule(String pattern) {      HashMap<String, Rule> rules = mRules;      Set<String> keySet = rules.keySet();      Rule<T, V> rule = null;      for (String scheme : keySet) {          if (pattern.startsWith(scheme)) {              rule = rules.get(scheme);              break;          }      }        return rule;  }</code></pre>    <p>getRule 的作用是根据 pattern 来获取规则, 这是一个私有的方法, 所以在使用的时候不需要关心, 它的原理很简单, 就是根据你的 pattern 来匹配 scheme 来获取对应的 Rule .</p>    <pre>  <code class="language-java">/**   * 添加路由   * @param pattern 路由uri   * @param klass 路由class   * @return {@code RouterInternal} Router真实调用类   */  public final <T> RouterInternal router(String pattern, Class<T> klass) {      Rule<T, ?> rule = getRule(pattern);      if (rule == null) {          throw new NotRouteException("unknown", pattern);      }        rule.router(pattern, klass);      return this;  }</code></pre>    <p>这个 router 方法就是我们添加路由的实现了, 首先我们根据路由的uri来获取对应的 Rule , 然后调用该 Rule 的 router 方法, 至于 Rule.router 方法如何实现的, 我们稍后看~</p>    <pre>  <code class="language-java">/**   * 路由调用   * @param ctx Context   * @param pattern 路由uri   * @return {@code V} 返回对应的返回值   */  /*package*/ final <V> V invoke(Context ctx, String pattern) {      Rule<?, V> rule = getRule(pattern);      if (rule == null) {          throw new NotRouteException("unknown", pattern);      }        return rule.invoke(ctx, pattern);  }</code></pre>    <p>invoke 方法就是我们调用的时候执行的代码的, 返回值 T 是返回的 Rule 范型中指定的类型, 例如前面的 Intent .</p>    <p>综上代码, 我们发现 RouterInternal 其实就是一个管理 Rule 的类, 具体的调用还是在各个 Rule 中实现, 上面提到过, Rule 是一个接口, 它具有两个范型, 分别对应的调用 invoke 的返回值类型和我们要路由的类的类型. 解析来我们就来看看默认的几个路由规则是如何实现的.</p>    <p>对于Activity, Service, BroadcastReceiver的调用, 总结了一下, 它们其实都是返回的 Intent 类型, 所以我们可以先构建一个指定返回值是 Intent 的Base类型.</p>    <pre>  <code class="language-java">/**   * 返回Intent的路由规则的基类<br />   * Created by qibin on 2016/10/9.   */    public abstract class BaseIntentRule<T> implements Rule<T, Intent> {        private HashMap<String, Class<T>> mIntentRules;        public BaseIntentRule() {          mIntentRules = new HashMap<>();      }        /**       * {@inheritDoc}       */      @Override      public void router(String pattern, Class<T> klass) {          mIntentRules.put(pattern, klass);      }        /**       * {@inheritDoc}       */      @Override      public Intent invoke(Context ctx, String pattern) {          Class<T> klass = mIntentRules.get(pattern);          if (klass == null) { throwException(pattern);}          return new Intent(ctx, klass);      }        /**       * 当找不到路由规则时抛出异常       * @param pattern 路由pattern       */      public abstract void throwException(String pattern);  }</code></pre>    <p>router 方法不多说, 还是向 Map 中添加键值对, invoke 方法, 我们通过参数中的 pattern 从 mIntentRules 目标类, 然后构建一个 Intent 返回, 最后一个 throwException 是一个抽象方法, 用来在调用没有 router 的类时抛出异常用~, 可以发现, 其实大部分的实现在这里都实现了, 对于Activity继承这个 BaseIntentRule ,并且指定要路由类的类型是Activity, 并且实现 throwException 方法就可以了.</p>    <pre>  <code class="language-java">/**   * activity路由规则<br />   * Created by qibin on 2016/10/8.   */    public class ActivityRule extends BaseIntentRule<Activity> {        /** activity路由scheme*/      public static final String ACTIVITY_SCHEME = "activity://";        /**       * {@inheritDoc}       */      @Override      public void throwException(String pattern) {          throw new ActivityNotRouteException(pattern);      }  }</code></pre>    <p>ActivityRule 首先继承了 BaseIntentRule 并指定了范型是 Activity , 实现的 throwException 方法也很简单, 就是抛出了一个 ActivityNotRouteException 异常, 对于这个异常, 大家可以在文章最后的源码下载部分找到~ 看完 ActivityRule 的实现, 其实其他两个默认 Rule 的实现都一样了~ 大家也是自己去看代码吧.</p>    <p>其实实现一个路由很简单, 原理就是给我们要路由的类定义一个别名, 然后在调用的地方通过别名去调用. 而且在封装的时候尽量要符合现在用户的使用习惯, 不要过多的封装而忽略了使用者的感受.</p>    <p> </p>    <p>来自:http://blog.csdn.net/qibin0506/article/details/53373412</p>    <p> </p>