动态导航总线 Phoenix – URL Router

VicSleigh 8年前
   <p>在我们 Android 开发过程中,在做页面跳转的时候,一般情况下都是写死了代码逻辑,比如 startActivity(new Intent(context, SomeActivity.class)) 或者使用 scheme URL 方式隐性 Intent 跳转,无论如何,一旦我们写完了代码发布出去后,便无法更改跳转逻辑了,而且传统的 Class 跳转将强依赖目标类文件,造成了模块化开发的困难。</p>    <p>如果这时候某个页面出现了巨大的 bug,我们无法在不更新程序的情况下,动态将它转向到一个专门的 error 页面或者某个临时替代的 H5 Web 页面。</p>    <p>即 A -> B 是一个既定的关系,为了达到动态化,必然需要对整个 App 的导航进行统一处理,经过路由中心分发,这就是 URL Router 要做的事情。</p>    <p>Phoenix-URLRouter 便是这样一个 Android 平台上的 URL Router,实现了路由规则的动态可配置性,AOP,以及方便的参数、数据传递。</p>    <p><img src="https://simg.open-open.com/show/006742770a888f1aba1174a0a7772ec9.jpg"></p>    <h2><strong>特性</strong></h2>    <ul>     <li>实现 AOP (面向切面编程), 能够在 URI 变换前后插入拦截器( Interceptor )</li>     <li>拦截器将能够决定 Chain 是否继续、中止或转向</li>     <li>能够随意更改诸如 drakeet.app/home 的映射:      <ul>       <li>使用诸如 drakeet.app/home 能打开 Home 页</li>       <li>当 drakeet.app/home 不存在映射时, 不做任何响应, 但可被拦截</li>       <li>使用诸如 http://drakeet.app/home 能打开原生 Home 页, 当映射不存在的时候, 能够使用 web 页打开 URL</li>       <li>使用诸如 cn://drakeet.app/home 能打开 Home 页</li>       <li>使用诸如 cn://drakeet.app/home 能映射到 Error 页, 或做错误处理</li>       <li>使用诸如 https://drakeet.app/home?user_id=drakeet 能跳转到 http://xxx.com/xxx?user_id_drakeet</li>      </ul> </li>     <li>能够携带 Parcelable / Serializable / Bundle 数据</li>     <li>能够设置出错回调</li>     <li>支持 class 和 Scheme URL 作为 target</li>     <li>完备的 Espresso  / 单元测试</li>    </ul>    <h2><strong>思考</strong></h2>    <ul>     <li>拦截器是否需要支持修改、新增携带的数据(区别于参数)</li>     <li>能够设置跳转白名单(不必要,跳转的目标原本就是在被限定的范围)</li>     <li>是否要支持服务端插入新参数, 将决定:      <ul>       <li>使用诸如 cn://drakeet.app/home 能变成 cn://drakeet.app/home?show_dialog=xxx 从而能击中预设的拦截规则, 弹出对话框 Dialog (考虑到纯粹性和安全性,已放弃)</li>      </ul> </li>    </ul>    <h2><strong>使用</strong></h2>    <p><strong>全局初始化</strong></p>    <pre>  <code class="language-java">public class App extends Application {        @Override public void onCreate() {          super.onCreate();          CrashWoodpecker.flyTo(this);            Map<String, String> url2pageMap = new HashMap<>();          url2pageMap.put("m.drakeet.me/home", "home");          url2pageMap.put("m.drakeet.me/web", "web");            PageBinder.bind("home", "cn://m.drakeet.me/home");          PageBinder.bind("web", "cp://m.drakeet.me/web");            Phoenix.install(this)              .addRequestInterceptor(new LogInterceptor("Request"))              .addTargetInterceptor(new LogInterceptor("Target"))              .addHttpUrlNotFoundObserver(new HttpUrlObserver())              .addErrorObserver(new OnErrorObserver());          Phoenix.apply(this, url2pageMap);      }  }</code></pre>    <ul>     <li>addRequestInterceptor 插入请求拦截器</li>     <li>addTargetInterceptor 插入结果拦截器</li>     <li>addHttpUrlNotFoundObserver 对于 HTTP URL 如果没有找到映射目标,则交给此 Observer 对象</li>     <li>Phoenix.apply 动态配置路由规则接口</li>    </ul>    <p><strong>携带参数跳转</strong></p>    <p>m.drakeet.me/home 映射 cp://m.drakeet.com/home scheme</p>    <pre>  <code class="language-java">public void onStartHomeWithParams(View view) {      Phoenix.navigation()          .navigate(this, "m.drakeet.me/home")          .appendQueryParameter("tag", "just_params")          .appendQueryParameter("tab", "delivery")          .appendQueryParameter("user_id", "drakeet")          .start();  }</code></pre>    <p><strong>携带参数 & 数据跳转</strong></p>    <p>m.drakeet.me/home 映射 cp://m.drakeet.com/home scheme</p>    <pre>  <code class="language-java">public void onStartHomeWithExtraDataAndParams(View view) {      /* A java bean implements Parcelable */      Mail mail = new Mail();      mail.name = "A gift from drakeet";      mail.from = "drakeet";      mail.to = "Xiaoai";        Phoenix.navigation()          .navigate(this, "m.drakeet.me/home")          .appendQueryParameter("tab", "delivery")          .appendQueryParameter("user_id", "drakeet")          .thenExtra() // 转向到数据配置          .putParcelable(KEY_MAIL, mail)          .start();  }</code></pre>    <p> </p>    <p><strong>映射到某个 Activity.class – 已废弃</strong></p>    <p>由于考虑到拦截器统一性参数类型, 并结合 Class 的必要性和其带来的弊端, 如对于 Class 文件的耦合, 废弃了此接口</p>    <p>https://m.drakeet.me/home_by_class 映射 HomeActivity.class</p>    <pre>  <code class="language-java">public void onStartClassHome(View view) {      Phoenix.navigation()          .navigate(this, "https://m.drakeet.me/home_by_class")          .appendQueryParameter("tag", "just_params")          .appendQueryParameter("tab", "delivery")          .appendQueryParameter("user_id", "drakeet")          .start();  }</code></pre>    <h2><strong>示例: 消息中心</strong></h2>    <pre>  <code class="language-java">/**   * http://m.drakeet.com/order_detail -> m.drakeet.com/order_detail   * -> cp://drakeet.sdk/target -> TargetActivity(order_detail)   */  public void onMessageToDetailClick(View view) {      Order order = new Order(123456);      Bundle bundle = new Bundle();      data.putString("xxxKey", "xxxValue");        Phoenix.navigation()          .navigate(this, "http://m.drakeet.com/home")          .appendQueryParameter("xxx", "sss")          .appendQueryParameter("user_id", "drakeet")          .thenExtra()          .putSerializable("key_order", order)          .putExtras(bundle)          .start();  }    /**   * https://m.drakeet.com/post -> m.drakeet.com/post   * -> cp://drakeet.sdk/target -> TargetActivity(punish)   */  public void onMessageToPunishClick(View view) {      Phoenix.navigation()          .navigate(this, "https://m.drakeet.com/post")          .appendQueryParameter("user_id", "xiaoai")          .thenExtra()          .start();  }</code></pre>    <h2><strong>Phoenix APIs</strong></h2>    <pre>  <code class="language-java">public interface Navigation {        interface Encode {          Encode appendQueryParameter(String key, String value);          Extra thenExtra();          void start();      }        interface Extra {           Extra putParcelable(String key, Parcelable value);          Extra putSerializable(String key, Serializable value);          Extra putExtras(Bundle bundle);          void start();      }        Encode navigate(Context context, String baseUrl);      Encode navigateWithFlag(Context context, String baseUrl, int flag);      void start();  }</code></pre>    <h2><strong>后话</strong></h2>    <p>这个 URL Router 目前大致思想和设计就是这样了,一开始也只是听说要做这么一个东西,没有任何借鉴和参考,完全一人花费了两三天独立设计和开发完成,暂没有开源,目前它被用于菜鸟 SDK 中 native 页面和 Weex 的导航中转。</p>    <p> </p>    <p> </p>    <p>来自:https://drakeet.me/phoenix-url-router</p>    <p> </p>