動態導航總線 Phoenix – URL Router

VicSleigh 8年前發布 | 5K 次閱讀 URL 安卓開發 Android開發 移動開發

在我們 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 的導航中轉。

 

 

來自:https://drakeet.me/phoenix-url-router

 

 本文由用戶 VicSleigh 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!