Android组件化路由实践

我是研究僧i 提交于 2020-04-21 03:45:58

Android应用组件化各个组件页面之间要实现跳转使用路由是一个很好的选择。本文将实现一个比较轻量级的路由组件,主要涉及以下知识:

  • Annotation (声明路由目标信息)
  • AnnotationProcessor (处理注解)
  • JavaPoet (生成Java文件)
  • UriMatcher (匹配Uri)

本文将使用Java注解来实现一个简单的路由组件,主要从这几方面来讲解:

  1. 注解定义与使用
  2. 注解跳转服务
  3. 使用AnnotationProcessor处理注解、生成文件
  4. Uri的匹配
  5. 安全参数
  6. 注解跳转服务的开发

由于使用AnnotationProcessor,所以整个路由可分为以下模块:

  • lib-component-router (Android工程)
  • lib-component-router-annotation (存放注解)
  • lib-component-router-compiler (注解处理)

注解定义

由于我们的路由组件相对简单,主要定义以下注解:

  • UriDestination (声明路由目标信息)
  • DestinationUri (定义Uri路径)
  • DestinationArgument (参数声明)

声明目标路由注解


@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface UriDestination {

    String name();

    DestinationUri uri();

    DestinationArgument[] out() default {};

    DestinationArgument[] in() default {};
}


该注解主要用来注解Activity,声明一个路由目标的信息,各参数说明如下:

  • authority (匹配Uri authority)
  • scheme (匹配Uri scheme)
  • path (匹配Uri路径)
  • out (输出参数)
  • in (输入参数)

路由Uri注解


@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.ANNOTATION_TYPE)
public @interface DestinationUri {

    String authority() default "imxingzhe.com";

    String scheme() default "xingzhe";

    String path() default "";

}

该路由主要用于声明路由目标的Uri信息,各参数说明如下:

  • authority (匹配android.net.Uri authority)
  • scheme (匹配android.net.Uri scheme)
  • path (匹配路由路径信息)

路由参数注解

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.ANNOTATION_TYPE)
public @interface DestinationArgument {

    String key();

    boolean require() default false;

    Class<?> type();
}

该注解主要用于声明路由的输入、输出参数信息,各参数说明如下:

  • key (参数的名称)
  • require (是否是必需的参数)
  • type (参数的类型)

路由组件功能实现

目标Action

public interface DestinationAction {

    Context getContext();

    int getFlags();

    int getRequestCode();

    boolean getUriOnly();

    Bundle getArguments();

}

Action可理解为一次跳转动作,使用端通过Builder模式生成Action实例,然后再通过DestinationService执行给定的动作。

跳转服务逻辑

public interface DestinationService {

    void start(DestinationAction destinationAction);

}

此接口只包含一个start方法用于执行DestinationAction逻辑。主要实现跳转方式是使用类式ContentProvider的UriMatcher方式来实现。首先声明一个抽象Service:

public abstract class AbstractUriDestinationService implements DestinationService {

    private final static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static boolean isDestinationDefinitionResolved;


    @Override
    public void start(DestinationAction ){
        ...
    }


    ...
    
    protected abstract List<DestinationDefinition> getDestinationDefinitions();
}

方法getDestinationDefinitions由子类来实现,主要提供此路由目标的相关信息, DestinationDefinition类如下:

public class DestinationDefinition {

    private final String name;
    private final Class<?> destination;
    private final List<DestinationArgumentDefinition> inArgumentDefinitions;
    private final List<DestinationArgumentDefinition> outArgumentDefinitions;


    public DestinationDefinition(String name, Class<?> destination, List<DestinationArgumentDefinition> inArgumentDefinitions, List<DestinationArgumentDefinition> outArgumentDefinitions) {
        this.name = name;
        this.destination = destination;
        this.inArgumentDefinitions = inArgumentDefinitions;
        this.outArgumentDefinitions = outArgumentDefinitions;
    }

    public String getName() {
        return name;
    }

    public Class<?> getDestination() {
        return destination;
    }


    public List<DestinationArgumentDefinition> getInArgumentDefinitions() {
        return inArgumentDefinitions;
    }

    public List<DestinationArgumentDefinition> getOutArgumentDefinitions() {
        return outArgumentDefinitions;
    }
}

AbstractUriDestinationService类中的start方法实现真正的跳转逻辑:

public abstract class AbstractUriDestinationService implements DestinationService {

    private final static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static boolean isDestinationDefinitionResolved;


    @Override
    public void start(DestinationAction destinationAction) {
        List<DestinationDefinition> destinationDefinitions = getDestinationDefinitions();
        resolveDestinationDefinition(destinationDefinitions);

        Context context = destinationAction.getContext();

        if (context == null) {
            throw new IllegalArgumentException("content == null");
        }

        PackageManager packageManager = context.getPackageManager();

        if (destinationAction instanceof UriDestinationAction) {
            Uri uri = ((UriDestinationAction) destinationAction).getUri();
            int index = matcher.match(uri);

            if (UriMatcher.NO_MATCH == index || index >= destinationDefinitions.size()) {
                throw new IllegalStateException("Not found destination for : " + uri);
            }

            DestinationDefinition destinationDefinition = destinationDefinitions.get(index);
            List<DestinationArgumentDefinition> destinationArgumentDefinitions = destinationDefinition.getInArgumentDefinitions();
            for (DestinationArgumentDefinition argumentDefinition : destinationArgumentDefinitions) {
                Bundle args = destinationAction.getArguments();
                if (argumentDefinition.isRequire() && !args.containsKey(argumentDefinition.getKey())) {
                    throw new IllegalArgumentException("No such key: " + argumentDefinition.getKey());
                }

            }


            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(uri);
            if (packageManager.resolveActivity(intent, 0) == null) {
                if (destinationAction.getUriOnly()) {
                    throw new IllegalStateException("Not found activity for : " + uri);
                } else {
                    intent = new Intent(context, destinationDefinition.getDestination());

                    if (packageManager.resolveActivity(intent, 0) == null) {
                        throw new IllegalStateException("Not found activity for : " + uri);
                    }
                }
            }


            intent.addFlags(destinationAction.getFlags());
            Bundle args = destinationAction.getArguments();
            if (args != null) {
                intent.putExtras(args);
            }

            if (context instanceof Activity) {
                ((Activity) context).startActivityForResult(intent, destinationAction.getRequestCode());
            } else {
                context.startActivity(intent);
            }

        } else {
            throw new IllegalStateException("Not support operate");
        }
    }


    private static void resolveDestinationDefinition(List<DestinationDefinition> destinationDefinitions) {
        if (isDestinationDefinitionResolved) {
            return;
        }


        int index = 0;
        for (DestinationDefinition destinationDefinition : destinationDefinitions) {
            if (destinationDefinition instanceof UriDestinationDefinition) {
                Uri uri = ((UriDestinationDefinition) destinationDefinition).getUri();

                String stringForUri = uri.toString();
                String path = uri.getPath();

                int pathIndex = stringForUri.indexOf(path);
                if (pathIndex != -1) {
                    path = stringForUri.substring(
                            pathIndex,
                            stringForUri.length()
                    );
                }

                matcher.addURI(uri.getAuthority(), path, index++);
            }
        }

        isDestinationDefinitionResolved = true;
    }


    protected abstract List<DestinationDefinition> getDestinationDefinitions();
}

这样通过实现AbstractUriDestinationService类,提供相应的DestinationDefinition就可以实现路由的跳转功能,由于使用的注册我们可以使用AnnotationProcessor来处理注解生成DestinationService的实现类。

源码下载: https://github.com/yjwfn/AndroidRouterSample

<br /> <br />

《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!