在从0到1实现一个路由(1)——初探路由中,介绍了一个五脏俱全的路由例子,路由是通过URL到达页面,那么URL解析器是个很重要的步骤,负责解析、跳转、拦截、传参等等。
所谓URL解析器,可以看成是URL和Intent的映射,因为Android启动页面只能通过Intent来跳转,不论是显试调用还是隐式调用,归根结底是一个Intent。
在上个例子的基础上,进行改造,将URL解析器作为单独模块抽出。
结构如下:
fun urlProcess(context: Context, url: String): Intent? { return null}
通过URL到达页面,URL是可以携带参数的,比如GET请求中的url就是含有参数的,我们可以利用该特性对第一篇中的例子加以改造。
URL形式为scheme://host:port/path?query
query的形式为key1=value1&key2=value2。
定义好格式后,新的路由跳转修改为:
//本app支持的scheme else if (url.startsWith("easyrouter")) { with(URI(url)) { for ((key, value) in routeMap) { if (key.equals(path)) { return Intent(context, value).apply { if (!TextUtils.isEmpty(query)) { query.split("&").forEach { it.split("=").apply { putExtra(get(0), get(1)) } } } } } } } }
主要改变是url只要以路由表中的key开头,就能跳转到该Activity,同时也允许不传参的跳转,带有参数的则解析然后放到Intent中进行跳转。 这样的方式需要注意页面对默认值的一些处理,因为有时有些key可能是不提供的。
val paramsActivityUrl = "/paramsActivity" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btnWithParams.setOnClickListener { goToPages(this, paramsActivityUrl + "?firstname=hello&secondname="world") } btnWithoutParams.setOnClickListener { goToPages(this, paramsActivityUrl) } }}
定义好了key之后,再从ParamsActivity中取出就可以了。
经常会遇到跳转到一个页面之前需要首先进行登录操作,那么如果在没有登录的状态下,需要先跳转到登录页面。这里就可以通过URL拦截来实现登录拦截。
拦截需要考虑的点是:如何在拦截后继续跳转到目的页面。比如登录拦截,登录页面好启动,登录成功后,通常逻辑是进入首页,这时如何跳转到目的页面呢?关于这一点,因为原有的页面跳转逻辑都是有的,比如登录成功后就跳转到首页,而现在需要改变跳转逻辑,实现的话侵入会比较严重,需要用到Hook。在寻求解决方案中,先看个简单点的拦截。
举个例子:在跳转SecondActivity之前弹出一个对话框来决定是否继续跳转,新的处理逻辑如下:
un goToPages(context: Context, url: String) { if (url.contains("/secondActivity")) { AlertInterceptor().interceptor(context, URI(url), object : OnSuccessListener { override fun onSuccess(context: Context, uri: URI) { urlProcess(context, url)?.apply { context.startActivity(this) } } }, object : OnErrorListener { override fun onError() { Toast.makeText(context, "用户取消了跳转", Toast.LENGTH_SHORT).show() } } ) } else { urlProcess(context, url)?.apply { context.startActivity(this) } }}
在处理url之前,首先交给AlerInterceptor处理一把,通过两个接口将是否继续跳转通知出来。Interceptor的结构如下:
nterface OnErrorListener { fun onError()}interface OnSuccessListener { fun onSuccess(context: Context, uri: URI)}interface Interceptor { fun interceptor( context: Context, uri: URI, onSuccessListener: OnSuccessListener? = null, onErrorListener: OnErrorListener? = null )}class AlertInterceptor : Interceptor { override fun interceptor( context: Context, uri: URI, onSuccessListener: OnSuccessListener?, onErrorListener: OnErrorListener? ) { AlertDialog.Builder(context).apply { setTitle("拦截提示") setMessage("是否跳转SecondActivity") setPositiveButton( "确定" ) { dialog, _ -> onSuccessListener?.onSuccess(context, uri) dialog.dismiss() } setNegativeButton( "取消" ) { dialog, _ -> onErrorListener?.onError() dialog.dismiss() } }.create().show() }}
url匹配,如果匹配不到Activity那应该怎么办?是否可以跳转到一个默认页面,比如说首页?
这里可以通过降级策略来实现。 可以通过接口暴露给上层,交由开发者自己实现,这里就先简单写死,概念最重要。
在urlProcess()方法最后加上一个默认的Intent,如下:
fun urlProcess(context: Context, url: String): Intent? { ... return Intent(context, DegradeActivity::class.java).apply { putExtra("error_msg", "没有找到目标页面") }}
本文主要在第一个的例子上增加了URL解析器,增加了URL拦截、传参、降级的功能。关于代码,可以参考https://github.com/wangli135/EasyRouter/tree/url_processor。