Android组件化路由实践

Posted 架构文摘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android组件化路由实践相关的知识,希望对你有一定的参考价值。

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

以上是关于Android组件化路由实践的主要内容,如果未能解决你的问题,请参考以下文章

课题实践总结

组件化解耦 | 浅析ARouter路由发现原理与简单实践

android片段-数据传递-最佳实践[重复]

Android 组件化路由组件 ( 注解处理器中使用 JavaPoet 生成代码 )

Android 组件化路由组件 ( 生成 Root 类记录模块中的路由表 )

在android中使用底部导航的最佳实践:活动与片段