ARouter原理解析之自定义路由框架DXRouter

前言

我们在前面两篇博客学习了ARouter的实现原理,那我们能否参考ARouter的实现,自己动手实现一套简单的路由框架呢?这一篇我们就尝试仿照ARouter,自定义一个属于我们自己的路由框架~~ DXRouter;
ARouter原理解析之注解处理器
ARouter原理解析之路由跳转

浅谈目标以及实现方式

目标:通过自定义注解实现各个模块间activity跳转以及数据传输
实现方式:
1.各个模块定义属于自己的Module中心统一管理activity跳转逻辑,并在注解上添加各个activity的路由地址,例如:

@DXRouter({"/app/activity1","/app/activity2"})
public class MainModule extends BaseModule {

    private static Map<String, Class> activityMaps = new HashMap<>();

    static {
        activityMaps.put("/app/activity1", MainActivity.class);
        activityMaps.put("/app/activity2", Main2Activity.class);
    }
	//route中完成具体的activity跳转功能
    @Override
    public void route(Context context, String path, Bundle bundle, int requestCode) {
        Class clazz = activityMaps.get(path);
        if (clazz != null) {
            Intent intent = new Intent(context, clazz);
            intent.putExtras(bundle);
            if (requestCode > 0 && context instanceof Activity) {
                ((Activity) context).startActivityForResult(intent, requestCode);
            } else {
                context.startActivity(intent);
            }
        }
    }
}

其中BaseModule

public abstract class BaseModule {

    /**
     * 跳转逻辑处理
     * @param context 上下文
     * @param path 路由path
     * @param bundle 携带参数
     * @param requestCode requestCode
     */
    public abstract void route(Context context, String path, Bundle bundle, int requestCode);
}

2.注解解析器根据注解动态生成各模块的modulemap集合,apt生成代码如下:

public class DXRouter$$app$$ModuleMaps implements DXRouterPath {
  @Override
  public Map<String, String> getModuleMaps() {
    Map<String,String> moduleMaps= new HashMap<>();
    moduleMaps.put("/app/activity1","com.dongxian.dxrouter.MainModule");
    moduleMaps.put("/app/activity2","com.dongxian.dxrouter.MainModule");
    return moduleMaps;
  }
}

3.发起路由跳转时,通过传入的path查找对应的module中心管理类【根据包名反射生成并做缓存】,完成对应路由跳转;

自定义DXRouter框架

自定义DXRouter注解以及注解解析器

首先,我们依照ARouter一样,编写我们自己的DXRouter注解,比较简单;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DXRouter {
    String[] value() default {};
}

接着,我们来实现注解解析器DXRouterProcessor,我们目标是在各个模块下生成文件,这里文件路径我们统一固定为com.dongxian.dxrouter_api.DXRouter$$【模块名】$$ModuleMaps文件名固定为DXRouter$$+【模块名】+$$ModuleMaps,通过模块进行组划分以及分模块进行缓存查找;

我们先看下DXRouterPath接口定义,主要是为了后续通过反射强转成DXRouterPath方便getModuleMaps方法调用;

public interface DXRouterPath {

    Map<String,String> getModuleMaps();
}

我们简单看下DXRouterProcessor的实现代码,这里使用JavaPoet技术来完成文件写入,相关学习可以参考JavaPoet使用攻略

/**
 * 路由注解解析器,用于解析自定义DXRouter
 *
 * @author DongXian
 * on 2022/7/26
 */
@AutoService(Processor.class)
@SupportedAnnotationTypes(DX_ROUTER_ANNOTATION_PACKAGE_NAME)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class DXRouterProcessor extends AbstractProcessor {
    /**
     * 路由管理类基类
     */
    protected final String packageName = "com.dongxian.dxrouter_api.BaseModule";

    private final String TAG = DXRouterProcessor.class.getSimpleName();
    /**
     * 操作Element的工具类(类、函数、属性等对应的都是element)
     */
    private Elements elements;
    /**
     * type(类信息)的工具类,包含用于操作TypeMirror
     */
    private Types types;
    /**
     * 日志打印工具类
     */
    private Messager messager;
    /**
     * 文件写操作
     */
    private Filer filer;

    /**
     * module集合map
     */
    private Map<String, List<String>> moduleMaps = new HashMap<>();


    /**
     * 各个模块传递过来的模块名,如app、order、personal
     */
    private String options;

    /**
     * 初始化准备工作
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elements = processingEnv.getElementUtils();
        types = processingEnv.getTypeUtils();
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
        options = processingEnv.getOptions().get(OPTIONS);
        messager.printMessage(Diagnostic.Kind.NOTE, TAG + " init");
    }

    /**
     * 注解处理
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) {
            return false;
        }
        //获取DXRouter标注的类
        Set<? extends TypeElement> routesElements = (Set<? extends TypeElement>) roundEnvironment.getElementsAnnotatedWith(DXRouter.class);
        for (Element element : routesElements) {
            TypeElement typeElement = (TypeElement) element;
            String clazzName = typeElement.getQualifiedName().toString();
            if (isExtendsParentClass(typeElement, packageName)) {
                createModulesFile(typeElement, clazzName);
            } else {
                messager.printMessage(Diagnostic.Kind.ERROR, "The class:" + clazzName + "should extends " + packageName);
            }
        }

        return false;
    }

    /**
     * 生成modules map集合文件
     *
     * @param typeElement
     * @param clazzName
     */
    private void createModulesFile(TypeElement typeElement, String clazzName) {
        List<String> paths = new ArrayList<>();
        DXRouter dxRouter = typeElement.getAnnotation(DXRouter.class);
        String[] value = dxRouter.value();
        for (String path : value) {
            if (isLegalPath(path)) {
                paths.add(path.trim());
            }
        }
        moduleMaps.put(clazzName, paths);
        //返回类型Map<String,String>
        TypeName methodReturn = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(String.class)
        );
        //方法名
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getModuleMaps")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .returns(methodReturn);

        //代码块 Map<String,String> moduleMaps= new HashMap<>();
        methodBuilder.addStatement("$T<$T,$T> $N= new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(String.class),
                MODULE_MAPS,
                ClassName.get(HashMap.class));
        for (Map.Entry<String, List<String>> entry : moduleMaps.entrySet()) {
            for (String moduleName : entry.getValue()) {
                methodBuilder.addStatement("$N.put($S,$S)",
                        MODULE_MAPS,
                        moduleName,
                        entry.getKey()
                );
            }

        }
        //return modulesMaps
        methodBuilder.addStatement("return $N", MODULE_MAPS);
        TypeElement pathTypeElement = elements.getTypeElement(DXROUTER_API_PATH);
        //生成文件
        try {
            //生成文件路径固定写死为 com.dongxian.dxrouter_api.DXRouter$$[模块名称]$$ModuleMaps
            JavaFile.builder(MODULE_MAPS_FILE_PACKAGE_NAME,
                    TypeSpec.classBuilder(MODULE_MAPS_FILE_NAME + options + "$$ModuleMaps")
                            .addSuperinterface(ClassName.get(pathTypeElement))
                            .addModifiers(Modifier.PUBLIC)
                            .addMethod(methodBuilder.build())
                            .build()
            ).build().writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.ERROR, "file write exception:" + e.getMessage());
        }
    }


    /**
     * 查看element是否继承或实现指定的parent
     *
     * @param element
     * @param parent
     * @return
     */
    private boolean isExtendsParentClass(TypeElement element, String parent) {
        TypeElement parentType = elements.getTypeElement(parent);
        if (parentType == null) {
            messager.printMessage(Diagnostic.Kind.WARNING, TAG + element.getSimpleName() + "don't find parent" + parent);
            return false;
        }
        return types.isAssignable(element.asType(), parentType.asType());
    }

    /**
     * 检查path路径是否符合/app/activity格式要求
     *
     * @param path
     * @return
     */
    private boolean isLegalPath(String path) {
        if (StringUtils.isEmpty(path) || !path.startsWith("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "The" + path + "is illegal,should be like /app/activity");
            return false;
        }

        if (path.lastIndexOf("/") == 0) {
            messager.printMessage(Diagnostic.Kind.ERROR, "The" + path + "is illegal,should be like /app/activity");
            return false;
        }
        //截取出group字段
        String finalGroup = path.substring(1, path.indexOf("/", 1));
        if (StringUtils.isEmpty(finalGroup) || !finalGroup.equals(options)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "The " + finalGroup + " should be equals " + options);
            return false;
        }
        return true;

    }
}

DXRouter核心路由类封装【dxrouter_api模块】

public final class DXRouter {
    private final String TAG = DXRouter.class.getSimpleName();
    /**
     * 跳转路径前缀
     */
    private final String TARGET_PREFIX = "com.dongxian.dxrouter_api.DXRouter$$";
    /**
     * 跳转路径后缀
     */
    private final String TARGET_SUFFIX = "$$ModuleMaps";

    private static volatile DXRouter instance;
    private Map<String, Map<String, String>> groupMap;
    private Map<String, BaseModule> moduleMap;

    private DXRouter() {
        groupMap = new HashMap<>(20);
        moduleMap = new HashMap<>(100);
    }

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

    /**
     * 携带Bundle数据传输
     *
     * @param path    目标
     * @param context 上下文参数
     * @param bundle  携带数据
     */
    public void navigation(String path, Context context, Bundle bundle) {
        if (bundle == null) {
            bundle = new Bundle();
        }
        this.navigation(context, path, bundle, -1);
    }


    /**
     * 直接跳转
     *
     * @param path    目标
     * @param context 上下文参数
     */
    public void navigation(String path, Context context) {
        this.navigation(context, path, new Bundle(), -1);
    }


    /**
     * 携带Bundle以及requestCode跳转
     *
     * @param context
     * @param path
     * @param bundle
     * @param requestCode
     */
    public void navigation(Context context, String path, Bundle bundle, int requestCode) {
        if (!isLegalPath(path)) {
            //路径非法直接return
            Log.e(TAG, "The path:" + path + "is Illegal!!!");
            return;
        }
        //截图出group名称
        String finalGroup = path.substring(1, path.indexOf("/", 1));
        Map<String, String> moduleMaps = groupMap.get(finalGroup);
        if (moduleMaps == null) {
            //通过反射生成相关类
            String clazzName = TARGET_PREFIX + finalGroup + TARGET_SUFFIX;
            try {
                Class<?> moduleMap = Class.forName(clazzName);
                DXRouterPath dxRouterPath = (DXRouterPath) moduleMap.newInstance();
                moduleMaps = dxRouterPath.getModuleMaps();
                groupMap.put(finalGroup, moduleMaps);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (moduleMaps == null) {
            Log.e(TAG, "The groupMap is null");
            return;
        }
        String moduleName = moduleMaps.get(path);
        if (TextUtils.isEmpty(moduleName)) {
            Log.e(TAG, "No Find Module by The path:" + path);
            return;
        }
        BaseModule baseModule = moduleMap.get(moduleName);
        if (baseModule == null) {
            try {
                Class<?> module = Class.forName(moduleName);
                baseModule = (BaseModule) module.newInstance();
                moduleMap.put(moduleName, baseModule);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (baseModule == null) {
            Log.e(TAG, "Not Find Module by Path!!!");
            return;
        }
        if (bundle == null) {
            bundle = new Bundle();
        }
        baseModule.route(context, path, bundle, requestCode);
    }


    /**
     * 检查path路径是否符合/app/activity格式要求
     *
     * @param path
     * @return
     */
    private boolean isLegalPath(String path) {
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            return false;
        }

        if (path.lastIndexOf("/") == 0) {
            return false;
        }
        return true;

    }
}

这里使用groupMap以及moduleMap进行缓存,避免二次反射造成的性能损耗,同时也实现了分组按需加载,避免一次全部加载造成的内存浪费,当然上面也只是简单做了一层路由封装,有兴趣的小伙伴可以仿照ARouter_ARouter的装饰模式,进行二次封装,这里不再赘述;

功能验证

接下来我们编写测试代码,来验证我们自定义的DXRouter框架是否可以正常运行~~
1.app模块中相关代码;

### MainModule
@DXRouter({"/app/activity1","/app/activity2"})
public class MainModule extends BaseModule {

    private static Map<String, Class> activityMaps = new HashMap<>();

    static {
        activityMaps.put("/app/activity1", MainActivity.class);
        activityMaps.put("/app/activity2", Main2Activity.class);
    }

    @Override
    public void route(Context context, String path, Bundle bundle, int requestCode) {
        Class clazz = activityMaps.get(path);
        if (clazz != null) {
            Intent intent = new Intent(context, clazz);
            intent.putExtras(bundle);
            if (requestCode > 0 && context instanceof Activity) {
                ((Activity) context).startActivityForResult(intent, requestCode);
            } else {
                context.startActivity(intent);
            }
        }
    }
}

### MainActivity
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            	//携带一个name参数
                Bundle bundle = new Bundle();
                bundle.putString("name", "DXRouter");
                DXRouter.getInstance().navigation("/personal/activity2", MainActivity.this, bundle);
            }
        });

    }
}

personal模块相关代码;

### PersonalModule
@DXRouter({"/personal/activity2"})
public class PersonalModule extends BaseModule {

    private static Map<String, Class> activityMaps = new HashMap<>();

    static {
        activityMaps.put("/personal/activity2", PersonalActivity.class);
    }

    @Override
    public void route(Context context, String path, Bundle bundle, int requestCode) {
        Class clazz = activityMaps.get(path);
        if (clazz != null) {
            Intent intent = new Intent(context, clazz);
            intent.putExtras(bundle);
            if (requestCode > 0 && context instanceof Activity) {
                ((Activity) context).startActivityForResult(intent, requestCode);
            } else {
                context.startActivity(intent);
            }
        }
    }
}

### PersonalActivity
public class PersonalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_personal);
        TextView tv = findViewById(R.id.tv);
        Button btn = findViewById(R.id.btn);

        Bundle bundle = getIntent().getExtras();
        String name = bundle.getString("name");
        tv.setText("get Bundle value:" + name);

        btn.setOnClickListener(v -> {
            DXRouter.getInstance().navigation("/app/activity1", PersonalActivity.this);
        });
    }
}

最终效果gif:
DXRouter路由跳转效果

结语

最后附上github源码链接:https://github.com/ULooper/DXRouter
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值