动态导航总线 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>