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

从0到1实现一个Android路由系列文章

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

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

实现

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

路由信息的保存与销毁

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

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,如果有,那就交给路由处理一把。

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()方法中,如下:

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,那么需要进行登录拦截判断。如下:

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接口进行很好的设计。

参考

关注我的技术公众号,不定期会有技术文章推送,不敢说优质,但至少是我自己的学习心得。微信扫一扫下方二维码即可关注:
二维码

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页