Andorid Componentized Jump Routing

Brief introduction

Routing plays a very important role in component engineering. How do two module s communicate without mutual reference?

We can use EventBus, broadcast, class loading, reflection, scheme, implicit intention and so on. Each of these methods has its own advantages and disadvantages. In the current open source routing framework, ARouter uses the method of class loading. We also use class loading to encapsulate a small routing by ourselves.
Then how to use the method of class loading to communicate between different components? It's very simple as long as we can get a full class name.

For example, create a new project appcom.chs.mymodule.MainActivity and create two modules, an order component com.chs.order.OrderActivity and an integral component com.chs.integral.Integral Activity. Now jumping from app module to order module requires only the following actions

    try {
            Class<?> clzz = Class.forName("com.chs.order.OrderActivity");
            Intent intent = new Intent(this,clzz);
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }

It's a bit troublesome to write class names like this every time. It's easy to make mistakes in all kinds of points. Can we encapsulate that? In fact, we just need to get the class name in OrderActivity.
So we can save these classes that need to be jumped, such as in a map, get the class name by key when jumping, and then continue to jump through Intent.
Simple implementation
First, create an entity class to save the path and the corresponding class

public class PathBean {
    private String path;
    private Class<?> clzz;

    public PathBean() {
    }

    public PathBean(String path, Class<?> clzz) {
        this.path = path;
        this.clzz = clzz;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Class<?> getClzz() {
        return clzz;
    }

    public void setClzz(Class<?> clzz) {
        this.clzz = clzz;
    }
}

Then create a new management class to save the registered paths and class information.

public class RecordPathManager {

    public static Map<String, List<PathBean>> groupMap = new HashMap<>();

    public static void joinGroup(String groupName,String pathName,Class<?> clzz){
        List<PathBean> pathBeans = groupMap.get(groupName);
        if(pathBeans == null){
            pathBeans = new ArrayList<>();
            pathBeans.add(new PathBean(pathName,clzz));
            groupMap.put(groupName,pathBeans);
        }else {
            if(!isExit(pathName,pathBeans)){
                pathBeans.add(new PathBean(pathName,clzz));
            }
            groupMap.put(groupName,pathBeans);
        }
    }
    private static boolean isExit(String pathName,List<PathBean> list){
        for (PathBean bean : list) {
            if(pathName.equals(bean.getPath())){
                return true;
            }
        }
        return false;
    }

    public static Class<?> getTargetClass(String groupName,String pathName){
        List<PathBean> list = groupMap.get(groupName);
        if(list == null){
            return null;
        }else {
            for (PathBean bean : list) {
                if(pathName.equalsIgnoreCase(bean.getPath())){
                    return bean.getClzz();
                }
            }
        }
        return null;
    }
}

This class is very simple, a Map, a stored method and a fetched method. In order to improve efficiency, we divide it into groups. Each module is a group. key is the name of the module. value is the collection of class information under the group.

Finally, how to save it? Of course, the sooner you save it, the better. Otherwise, if you haven't saved it, you can't get the related classes by jumping. So we store it in the application in the app module
When an application is packaged, the app module must be all the modules that it depends on, so the classes in the sub-module must also be available. So the registration code is as follows

public class App extends BaseApplication {

    @Override
    public void onCreate() {
        super.onCreate();
        RecordPathManager.joinGroup("order","OrderActivity", OrderActivity.class);
        RecordPathManager.joinGroup("integral","IntegralActivity", IntegralActivity.class);
        ......
    }
}

The module name, class name and class information of the class activity to be jumped will be saved in the Map of the management class. The time of use is as follows

  try {
            Class<?> clzz = RecordPathManager.getTargetClass("integral","IntegralActivity");
            Intent intent = new Intent(this,clzz);
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }

It seems a little simpler than the beginning, but it is too troublesome to register one by one in the activity. Just a few classes are better. Dozens of classes will probably spit themselves out. Can the system help us write the registration code? Of course, it can be done by using APT technology at this time.

Use APT to automatically complete registration.

Following is an example of ARouter to implement a simple jump routing.
The project structure is as follows:

  • Annotation is annotation module is a java module
  • Compoiler is an annotation processor and must be a java module
  • order and integral are two word module s
  • Arounter is used to define storage specifications

Define a compile-time annotation in annotation

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ARouter {
     String path();

     String group() default "";
}

In defining a RouterBean, it is equivalent to the PathBean object in the previous simple implementation, which is used to store paths, classes, group names, etc.

public class RouterBean {

    public enum Type{
        /**
         * activity type
         */
        ACTIVITY;
    }

    private Type mType;

    /**
     * Class Node
     */
    private Element mElement;
    /**
     * Group name
     */
    private String group;
    /**
     * Routing address
     */
    private String path;

    private Class<?> mClazz;


    private RouterBean() {
    }
    private RouterBean(Type type, Class<?> clazz, String path, String group) {
        this.mType = type;
        this.mClazz = clazz;
        this.path = path;
        this.group = group;
    }
    public static RouterBean create(Type type,Class<?> clazz,String path,String group){
        return new RouterBean(type,clazz,path,group);
    }
    private RouterBean(Builder builder) {
        mElement = builder.mElement;
        group = builder.group;
        path = builder.path;
    }

    public Type getType() {
        return mType;
    }

    public Element getElement() {
        return mElement;
    }

    public String getGroup() {
        return group;
    }

    public String getPath() {
        return path;
    }

    public Class<?> getClzz() {
        return mClazz;
    }

    public void setType(Type type) {
        mType = type;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public static final class Builder{
        private Element mElement;
        private String group;
        private String path;

        public Builder setElement(Element element) {
            mElement = element;
            return this;
        }

        public Builder setGroup(String group) {
            this.group = group;
            return this;
        }

        public Builder setPath(String path) {
            this.path = path;
            return this;
        }
        public RouterBean build(){
            if(path==null||path.length()==0){
              throw new IllegalArgumentException("path For example /app/MainActivity");
            }
            return new RouterBean(this);
        }
    }

    @Override
    public String toString() {
        return "RouterBean{" +
                "group='" + group + '\'' +
                ", path='" + path + '\'' +
                '}';
    }
}

Next, we define two interfaces, ARouterLoadGroup and RouterLoadPath, in the module arouter_api.
There will be many modules in a project. If every time some classes are loaded, it will consume memory. So design a group to manage. The group name is the name of the current module. It manages all registered activity classes under the module. Only when the user reaches the module, it will load.
We generate classes that inherit from these two interfaces.

/**
 *key:"app", value:"app"Routing Detailed Object Class for Packet Correspondence
 */
public interface ARouterLoadGroup {
    
    Map<String,Class<? extends ARouterLoadPath>> loadGroup();

}
public interface ARouterLoadPath {
    /**
     * For example, the incoming / app/MainActivity finds the corresponding group through app, and then finds the corresponding class through / app/MainActivity.
     */
    Map<String, RouterBean> loadPath();
}

Now we come to the Comment Manager Compoiler.
First, introduce the relevant tools in its gradle file

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

    // Help us generate Java code in the form of class calls
    implementation "com.squareup:javapoet:1.9.0"

    // Dependence on annotations
    implementation project(':annotation')
}

// java console output Chinese scrambling code
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "7"
targetCompatibility = "7"

auto-service is provided by Google, which can automatically trigger annotation manager to run.
javapoet is a tool produced by the famous square company to generate java classes
javapoet has eight commonly used classes

Formatting rules for JavaPort strings

The data in the two tables above is more important, and the annotation manager below will be used.

Specific usage can be viewed in https://github.com/square/javapoet
Because the related classes are generated by ourselves, we must know the final appearance of them beforehand. Let's first look at the final appearance of the classes that need to be generated.

public class ARouter$$Group$$app implements ARouterLoadGroup {
  @Override
  public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
    Map<String,Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
    groupMap.put("app",ARouter$$Path$$app.class);
    return groupMap;
  }
}
public class ARouter$$Path$$app implements ARouterLoadPath {
  @Override
  public Map<String, RouterBean> loadPath() {
    Map<String,RouterBean> pathMap = new HashMap<>();
    pathMap.put("/app/Main2Activity",RouterBean.create(RouterBean.Type.ACTIVITY,Main2Activity.class,"/app/Main2Activity","app"));
    pathMap.put("/app/MainActivity",RouterBean.create(RouterBean.Type.ACTIVITY,MainActivity.class,"/app/MainActivity","app"));
    return pathMap;
  }
}

Inheriting the two interfaces defined above, one is used to initialize the map of the group and store the path information. The other is the path information class, which initializes and saves the related path and class information.

It is important to note that the second class is used in the first class, so when you generate a class, you need to generate the second class and then the first class.
Compare the above two classes to start writing annotation managers:

@AutoService(Processor.class)
//Here is the full class name of the annotation, such as com.chs.annotation.ARouter, which is saved in a constant.
@SupportedAnnotationTypes(Const.ACTIVITY_ANNOTATION_TYPE)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedOptions({Const.MODULE_NAME,Const.APT_PACKAGE})
public class ARouterProcessor extends AbstractProcessor {
    /**
     * Node Tool Class Function Attributes Are Elements
     */
    private Elements elementUtils;
    /**
     * Class Information Tool
     */
    private Types typeUtils;
    /**
     * Printing Tool Class
     */
    private Messager mMessager;
    /**
     * File Generator
     */
    private Filer mFiler;
    /**
     * Group name
     */
    private String moduleName;
    /**
     * The path package name of the generated apt file
     */
    private String packageNameForAPT;

    /**
     * Traverse the class that temporarily stores the path when it is generated
     * key Group names such as app value group routing paths such as ARouter $Path $app. class
     */
    private Map<String, List<RouterBean>> tempPathMap = new HashMap<>();
    /**
     * Classes of temporary storage groups
     * For example, key app value ARouter $Path $app
     */
    private Map<String, String> tempGroupMap = new HashMap<>();


    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnv.getTypeUtils();
        mMessager = processingEnv.getMessager();
        mFiler = processingEnv.getFiler();

        Map<String, String> options = processingEnv.getOptions();
        mMessager.printMessage(Diagnostic.Kind.NOTE,options.toString());
        if(!EmptyUtils.isEmpty(options)){
            moduleName = options.get(Const.MODULE_NAME);
            packageNameForAPT = options.get(Const.APT_PACKAGE);
            mMessager.printMessage(Diagnostic.Kind.NOTE,"moduleName:"+moduleName);
            mMessager.printMessage(Diagnostic.Kind.NOTE,"packageNameForAPT:"+packageNameForAPT);
        }
        if(EmptyUtils.isEmpty(moduleName)||EmptyUtils.isEmpty(packageNameForAPT)){
            throw new IllegalArgumentException("Annotate the parameters required by the processor module or packageName For empty, need gradle Configuration in");
        }

    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if(!EmptyUtils.isEmpty(set)){
           //Get all sets of elements annotated by ARouter
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            if(!EmptyUtils.isEmpty(elements)){
                try {
                    parseElements(elements);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Parsing the set of elements of all individual ARouter annotations
     * @param elements
     */
    private void parseElements(Set<? extends Element> elements) throws IOException {
        //The path to get activity type activity through the element tool is android.app.Activity
        TypeElement typeElement = elementUtils.getTypeElement(Const.ACTIVITY);
        //Get TypeMirror for activity
        TypeMirror activityTypeMirror = typeElement.asType();
        for (Element element : elements) {
            //Get class information for each element
            TypeMirror elementTypeMirror = element.asType();
            mMessager.printMessage(Diagnostic.Kind.NOTE,"The traversal element information is:"+elementTypeMirror.toString());

            //Get the path value corresponding to the ARouter annotation on each class
            ARouter aRouter = element.getAnnotation(ARouter.class);
            //Packaging RouterBean
            RouterBean routerBean = new RouterBean.Builder()
                    .setGroup(aRouter.group())
                    .setPath(aRouter.path())
                    .setElement(element)
                    .build();
            //Prevent users from scribbling judgments that annotations work on activity
            if(typeUtils.isSubtype(elementTypeMirror,activityTypeMirror)){
                  routerBean.setType(RouterBean.Type.ACTIVITY);
            }else {
                throw new IllegalArgumentException("@ARouter It can only be used at present. Activity Above");
            }

            //Store it in a temporary map for later assembly
            valueOfPathMap(routerBean);
        }
        //Types of ARouterLoadGroup and ARouterLoadPath
        TypeElement groupElementType = elementUtils.getTypeElement(Const.AROUTER_GROUP);
        TypeElement pathElementType = elementUtils.getTypeElement(Const.AROUTER_PATH);
        //Mr. 1 routing path files such as ARouter $Path $order
        createPathFile(pathElementType);
        //2 In generating routing group files such as ARouter $GROUP $order because the above class is used in the group
        createGroupFile(groupElementType,pathElementType);
    }

    /**
     * Generate path ARouter $Path $order corresponding to group
     * @param pathElementType
     */
    private void createPathFile(TypeElement pathElementType) throws IOException {
       if(EmptyUtils.isEmpty(tempPathMap)){
           return;
       }
       //Method return value Map < String, RouterBean >
        TypeName typeName = ParameterizedTypeName.get(
                ClassName.get(Map.class),ClassName.get(String.class),ClassName.get(RouterBean.class));
       //Traversing through groups, each group creates a path file ARouter $Path $order
        for (Map.Entry<String, List<RouterBean>> entry : tempPathMap.entrySet()) {
           //Method volume public Map < String, RouterBean > loadPath () {}
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Const.PATH_METHOD_NAME)
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(typeName);
            //Map<String, RouterBean> pathMap = new HashMap<>();
            methodBuilder.addStatement("$T<$T,$T> $N = new $T<>()",ClassName.get(Map.class)
            ,ClassName.get(String.class),ClassName.get(RouterBean.class),Const.PATH_PARAMA_NAME
            ,HashMap.class);
            List<RouterBean> values = entry.getValue();
            for (RouterBean bean : values) {
                // pathMap.put("/order/Order_MainActivity",RouterBean.create(RouterBean.Type.ACTIVITY
                //        , Order_MainActivity.class,"/order/Order_MainActivity","order"));
                methodBuilder.addStatement("$N.put($S,$T.create($T.$L,$T.class,$S,$S))",
                        Const.PATH_PARAMA_NAME,
                        bean.getPath(),
                        ClassName.get(RouterBean.class),
                        ClassName.get(RouterBean.Type.class),
                        bean.getType(),
                        ClassName.get((TypeElement) bean.getElement()),
                        bean.getPath(),
                        bean.getGroup());
            }
            methodBuilder.addStatement("return $N",Const.PATH_PARAMA_NAME);

            String finalClassName = Const.PATH_FILD_NAME + entry.getKey();
            mMessager.printMessage(Diagnostic.Kind.NOTE,
                    "APT Generated path Class is"+packageNameForAPT+finalClassName);
            JavaFile.builder(packageNameForAPT,
                    TypeSpec.classBuilder(finalClassName)
                            //Interfaces implemented by this class
                            .addSuperinterface(ClassName.get(pathElementType))
                            .addModifiers(Modifier.PUBLIC)
                            //Method body
                            .addMethod(methodBuilder.build())
                            .build())
                     .build()
            .writeTo(mFiler);

            tempGroupMap.put(entry.getKey(),finalClassName);
        }
    }

    /**
     * Generate group ARouter $Group $order
     * @param groupElementType
     * @param pathElementType
     */
    private void createGroupFile(TypeElement groupElementType, TypeElement pathElementType) throws IOException {
        if(EmptyUtils.isEmpty(tempPathMap)||EmptyUtils.isEmpty(tempGroupMap)){
                return;
        }
        //public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
        TypeName methodReturn = ParameterizedTypeName.get(ClassName.get(Map.class),ClassName.get(String.class)
        ,ParameterizedTypeName.get(ClassName.get(Class.class),
                        //Class<? extends ARouterLoadPath>
                        WildcardTypeName.subtypeOf(ClassName.get(pathElementType))));
        //Method volume public Map < String, RouterBean > loadPath () {}
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Const.GROUP_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .returns(methodReturn);
        //  Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
        methodBuilder.addStatement("$T<$T,$T> $N = new $T<>()",
                ClassName.get(Map.class)
                ,ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathElementType))),
                Const.GROUP_PARAMA_NAME
                ,HashMap.class);
        //groupMap.put("order",ARouter$$Path$$order.class);
        for (Map.Entry<String, String> entry : tempGroupMap.entrySet()) {
            methodBuilder.addStatement("$N.put($S,$T.class)",
                    Const.GROUP_PARAMA_NAME,
                    entry.getKey(),
                    //Under the specified package name
                    ClassName.get(packageNameForAPT,entry.getValue()));
        }
        methodBuilder.addStatement("return $N",Const.GROUP_PARAMA_NAME);
        String finalClassName = Const.GROUP_FILD_NAME + moduleName;
        mMessager.printMessage(Diagnostic.Kind.NOTE,
                "APT Generated group Class is"+packageNameForAPT+finalClassName);
        JavaFile.builder(packageNameForAPT,
                TypeSpec.classBuilder(finalClassName)
                        //Interfaces implemented by this class
                        .addSuperinterface(ClassName.get(groupElementType))
                        .addModifiers(Modifier.PUBLIC)
                        //Method body
                        .addMethod(methodBuilder.build())
                        .build())
                .build()
                .writeTo(mFiler);
    }

    private static boolean isExit(String pathName,List<RouterBean> list){
        for (RouterBean bean : list) {
            if(pathName.equalsIgnoreCase(bean.getPath())){
                return true;
            }
        }
        return false;
    }
    private void valueOfPathMap(RouterBean routerBean) {
        if(checkRouterPath(routerBean)){
            mMessager.printMessage(Diagnostic.Kind.NOTE,"routerBean object"+routerBean.toString());
            //Start putting map
            List<RouterBean> list = tempPathMap.get(routerBean.getGroup());
            if(EmptyUtils.isEmpty(list)){
                list = new ArrayList<>();
                list.add(routerBean);
                tempPathMap.put(routerBean.getGroup(),list);
            }else {
                if(!isExit(routerBean.getPath(),list)){
                    list.add(routerBean);
                }
            }
        }else {
            mMessager.printMessage(Diagnostic.Kind.ERROR,"@ARouter The annotations did not follow the rules. /app/MainActivity");
        }

    }

    private boolean checkRouterPath(RouterBean routerBean) {
        String path = routerBean.getPath();
        String group = routerBean.getGroup();
        if(EmptyUtils.isEmpty(path)||!path.startsWith("/")){
            mMessager.printMessage(Diagnostic.Kind.ERROR,"@ARouter The annotations did not follow the rules. /app/MainActivity");
            return false;
        }
        if(path.lastIndexOf("/")== 0){
            mMessager.printMessage(Diagnostic.Kind.ERROR,"@ARouter The annotations did not follow the rules. /app/MainActivity");
            return false;
        }
        String finalGroup = path.substring(1,path.indexOf("/",1));
        mMessager.printMessage(Diagnostic.Kind.NOTE,"finalGroup:"+finalGroup);

        if(finalGroup.contains("/")){
            mMessager.printMessage(Diagnostic.Kind.ERROR,"@ARouter The annotations did not follow the rules. /app/MainActivity");
            return false;
        }
        if(!EmptyUtils.isEmpty(group)&&!group.equalsIgnoreCase(moduleName)){
            mMessager.printMessage(Diagnostic.Kind.ERROR,"group Must be the name of the current module");
            return false;
        }else {
            routerBean.setGroup(finalGroup);
        }
        return true;
    }
}
public class EmptyUtils {
    public static boolean isEmpty(CharSequence c){
       return c==null||c.length()==0;
    }
    public static boolean isEmpty(Collection<?> c){
       return c==null||c.isEmpty();
    }
    public static boolean isEmpty(final Map<?,?> c){
       return c==null||c.isEmpty();
    }
}
//Const classes are all constant strings.

The @SupportedOptions annotation on the above class can accept parameters passed from build.grale. For example, we pass in the name of module and the package name of the last generated class in build.gradle

//rootProject.ext.packageNameForAPT is a constant com.chs.module.apt defined in gradle.
  javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName(), packageNameForAPT: rootProject.ext.packageNameForAPT]
            }
        }

Introduce annotation and annotation manager in each module that needs to generate classes

 implementation project(':annotation')
 annotationProcessor project(':complier')

Finally, rebuild the project, and the corresponding classes will be generated under the corresponding module's build folder, such as the classes ARouterGroupApp and AlterPathPathPathPathPath generated under the path app build generate source apt debug com.chs. module. apt in the appmodule.

Finally, the generated class is used to implement the jump.

 ARouterLoadGroup group = new ARouter$$Group$$order();
        Map<String, Class<? extends ARouterLoadPath>> map = group.loadGroup();
        // Getting the corresponding routing path object through the order group name
        Class<? extends ARouterLoadPath> clazz = map.get("order");

        try {
            // Class loading dynamically loads routing path objects
            ARouter$$Path$$order path = (ARouter$$Path$$order) clazz.newInstance();
            Map<String, RouterBean> pathMap = path.loadPath();
            // Acquiring target object encapsulation
            RouterBean bean = pathMap.get("/order/Order_MainActivity");

            if (bean != null) {
                Intent intent = new Intent(this, bean.getClzz());
                intent.putExtra("name", "lily");
                startActivity(intent);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

When the final integration package is completed, all the class files generated by sub-modules app, order, integral through APT will be packaged into the apk, so don't worry about not finding them.
After running, you will see that the jump succeeds, but it seems that writing so much code at a time is not what we want. Our ideal jump should look like the following

  RouterManager.getInstance().build("/personal/Personal_MainActivity")
                .withString("name","chs")
                .navigation(this);

This requires us to encapsulate the jump code in front of us.

public class RouterManager {

    private static  RouterManager instence;
    private LruCache<String, ARouterLoadGroup> groupCache;
    private LruCache<String, ARouterLoadPath>  pathCache;
    private String path;
    private String group;
    private static final String GROUP_PRFIX_NAME = ".ARouter$$Group$$";

    public static RouterManager getInstance(){
        if(instence == null){
            synchronized (RouterManager.class){
                if(instence == null){
                    instence = new RouterManager();
                }
            }
        }
        return instence;
    }

    public RouterManager() {
        groupCache = new LruCache<>(100);
        pathCache = new LruCache<>(100);
    }

    public BundleManager build(String path) {
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            throw new IllegalArgumentException("Not configured according to specifications, such as:/app/MainActivity");
        }
        group = subFromPath2Group(path);
        this.path = path;
        return new BundleManager();
    }

    /**
     * Determine whether the format of path conforms to the specification
     * @param path Paths that need to be taken
     * @return
     */
    private String subFromPath2Group(String path) {
        if(path.lastIndexOf("/") == 0){
            throw new IllegalArgumentException("@ARouter Notes are not configured according to specifications, such as:/app/MainActivity");
        }
        String finalGroup = path.substring(1, path.indexOf("/", 1));
        if (TextUtils.isEmpty(finalGroup)) {
            throw new IllegalArgumentException("@ARouter Notes are not configured according to specifications, such as:/app/MainActivity");
        }
        return finalGroup;
    }

    public Object navigation(Context context, BundleManager bundleManager, int code) {
        String groupClassName = context.getPackageName() + ".apt" + GROUP_PRFIX_NAME + group;
        Log.e("chs >>> ", "groupClassName -> " + groupClassName);

        try {
        ARouterLoadGroup aRouterLoadGroup = groupCache.get(groupClassName);
            if(aRouterLoadGroup == null){
                Class<?> clazz  = Class.forName(groupClassName);
                aRouterLoadGroup = (ARouterLoadGroup) clazz.newInstance();
                groupCache.put(groupClassName,aRouterLoadGroup);
            }
            // Get the map of the routing path class ARouter $Path $app
            if (aRouterLoadGroup.loadGroup().isEmpty()) {
                throw new RuntimeException("Routing Load Failure");
            }

            ARouterLoadPath aRouterLoadPath = pathCache.get(path);
            if(aRouterLoadPath == null){
                Class<?> clazz = aRouterLoadGroup.loadGroup().get(group);
                if (clazz != null){
                    aRouterLoadPath = (ARouterLoadPath) clazz.newInstance();
                    pathCache.put(path,aRouterLoadPath);
                }
            }

            if(aRouterLoadPath.loadPath().isEmpty()){
                throw new RuntimeException("Routing Path Loading Failed");
            }
            RouterBean routerBean = aRouterLoadPath.loadPath().get(path);
            if (routerBean != null) {
                switch (routerBean.getType()){
                    case ACTIVITY:
                        Intent intent = new Intent(context, routerBean.getClzz());
                        intent.putExtras(bundleManager.getBundle());

                        if (bundleManager.isResult()) {
                            ((Activity) context).setResult(code, intent);
                            ((Activity) context).finish();
                        }else {
                            if (code > 0) { // Whether to call back when jumping
                                ((Activity) context).startActivityForResult(intent, code, bundleManager.getBundle());
                            } else {
                                context.startActivity(intent, bundleManager.getBundle());
                            }
                        }
                        break;
                }
            }

        }catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

The core class in the above code is actually the jump code.

By passing in the path path path, the group name is intercepted
Then the name of the completed group class is concatenated according to the group name, package name and defined prefix, and the object is instantiated by class loading.
After the instance is completed, it calls its loadGroup method, finds the map of the path according to the group, and finally gets the relevant class to perform the jump.
In order to improve the efficiency of operation, two LruCache classes are used to cache group classes and path classes, which are fetched first, but not created.

When we jump in activity, we sometimes pass parameters, so in the previous code, we created a class Bundle Manager to manage parameters and encapsulate parameters into Bundle. The Bundle Manager class is very simple to encapsulate Bundle.

public class BundleManager {

    private Bundle bundle = new Bundle();
    private boolean isResult;

    public Bundle getBundle() {
        return bundle;
    }

    public boolean isResult() {
        return isResult;
    }

    public BundleManager withString(@NonNull String key, @Nullable String value) {
        bundle.putString(key, value);
        return this;
    }

    public BundleManager withResultString(@NonNull String key, @Nullable String value) {
        bundle.putString(key, value);
        isResult = true;
        return this;
    }
    public BundleManager withBoolean(@NonNull String key, boolean value) {
        bundle.putBoolean(key, value);
        return this;
    }
    public BundleManager withInt(@NonNull String key, int value) {
        bundle.putInt(key, value);
        return this;
    }

    public BundleManager withBundle(@NonNull Bundle bundle) {
        this.bundle = bundle;
        return this;
    }

    public Object navigation(Context context) {
       return RouterManager.getInstance().navigation(context,this,-1);
    }
    public Object navigation(Context context,int code) {
       return RouterManager.getInstance().navigation(context,this,code);
    }
}

OK to here a simple jump routing is completed.

Keywords: Java Gradle Google encoding

Added by mrmachoman on Wed, 24 Jul 2019 12:36:05 +0300