Android路由实现

本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发。

号外!号外! 我正在参加CSDN2016年博客之星评选, 请大家给我投上一票.

这里是我的地址: http://blog.csdn.net/vote/candidate.html?username=qibin0506

当然了, 还有鸿洋的: http://blog.csdn.net/vote/candidate.html?username=lmj623565791

注意: 每天都可以投哇,每天没人有10张票~~~

好了, 下面进入今天的主题, 前几个月有幸参加了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中找到~

时间: 2024-10-22 06:43:35

Android路由实现的相关文章

【转】 Android路由实现

本文转自: http://blog.csdn.net/qibin0506/article/details/53373412 前几个月有幸参加了CSDN组织的MDCC移动开发者大会, 一天下来我最大的收获就是了解到了模块化开发, 回来之后我就一直在思考模块化的一些优点, 不说别的, 提供一种可插拔的开发方式就足够我们兴奋一会了~ 接下来自己开始尝试了一些小demo, 发现在模块化开发中最大的问题就是组件间通讯, 例如: 在模块化架构中, 商城和个人中心分别是两个独立的模块, 在开发阶段, 个人中心

OpenVPN For Android实现手机刷Twitter

笔者有时候也会刷刷Twitter,或者上Facebook吹吹牛逼,目前的Android对于VPN支持实在是渣渣,用了很多免费的VPN方案都让人欲哭无泪.于是有了自己弄一套VPN的想法,以实现笔者刷刷Twitter,吹吹牛逼的梦想! 基本配置: 1.服务器一台(位于美帝的洛杉矶),CentOS5 64bit,编译安装OpenVPN Server v2.3.4 2.Android手机一部(酷派,android4.2,VPN在Android4.0以上,依赖Google提供的VPNService服务,无

Android组件化方案及组件消息总线modular-event实战

背景 组件化作为Android客户端技术的一个重要分支,近年来一直是业界积极探索和实践的方向.美团内部各个Android开发团队也在尝试和实践不同的组件化方案,并且在组件化通信框架上也有很多高质量的产出.最近,我们团队对美团零售收银和美团轻收银两款Android App进行了组件化改造.本文主要介绍我们的组件化方案,希望对从事Android组件化开发的同学能有所启发. 为什么要组件化 近年来,为什么这么多团队要进行组件化实践呢?组件化究竟能给我们的工程.代码带来什么好处?我们认为组件化能够带来两

香蕉派路由功Openwrt、Android功耗对照測试

路由这个东西是要长期通电使用的,所以功耗也是须要关注的.如今香蕉派路由已经有了openwrt和android两个 系统,这两个系统的功耗是否一样呢? 測试工具:QUIGG的德国产功耗測试仪一个.手机充电器一个 香蕉派路由:除网线.调试串口外,没有接不论什么外部设备,当然TF卡不可缺少.没有开启无线 手机充电器自身的功耗: 电流: 功耗: 一.openwrt稳定后的数据: 电流: 功耗: 二.android稳定后的数据: 电流: 功耗: 因为openwrt眼下功能不完好,至少HDMI没有驱动起来,

Android业务组件化之子模块SubModule的拆分以及它们之间的路由Router实现

前言: 前面分析了APP的现状以及业务组件化的一些探讨(Android业务组件化之现状分析与探讨),以及通信的桥梁Schema的使用(Android业务组件化之URL Schema使用),今天重点来聊下子模块SubModule的拆分以及它们之间的路由Router实现.本篇涉及的相关知识比较多,阅读本篇之间需要大致了解一下Java的注解(Java学习之注解Annotation实现原理).Java的动态代理机制(Java设计模式之代理模式(Proxy))等.业务组件化是一个循序渐进的过程,一开始很难

香蕉派路由功Openwrt、Android功耗对比测试

路由这个东西是要长期通电使用的,所以功耗也是需要关注的.现在香蕉派路由已经有了openwrt和android两个 系统,这两个系统的功耗是否一样呢? 测试工具:QUIGG的德国产功耗测试仪一个.手机充电器一个 香蕉派路由:除网线.调试串口外,没有接任何外部设备,当然TF卡必不可少.没有开启无线 手机充电器自身的功耗: 电流: 功耗: 一.openwrt稳定后的数据: 电流: 功耗: 二.android稳定后的数据: 电流: 功耗: 由于openwrt目前功能不完善,至少HDMI没有驱动起来,所以

展示一下香蕉派路由Android系统

没什么好说的,第一版的系统,Android部分还可以,路由设置确实有闪退现象.等稳定版发布,可以把机顶盒和路由二合一,再接个SSD,还是很不错的. 硬件 开机 桌面 安卓程序 安卓设置 可以打开USB调试,使用ADB了 各种程序设置 文件管理器 影视部分 播放设置 重点来了,路由设置部分 信息总览 流量信息 网络诊断 无线设置的一部分 关机.其实是重启了,软起.直接按Reset是硬起,长按Power直接关机 老外用起来很不适应,默认都是中文的,呵呵,也该让他们学学中文了.

Android 的媒体路由功能应用与框架解析

一.功能描述 Android 的媒体路由API被设计用来允许多种媒体(视频.音乐.图片)在与ANDROID设备连接(无线或有线)的辅助设备(如电视.立体声.家庭戏院系统.音乐播放机)上显示和播放,使用该框架和API,允许Android用户立即在辅助设备上显示图片.播放音乐.共享视频等. 媒体路由框架提供两种播放输出类型:远端播放和辅助输出.远端播放类型指的是辅助设备处理媒体内容的接收.解码和回放,而Android设备(如手机)只起远程控制作用,如ANDROID应用使用该类型用来支持Google

【转载】android客服端+eps8266+单片机+路由器之远程控制系统

用android客服端+eps8266+单片机+路由器做了一个远程控制的系统,因为自己是在实验室里,所以把实验室的门,灯做成了远程控制的. 控制距离有多远------只能说很远很远,只要你手机能上网的地方,不对应该是只要能打电话的地方,不对应该是只要是移动网(我用的是移动的卡)覆盖的地方, 这篇只说明怎么样才能实现远程通信(在路由器上怎样设置,wifi模块eps8266怎样设置),最后会贴上单片机,android的源码 请事先参考我的前几篇文章 android之WIFI小车编程详述, andro