1. 程式人生 > >Android組件化路由實踐

Android組件化路由實踐

方法 使用 substring 輸出參數 builder模式 string auth tty efi

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組件化路由實踐