在从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;
}
参考上面那篇文章的实现,增加了判断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点可以设置在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。
在上个版本的例子中进行增加,增加了一个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接口进行很好的设计。
本文分享自 每天学点Android知识 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!