• 那些年,被我们遗忘的技术<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

      端口,端口号,只有匹配了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"); 
  • 相关阅读:
    关于求 p_i != i and p_i != i+1 的方案数的思考过程
    poj 3041 Asteroids 二分图最小覆盖点
    poj 1325 Machine Schedule 最小顶点覆盖
    poj 1011 Sticks 减枝搜索
    poj 1469 COURSES 最大匹配
    zoj 1516 Uncle Tom's Inherited Land 最大独立边集合(最大匹配)
    Path Cover (路径覆盖)
    hdu 3530 SubSequence TwoPoint单调队列维护最值
    zoj 1654 Place the Rebots 最大独立集转换成二分图最大独立边(最大匹配)
    poj 1466 Girls and Boys 二分图最大独立子集
  • 原文地址:https://www.cnblogs.com/liemng/p/8042401.html
Copyright © 2020-2023  润新知