前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从0到1实现一个Android路由(6)——拦截请求再跳转

从0到1实现一个Android路由(6)——拦截请求再跳转

作者头像
用户1108631
发布2019-08-16 18:01:28
6690
发布2019-08-16 18:01:28
举报

从0到1实现一个Android路由(2)——URL解析器中,提到过请求拦截,其中有个常见的场景是某个页面是需要登录状态的,那么首先要调到登录页,完成了登录之后再跳转到路由页面,但通常登录页都是跳转到主页面的,这该怎么实现呢?上篇文章中没有解决这个问题,本文主要来解决这个问题。

解决这个问题的核心是Hook,接管startActivity(),进行偷梁换柱。因为所有的跳转最终都是通过startActivity来进行的,这里就选择了这么做。关于Hook原理,可以参考Android插件化原理解析——Hook机制之动态代理,本文主要着重说实现。

实现

关于实现,需要考虑的问题是如何保存url,在到了登录界面后,再跳转到原有页面的过程中还能找到先前的url进行跳转。

路由信息的保存与销毁

在拦截成功后,将该URL保存起来;在经过路由跳转的情况下,startActivity之前,清除URL。 EasyRouter的改造如下:

代码语言:javascript
复制
public boolean goToPages(Context context, String url) {
        boolean find = false;
        if (TextUtils.isEmpty(url)) {
            return find;
        }
        //判断是否拦截
        if (routerListener != null && routerListener.onIntercept(url)) {
            find = true;
            //保存URL
            currentUrl = url;
            return find;
        }

        。。。

        for (String key : urlRouterMap.keySet()) {
            //key肯定是大于等于urlPath的,包含了绝对URL和相对URL
            if (key.endsWith(urlPath)) {
                find = true;
                。。。
                //消除URL
                currentUrl = null;
                context.startActivity(intent);
                break;
            }
        }
        //没有找到
        if (!find && routerListener != null) {
            routerListener.onLost(url);
        }
        return find;
    }

Hook实现

参考上面那篇文章的实现,增加了判断EasyRouter中是否有没有处理的URL,如果有,那就交给路由处理一把。

代码语言:javascript
复制
public class InstrumentationHook extends Instrumentation {

    private Instrumentation base;

    public InstrumentationHook(Instrumentation base) {
        this.base = base;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        //如果有URL,说明之前拦截过,交给路由继续执行
        if (!TextUtils.isEmpty(EasyRouter.getInstance().getCurrentUrl())) {
            EasyRouter.getInstance().goToPages(who, EasyRouter.getInstance().getCurrentUrl());
            return null;
        }

        // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
        // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(base, who,
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            // 某该死的rom修改了  需要手动适配
            throw new RuntimeException("do not support!!! pls adapt it");
        }
    }
}

Hook

Hook点可以设置在init()方法中,如下:

代码语言:javascript
复制
public boolean init(String scheme, String host) {
        try {
            UrlCollector urlCollector = (UrlCollector) Class.forName(URL_COLLECTOR_IMPL_CLASS_NAME).newInstance();
            urlRouterMap = urlCollector.getUrlRouterMap();
            this.scheme = scheme;
            this.host = host;
            //Hook
            InstrumentationHook.attachContext();
            return true;
        }  catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

介绍完了原理后,再来看下demo。

demo

在上个版本的例子中进行增加,增加了一个LoginActivity,有个变量判断是否登录过,然后跳转到MainActivity,MainActivity对路由进行了拦截设置,如果url是启动SecondActivity,那么需要进行登录拦截判断。如下:

代码语言:javascript
复制
override fun onIntercept(url: String?): Boolean {
                    if (url != null && url.startsWith("http")) {
                        startActivity(Intent(this@MainActivity, WebViewActivity::class.java).apply {
                            putExtra("external_url", url)
                        })
                        return true
                    }
                    if ((url == secondActivityUrl || url == dynamicUrl) && !LoginActivity.isLogin) {
                        startActivity(Intent(this@MainActivity, LoginActivity::class.java))
                        return true
                    }
                    return false
                }

现在来看下效果,启动SecondActivity或DynamicActivity时,会进行登录拦截判断。

这里启动SecondActivity时做了登录拦截,没有登录的时候,出现了登录界面,点击登录按钮后,本应出现MainActivity,但由于hook的原因,跳转到了本应跳转的SecondActivity。从而实现了拦截再跳转。

总结

本文主要是解决前面遗留的问题,拦截跳转的问题,本文使用的方式是Hook,记录需要跳转的路由,再Activity跳转前检测一次,需要的话就交给路由继续处理,从而hook掉原来的跳转。关于本文代码,可以参考master分支

至此,完成了从0到1实现一个Android路由的所有文章,一个好的路由是给别人用的,要有好的API接口,这儿主要是介绍思想,就没有对API接口进行很好的设计。

参考

  • Android插件化原理解析——Hook机制之动态代理
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学点Android知识 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现
    • 路由信息的保存与销毁
      • Hook实现
        • Hook
    • demo
    • 总结
    • 参考
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档