本文转自: http://blog.csdn.net/qibin0506/article/details/53373412java
前几个月有幸参加了CSDN组织的MDCC移动开发者大会, 一天下来我最大的收获就是了解到了模块化开发, 回来以后我就一直在思考模块化的一些优势, 不说别的, 提供一种可插拔的开发方式就足够咱们兴奋一会了~ 接下来本身开始尝试了一些小demo, 发如今模块化开发中最大的问题就是组件间通信, 例如: 在模块化架构中, 商城和我的中心分别是两个独立的模块, 在开发阶段, 我的中心如何想要跳转商城的某个页面咋办? 这里就须要引入一个路由的概念了. 作过web开发的都知道, 在大部分web框架中url路由也是框架中很重要的组成部分, 若是你们对路由的概念还不是很清楚, 能够先来看一下我这篇go web开发之url路由设计来了解下路由的概念, 这里稍稍解释一下路由就是起到一个转发的做用.
一张图来体会一下路由的做用, 由于我本地没有UML工具, 新的还在下载中… 900M+, 我这网速有点受不了. 因此我选择KolourPaint手动绘制一张具备魔性的图片先来体会一下.git
那到了咱们Android开发中呢? 若是咱们把项目模块化了, 那两个组件间进行通信或者跳转, 咱们通常构建Intent的方式就再也不使用了, 很简单, 由于在模块A中根本找不到模块B中的C类, 这就须要咱们自定义路由规则, 绕一道弯去进行跳转, 说白了就是给你的类起一个别名, 咱们用别用去引用. 其实在我准备本身去实现一个路由的时候我是google了一些解决方案的, 这些方案大体可分为两种.github
- 彻底本身实现路由, 彻底封装跳转参数
- 利用隐式意图跳转
对于这两种方式我总结了一下, 我的认为第一种方式封装的太多, 甚至有些框架是RESTFul like的, 这样的封装一是学习成本过高, 二是旧项目改动起来太麻烦. 那第二种方式呢? 利用隐式意图是一种不错的选择, 并且Android原生支持, 这也是你们在尝试模块化开发时的一个选择, 不过这种方式仅支持Activity, Service, BroadcastReceiver, 扩展性太差. 综上因素, 我仍是决定本身实现一个路由, 参考自上面的局限性, 咱们的路由具备一下2个特色.web
- 上手简单, 目标是与原生方式一行代码之差就能实现Activity, Service, BroadcastReceiver调用.
- 扩展性强, 开发者能够任意添加本身的路由实现, 不只仅局限于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模块吧.app
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接口的定义以下,ide
/** * 路由规则接口<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
, 而后调用该Rule
的 router
方法, 至于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
方法, 咱们经过参数中的pattern
从mIntentRules
目标类, 而后构建一个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中找到~