那些年,被我们遗忘的技术<Android 基于URL界面跳转>
对于安卓界面跳转主要大家常用的可能都是显示的调用方式,我记得曾经有次面试的时候还被问到,确实显示的跳转狠简单并且很暴力,同时也深受大众喜爱。但是既然Google提供了另一种隐式的界面跳转,能一直存在下来必然是有意义的。那么问题来了,为什么这么说? 鞥横。
对于系统级应用的调用我想应该很多人用到,例如调用系统打电话功能,系统相册,系统相机等等。对于这些调用其实我们都是隐式调用。这也许是Google提供该功能的一个重要原因吧!可能在当前应用内部很少会有人用到这种调用方式,但是对于当下组件化开发盛行时代,我相信隐式调用完成界面跳转的春天来了。
^_^
好吧,这里不啰嗦了,直接进入主题。
一、介绍android标签<data>
标签使用语法:
<data android:scheme="string" android:host="string" android:port="string" android:path="string" android:pathPattern="string" android:pathPrefix="string" android:mimeType="string" />
这里我们简单介绍下我们今天要用到的几个属性。
scheme
协议,可以自己定义,例如http等。
host
主机,主机名,只有先匹配scheme之后才有意义。
port
端口,端口号,只有匹配了schemem和host之后才有意义。
path
路径,匹配intent对象的路径。
二、如何在html中直接打开Activity?
这里是通过html中的a标签中的属性href完成界面跳转,不需要在通过js调用android中的java代码,然后在去通过android代码完成界面跳转。\(≧▽≦)/
需要打开的Activity的规则定义,清单文件intent-filter定义如下:
<activity android:configChanges="keyboardHidden|orientation|screenSize" android:screenOrientation="portrait" android:theme="@style/ActAnimTheme"> <intent-filter> <!--协议部分,随便设置--> <data android:scheme="http" android:host="jump" android:path="/jumpDetailActivity" android:port="6666"/> <!--下面这几行也必须得设置--> <category android:name="android.intent.category.DEFAULT"/> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.BROWSABLE"/> </intent-filter> </activity>
如上代码定义action规则是Intent.VIEW,category有一个默认的设置,另一个是指向浏览器端的category,当做默认设置设置即可。
data标签分别自定义协议和主机号,端口,路径(必须项)。
<a href="http://jump:8888/jumpDetailActivity?key=1367">点击测试跳转</a>
核心代码一行,拼接如下协议,主机,端口,路径,参数,都一一对应Intent-filter中data标签中的配置。
三、如何实现java代码中界面跳转?
对于java代码中的实现和html实现是一致的,不同在于java代码中没有a标签,但是我们有URI,直接去解析拼接的URL。
实现代码如下:
public boolean toRoute(){ PackageManager packageManager = builder.applicationContext.getPackageManager(); Intent intent = new Intent(mAction, Uri.parse(url)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0); boolean isValid = !activities.isEmpty(); if (isValid) { builder.applicationContext.startActivity(intent); } return isValid; }
四、通过注解实现URL跳转\(≧▽≦)/
由于每次都需要自己拼接URL感觉有些过于繁琐,有没有什么更加有效果方法简化我们的开发。
注解:
自定义注解,不知道大家看过整个url之后有没有注意到整个url的组织结构类似GET请求。我们能否类似Retrofit的解析方式实现我们自己的跳转(路由)功能。
自定义注解
协议类注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Scheme { String value() default ""; }
主机类注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Host { String value() default ""; }
端口类注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Port { String value() default ""; }
路径类注解
/** * 作者:liemng on 2017/12/14 * 邮箱:859686819@qq.com */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Path { String value() default ""; }
参数类注解
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface RouteParam { String value() default ""; }
如上是自定义的注解,那么我们当下需要如何解析注解并且拼装成一个URL,最后打开一个Activity。
定义路由类。
public class Router { private final Builder builder; private Map<Method, ServiceMethod> serviceMethodCache = new HashMap<>(); private Router(Builder builder) { this.builder = builder; } /** * 实例化对应的接口类对象 * @param clazz * @param <T> * @return */ public <T> T create(Class<T> clazz) { validateServiceInterface(clazz); return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /*如果调用的是Object类中的方法,则直接调用*/ if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } ServiceMethod serviceMethod = loadServiceMethod(method, args); return serviceMethod.toRoute(); } }); } /** * 检查注解是否完成了解析 * @param method * @param args * @return */ ServiceMethod loadServiceMethod(Method method, Object[] args) { ServiceMethod serviceMethod = serviceMethodCache.get(method); if (null != serviceMethod) return serviceMethod; synchronized (serviceMethodCache) { serviceMethod = new ServiceMethod(builder); serviceMethodCache.put(method, serviceMethod); } serviceMethod.parseAnnotation(method, args); return serviceMethod; } /** * 校验接口是否合法 * @param clazz 接口类的字节码 * @param <T> */ <T> void validateServiceInterface(Class<T> clazz) { if (!clazz.isInterface()) throw new IllegalArgumentException("clazz must be a interface."); if (clazz.getInterfaces().length > 0) throw new IllegalArgumentException("clazz must be not extent other interface."); } /** * 路由参数构建 */ public static class Builder { Context applicationContext; /*以下参数仅仅是默认值*/ String scheme; String host; String port; String path; /** * 为了避免内存泄露 * @param context */ public Builder(@NonNull Context context) { this.applicationContext = context.getApplicationContext(); } public Builder scheme(String scheme) { this.scheme = scheme; return this; } public Builder host(String host) { this.host = host; return this; } public Builder port(String port) { this.port = port; return this; } public Builder path(String path) { this.path = path; return this; } public Router build() { return new Router(this); } } }
Builder类主要是传递配置参数,对于默认其他值最为默认值,在找不到对应的注解参数会使用该值。
使用,可以传入一个接口类,然后返回指定的接口对象(动态代理),代理作用是解析接口中方法上的注解,然后拼接参数,然后打开指定的Activity。
注解解析类
public class ServiceMethod { private String url = ""; private Builder builder; private String mAction = Intent.ACTION_VIEW; public ServiceMethod(Builder builder) { this.builder = builder; } public void parseAnnotation(Method method, Object[] args) { /*解析方法注解*/ parseMethodAnnotation(method); /*解析方法参数注解*/ parseParamsAnnotation(method, args); } /** * 执行路由跳转 * @return */ public boolean toRoute(){ PackageManager packageManager = builder.applicationContext.getPackageManager(); Intent intent = new Intent(mAction, Uri.parse(url)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0); boolean isValid = !activities.isEmpty(); if (isValid) { builder.applicationContext.startActivity(intent); } return isValid; } /** * 解析方法注解 * * @param method */ public void parseMethodAnnotation(Method method) { /*解析Action*/ Action action = method.getAnnotation(Action.class); if(null != action){ mAction = action.value(); } /*RouteUri: Scheme + Host + Port + Path*/ RouteUri routeUri = method.getAnnotation(RouteUri.class); if (null != routeUri) { url += routeUri.routeUri(); return; } /*拼接协议参数*/ Scheme scheme = method.getAnnotation(Scheme.class); if (null != scheme) { String value = scheme.value(); url += (TextUtils.isEmpty(value) ? builder.scheme : value); } /*拼接主机参数*/ Host host = method.getAnnotation(Host.class); if (null != host){ String value = host.value(); url += "://"; url += (TextUtils.isEmpty(value) ? builder.host : value); } /*拼接端口参数*/ Port port = method.getAnnotation(Port.class); if (null != port){ String value = port.value(); url += ":"; url += (TextUtils.isEmpty(value) ? builder.port : value); } /*拼接路径参数*/ Path path = method.getAnnotation(Path.class); if (null != path){ String value = path.value(); url += (TextUtils.isEmpty(value) ? builder.path : value); } } /** * 解析方法参数注解 * * @param method */ public void parseParamsAnnotation(Method method, Object[] args) { /**/ Annotation[][] annotations = method.getParameterAnnotations(); StringBuilder reqParamsBuilder = new StringBuilder(); for (int i = 0; i < annotations.length; i++) { Annotation[] annotationsArrays = annotations[i]; if (annotationsArrays.length > 0) { Annotation annotationsItem = annotationsArrays[0]; if (!(annotationsItem instanceof RouteParam)) break; if (i == 0) { reqParamsBuilder.append("?"); } else { reqParamsBuilder.append("&"); } /*添加Key*/ reqParamsBuilder.append(((RouteParam) annotationsItem).value()); reqParamsBuilder.append("="); /*添加Value*/ reqParamsBuilder.append(args[i]); } } url += reqParamsBuilder.toString(); } }
使用如下:
步骤一、定义接口类,声明需要的方法,并且通过自定义的注解完成参数传入。
步骤二、通过类Router的create方法创建一个对应的接口对象。
步骤三、调用接口中声明的方法。(完成界面跳转)
public interface IRoute { @Scheme("http") @Host("jump") @Port("6666") @Path("/jumpDetailActivity")
@Action(Intent.ACTION_VIEW)
void skip(@RouteParam("key") String value); }
创建对应的接口对象,并且调用声明方法,完成界面跳转。
Router build = new Router.Builder(getActivity()).build(); IRoute iRoute = build.create(IRoute.class); iRoute.skip("ArMn123");