那些年,被我们遗忘的技术

  对于安卓界面跳转主要大家常用的可能都是显示的调用方式,我记得曾经有次面试的时候还被问到,确实显示的跳转狠简单并且很暴力,同时也深受大众喜爱。但是既然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

  端口,端口号,只有匹配了schememhost之后才有意义。

  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-filterdata标签中的配置。

三、如何实现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();
    }
}

  使用如下:

  步骤一、定义接口类,声明需要的方法,并且通过自定义的注解完成参数传入。

  步骤二、通过类Routercreate方法创建一个对应的接口对象。

  步骤三、调用接口中声明的方法。(完成界面跳转)

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");