• 那些年,被我们遗忘的技术<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"); 
  • 相关阅读:
    网站服务器架构设计
    使用同步或异步的方式完成 I/O 访问和操作(Windows核心编程)
    堆栈上的舞蹈之释放重引用(UAF) 漏洞原理实验分析
    内核模式下的线程同步的分析(Windows核心编程)
    用户模式下的线程同步的分析(Windows核心编程)
    Linux下部署Django项目
    HDU 2075 A|B?
    HDU 2052 Picture
    HDU 2024 C语言合法标识符
    HDU 2026 首字母变大写
  • 原文地址:https://www.cnblogs.com/liemng/p/8042401.html
Copyright © 2020-2023  润新知