动态导航总线 Phoenix – URL Router

在我们 Android 开发过程中,在做页面跳转的时候,一般情况下都是写死了代码逻辑,比如 startActivity(new Intent(context, SomeActivity.class)) 或者使用 scheme URL 方式隐性 Intent 跳转,无论如何,一旦我们写完了代码发布出去后,便无法更改跳转逻辑了,而且传统的 Class 跳转将强依赖目标类文件,造成了模块化开发的困难。

如果这时候某个页面出现了巨大的 bug,我们无法在不更新程序的情况下,动态将它转向到一个专门的 error 页面或者某个临时替代的 H5 Web 页面。

即 A -> B 是一个既定的关系,为了达到动态化,必然需要对整个 App 的导航进行统一处理,经过路由中心分发,这就是 URL Router 要做的事情。

Phoenix-URLRouter 便是这样一个 Android 平台上的 URL Router,实现了路由规则的动态可配置性,AOP,以及方便的参数、数据传递。

特性

  • 实现 AOP (面向切面编程), 能够在 URI 变换前后插入拦截器(Interceptor)
  • 拦截器将能够决定 Chain 是否继续、中止或转向
  • 能够随意更改诸如 drakeet.app/home 的映射:
    • 使用诸如 drakeet.app/home 能打开 Home 页
    • drakeet.app/home 不存在映射时, 不做任何响应, 但可被拦截
    • 使用诸如 http://drakeet.app/home 能打开原生 Home 页, 当映射不存在的时候, 能够使用 web 页打开 URL
    • 使用诸如 cn://drakeet.app/home 能打开 Home 页
    • 使用诸如 cn://drakeet.app/home 能映射到 Error 页, 或做错误处理
    • 使用诸如 https://drakeet.app/home?user_id=drakeet 能跳转到 http://xxx.com/xxx?user_id_drakeet
  • 能够携带 Parcelable / Serializable / Bundle 数据
  • 能够设置出错回调
  • 支持 class Scheme URL 作为 target
  • 完备的 Espresso / 单元测试

思考

  • 拦截器是否需要支持修改、新增携带的数据(区别于参数)
  • 能够设置跳转白名单(不必要,跳转的目标原本就是在被限定的范围)
  • 是否要支持服务端插入新参数, 将决定:
    • 使用诸如 cn://drakeet.app/home 能变成 cn://drakeet.app/home?show_dialog=xxx 从而能击中预设的拦截规则, 弹出对话框 Dialog(考虑到纯粹性和安全性,已放弃)

使用

全局初始化
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);
    }
}
  • addRequestInterceptor 插入请求拦截器
  • addTargetInterceptor 插入结果拦截器
  • addHttpUrlNotFoundObserver 对于 HTTP URL 如果没有找到映射目标,则交给此 Observer 对象
  • Phoenix.apply 动态配置路由规则接口
携带参数跳转

m.drakeet.me/home 映射 cp://m.drakeet.com/home scheme

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();
}
携带参数 & 数据跳转

m.drakeet.me/home 映射 cp://m.drakeet.com/home scheme

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();
}
映射到某个 Activity.class – 已废弃

由于考虑到拦截器统一性参数类型, 并结合 Class 的必要性和其带来的弊端, 如对于 Class 文件的耦合, 废弃了此接口

https://m.drakeet.me/home_by_class 映射 HomeActivity.class

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();
}

示例: 消息中心

/**
 * 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();
}

Phoenix APIs

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();
}
后话

这个 URL Router 目前大致思想和设计就是这样了,暂没有开源,目前它被用于菜鸟 SDK 中 native 页面和 Weex 的导航中转,还处以初期阶段,有待完善。

  1. Pingback: How to rank higher in google search?

  2. Pingback: photography classes

  3. Pingback: venus factor

  4. Pingback: seo agency

  5. Pingback: Find Businesses in Australia

  6. Pingback: Internet Services in Australia

  7. Pingback: Arts & Crafts Retailers in Armidale , New South Wales , Australia

  8. Pingback: steroids online legit

  9. “如果这时候某个页面出现了巨大的 bug,我们无法在不更新程序的情况下,动态将它转向到一个专门的 error 页面或者某个临时替代的 H5 Web 页面。”这里的 “某个页面” 是 原生的还是?

  10. Pingback: Best Newspaper in India

  11. Pingback: xtreme article rewriter

  12. Pingback: perth fraudster

  13. Pingback: melbourne fraudster

  14. Pingback: economics tuition

  15. Pingback: economics tuition

  16. Pingback: joseph shihara rukshan de saram

  17. Pingback: joe de saram

  18. Pingback: joseph s r de saram

  19. Pingback: bio-ethanol haard kopen

  20. Pingback: testo c for sale

  21. Pingback: computer reparatie Aalten

  22. Pingback: humulin r for bodybuilding

  23. Pingback: kjole

  24. Pingback: arnaque

  25. 问一下:一个URI是如何绑定到对应的Activity上的?看上面代码应该是使用PageBinder.bind("home", "cn://m.drakeet.me/home");建立映射关系,但是如何映射到Activity上?求解惑。

  26. Pingback: steroid usage

  27. Pingback: http://britlock.com.au

  28. Pingback: hire an attorney

  29. Pingback: jason statham bodybuilding

  30. Pingback: http://www.godwinsremovals.co.uk/international-removals/northern-ireland

  31. Pingback: Vehicle Batteries

  32. Pingback: winstrol injections for sale

  33. Pingback: apps

  34. Pingback: android apps

  35. Pingback: 100 layer challenge

  36. Pingback: helpful resources

  37. Pingback: Skrota bilen

  38. Pingback: Skrota bilen

  39. Pingback: Bilskrot Göteborg

  40. Pingback: trumpforchildren

  41. Pingback: structural jacking and bracing

  42. Pingback: Link vao M88