我们在前面两篇博客学习了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中心管理类
【根据包名反射生成并做缓存】,完成对应路由跳转;
首先,我们依照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
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 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;
}
}
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:
最后附上github源码链接:https://github.com/ULooper/DXRouter
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )