1. 程式人生 > >Android路由實現

Android路由實現

本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。

前幾個月有幸參加了CSDN組織的MDCC移動開發者大會, 一天下來我最大的收穫就是了解到了模組化開發, 回來之後我就一直在思考模組化的一些優點, 不說別的, 提供一種可插拔的開發方式就足夠我們興奮一會了~ 接下來自己開始嘗試了一些小demo, 發現在模組化開發中最大的問題就是元件間通訊, 例如: 在模組化架構中, 商城和個人中心分別是兩個獨立的模組, 在開發階段, 個人中心如何想要跳轉商城的某個頁面咋辦? 這裡就需要引入一個路由的概念了. 做過web開發的都知道, 在大部分web框架中url路由也是框架中很重要的組成部分, 如果大家對路由的概念還不是很清楚, 可以先來看一下我這篇

go web開發之url路由設計來了解下路由的概念, 這裡稍稍解釋一下路由就是起到一個轉發的作用.
一張圖來體會一下路由的作用, 因為我本地沒有UML工具, 新的還在下載中… 900M+, 我這網速有點受不了. 所以我選擇KolourPaint手動繪製一張具有魔性的圖片先來體會一下.

自己實現一個路由的動機

那到了我們Android開發中呢? 如果我們把專案模組化了, 那兩個元件間進行通訊或者跳轉, 我們一般構建Intent的方式就不再使用了, 很簡單, 因為在模組A中根本找不到模組B中的C類, 這就需要我們自定義路由規則, 繞一道彎去進行跳轉, 說白了就是給你的類起一個別名, 我們用別用去引用. 其實在我準備自己去實現一個路由的時候我是google了一些解決方案的, 這些方案大致可分為兩種.

  1. 完全自己實現路由, 完全封裝跳轉引數
  2. 利用隱式意圖跳轉

對於這兩種方式我總結了一下, 個人認為第一種方式封裝的太多, 甚至有些框架是RESTFul like的, 這樣的封裝一是學習成本太高, 二是舊專案改動起來太麻煩. 那第二種方式呢? 利用隱式意圖是一種不錯的選擇, 而且Android原生支援, 這也是大家在嘗試模組化開發時的一個選擇, 不過這種方式僅支援Activity, Service, BroadcastReceiver, 擴充套件性太差. 綜上因素, 我還是決定自己實現一個路由, 參考自上面的侷限性, 我們的路由具有一下2個特點.

  1. 上手簡單, 目標是與原生方式一行程式碼之差就能實現Activity, Service, BroadcastReceiver呼叫.
  2. 擴充套件性強, 開發者可以任意新增自己的路由實現, 不僅僅侷限於Activity, Service, BroadcastReceiver.

體驗一下

在瞭解具體實現程式碼之前, 我們先來了解一下新的路由怎麼使用, 使用起來是不是符合上面兩點, 首先我們先建立三個moduler, 分別是殼app, 商城模組shoplib, bbs模組bbslib. app模組就是我們的殼了, 我們需要利用app模組去打包, 而且app也是依賴shoplib和bbslib的, 所以我們可以在app的application裡進行路由的註冊.

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

這裡註冊了兩個路由, 分別是商城模組的的ShopActivity和bbs模組的BBSActivity, 它們都是通過Router類的靜態方法router方法進行註冊的, 兩個引數, 第一個引數是路由地址(也可以理解成別名), 第二個引數對應的類. 註冊完了, 那接下來就是如何使用了, 我們來看看在商城模組如何跳轉BBS模組吧.

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

主要程式碼是在click事件裡, 我們呼叫了Router.invoke方法, 第一個引數是當前Activity, 第二個引數就是我們前面註冊的路由了, 這裡都很好理解, 關鍵是看它的返回值, 這裡直接返回了一個Intent, 這一點是最棒的~ 返回Intent也就是說明下面的程式碼和我們使用原生方式沒有任何區別! 這一點符合上面我們說到的上手簡單的目的.

至於第二點目標, 高擴充套件性, 大家可以實現Rule介面自定義路由Rule, 然後呼叫Router.addRule(String scheme, Rule rule)方法進行路由規則的註冊. Rule介面的定義如下,

/**
 * 路由規則介面<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);
}

解釋一下, 首先是Rule介面的兩個範型, 第一個T是我們註冊的路由型別, 例如前面使用的Activity型別, 第二個V是invoke方法的返回值型別, 例如前面使用的Intent型別.至於自定義的程式碼, 這裡我賴了~, 沒有提供demo~~~ 大家可以嘗試自定義一下.

路由實現程式碼

接下來我們開始進入實現程式碼環節~ 在來是程式碼之前, 還是先來一張圖瞭解下這個Router的結構.

帶著上面的圖片, 我們來看程式碼, 首先我們來看看Router類, 畢竟我們在使用的時候都是在和Router打交道.

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

哈, Router的程式碼很簡單, 主要就是起到一個類似靜態代理的作用, 主要的程式碼還是在RouterInternal裡, 那來看看RouterInternal的結構吧.

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

首先RouterInternal是一個單例, 一個mRules變數用來儲存我們的路由規則, 在構造中我們註冊了三個預設的路由規則, 這三個路由規則想都不用想就知道是Activity, Service和BroadcastReceiver的. 接下來看看其他的方法.

/**
 * 新增自定義路由規則
 * @param scheme 路由scheme
 * @param rule 路由規則
 * @return {@code RouterInternal} Router真實呼叫類
 */
public final RouterInternal addRule(String scheme, Rule rule) {
    mRules.put(scheme, rule);
    return this;
}

addRule方法是新增路由規則的實現, 這裡我們是直接向mRules這個HashMap中新增的.

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

getRule的作用是根據pattern來獲取規則, 這是一個私有的方法, 所以在使用的時候不需要關心, 它的原理很簡單, 就是根據你的pattern來匹配 scheme來獲取對應的Rule.

/**
 * 新增路由
 * @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;
}

這個router方法就是我們新增路由的實現了, 首先我們根據路由的uri來獲取對應的Rule, 然後呼叫該Rulerouter方法, 至於Rule.router方法如何實現的, 我們稍後看~

/**
 * 路由呼叫
 * @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);
}

invoke方法就是我們呼叫的時候執行的程式碼的, 返回值T是返回的Rule範型中指定的型別, 例如前面的Intent.
綜上程式碼, 我們發現RouterInternal其實就是一個管理Rule的類, 具體的呼叫還是在各個Rule中實現, 上面提到過, Rule是一個介面, 它具有兩個範型, 分別對應的呼叫 invoke的返回值型別和我們要路由的類的型別. 解析來我們就來看看預設的幾個路由規則是如何實現的.

對於Activity, Service, BroadcastReceiver的呼叫, 總結了一下, 它們其實都是返回的Intent型別, 所以我們可以先構建一個指定返回值是Intent的Base型別.

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

router方法不多說, 還是向Map中新增鍵值對, invoke方法, 我們通過引數中的patternmIntentRules目標類, 然後構建一個Intent返回, 最後一個throwException是一個抽象方法, 用來在呼叫沒有router的類時丟擲異常用~, 可以發現, 其實大部分的實現在這裡都實現了, 對於Activity繼承這個BaseIntentRule,並且指定要路由類的型別是Activity, 並且實現throwException方法就可以了.

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

ActivityRule首先繼承了BaseIntentRule並指定了範型是Activity, 實現的throwException方法也很簡單, 就是丟擲了一個ActivityNotRouteException異常, 對於這個異常, 大家可以在文章最後的原始碼下載部分找到~ 看完ActivityRule的實現, 其實其他兩個預設Rule的實現都一樣了~ 大家也是自己去看程式碼吧.

其實實現一個路由很簡單, 原理就是給我們要路由的類定義一個別名, 然後在呼叫的地方通過別名去呼叫. 而且在封裝的時候儘量要符合現在使用者的使用習慣, 不要過多的封裝而忽略了使用者的感受.

好了, 這篇文章就到這裡了, 文章中的程式碼大家可以到https://github.com/qibin0506/Module2Module一個模組化開發的小demo中找到~