面试官,Android 怎样实现 Router 框架?(一)

简介: 面试官,Android 怎样实现 Router 框架?

Android 开发中,组件化,模块化是一个老生常谈的问题。随着项目复杂性的增长,模块化是一个必然的趋势。除非你能忍受改一下代码,就需要六七分钟的漫长时间。


模块化,组件化随之带来的另外一个问题是页面的跳转问题,由于代码的隔离,代码之间有时候会无法互相访问。于是,路由(Router)框架诞生了。


目前用得比较多的有阿里的 ARouter,美团的 WMRouter,ActivityRouter 等。


今天,就让我们一起来看一下怎样实现一个路由框架。

实现的功能有。


  1. 基于编译时注解,使用方便
  2. 结果回调,每次跳转 Activity 都会回调跳转结果
  3. 除了可以使用注解自定义路由,还支持手动分配路由
  4. 支持多模块使用,支持组件化使用


使用说明


基本使用


第一步,在要跳转的 activity 上面注明 path,


@Route(path = "activity/main")
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

在要跳转的地方


Router.getInstance().build("activity/main").navigation(this);


如果想在多 moule 中使用


第一步,使用 @Modules({"app", "sdk"}) 注明总共有多少个 moudle,并分别在 moudle 中注明当前 moudle 的 名字,使用 @Module("") 注解。注意 @Modules({“app”, “sdk”}) 要与 @Module(“”) 一一对应。


在主 moudle 中,


@Modules({"app", "moudle1"})
@Module("app")
public class RouterApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Router.getInstance().init();
    }
}

在 moudle1 中,


@Route(path = "my/activity/main")
@Module("moudle1")
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_2);
    }
}

这样就可以支持多模块使用了。


自定义注入 router


Router.getInstance().add("activity/three", ThreeActivity.class);


跳转的时候调用


Router.getInstance().build("activity/three").navigation(this);


结果回调


路由跳转结果回调。


Router.getInstance().build("my/activity/main", new RouterCallback() {
    @Override
    public boolean beforeOpen(Context context, Uri uri) { 
    // 在打开路由之前
        Log.i(TAG, "beforeOpen: uri=" + uri);
        return false;
    }
   // 在打开路由之后(即打开路由成功之后会回调)
    @Override
    public void afterOpen(Context context, Uri uri) {
        Log.i(TAG, "afterOpen: uri=" + uri);
    }
    // 没有找到改 uri
    @Override
    public void notFind(Context context, Uri uri) {
        Log.i(TAG, "notFind: uri=" + uri);
    }
    // 发生错误
    @Override
    public void error(Context context, Uri uri, Throwable e) {
        Log.i(TAG, "error: uri=" + uri + ";e=" + e);
    }
}).navigation(this);

startActivityForResult 跳转结果回调

Router.getInstance().build("activity/two").navigation(this, new Callback() {
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.i(TAG, "onActivityResult: requestCode=" + requestCode + ";resultCode=" + resultCode + ";data=" + data);
    }
});

原理说明


实现一个 Router 框架,涉及到的主要的知识点如下:


  1. 注解的处理
  2. 怎样解决多个 module 之间的依赖问题,以及如何支持多 module 使用
  3. router 跳转及 activty startActivityForResult 的处理


我们带着这三个问题,一起来探索一下。


总共分为四个部分,router-annotion, router-compiler,router-api,stub


86eb0a5c81ad84218fad1148617df9cf_format,png.png


router-annotion 主要是定义注解的,用来存放注解文件


router-compiler 主要是用来处理注解的,自动帮我们生成代码


router-api 是对外的 api,用来处理跳转的。


stub 这个是存放一些空的 java 文件,提前占坑。不会打包进 jar。


router-annotion


主要定义了三个注解


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
    String path();
}
@Retention(RetentionPolicy.CLASS)
public @interface Modules {
    String[] value();
}
@Retention(RetentionPolicy.CLASS)
public @interface Module {
    String value();
}

Route 注解主要是用来注明跳转的 path 的。


Modules 注解,注明总共有多少个 moudle。


Module 注解,注明当前 moudle 的名字。


Modules,Module 注解主要是为了解决支持多 module 使用的。


router-compiler


router-compiler 只有一个类 RouterProcessor,他的原理其实也是比较简单的,扫描那些类用到注解,并将这些信息存起来,做相应的处理。这里是会生成相应的 java 文件。


主要包括以下两个步骤


  1. 根据是否有 @Modules @Module 注解,然后生成相应的 RouterInit 文件
  2. 扫描 @Route 注解,并根据 moudleName 生成相应的 java 文件


注解基本介绍


在讲解 RouterProcessor 之前,我们先来了解一下注解的基本知识。


如果对于自定义注解还不熟悉的话,可以先看我之前写的这两篇文章。Android 自定义编译时注解1 - 简单的例子,Android 编译时注解 —— 语法详解


public class RouterProcessor extends AbstractProcessor {
    private static final boolean DEBUG = true;
    private Messager messager;
    private Filer mFiler;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        mFiler = processingEnv.getFiler();
        UtilManager.getMgr().init(processingEnv);
    }
    /**
     * 定义你的注解处理器注册到哪些注解上
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(Route.class.getCanonicalName());
        annotations.add(Module.class.getCanonicalName());
        annotations.add(Modules.class.getCanonicalName());
        return annotations;
    }
    /**
     * java版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

首先我们先来看一下 getSupportedAnnotationTypes 方法,这个方法返回的是我们支持扫描的注解。


注解的处理


接下来我们再一起来看一下 process 方法


@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   // 注解为 null,直接返回
   if (annotations == null || annotations.size() == 0) {
       return false;
   }
   UtilManager.getMgr().getMessager().printMessage(Diagnostic.Kind.NOTE, "process");
   boolean hasModule = false;
   boolean hasModules = false;
   // module
   String moduleName = "RouterMapping";
   Set<? extends Element> moduleList = roundEnv.getElementsAnnotatedWith(Module.class);
   if (moduleList != null && moduleList.size() > 0) {
       Module annotation = moduleList.iterator().next().getAnnotation(Module.class);
       moduleName = moduleName + "_" + annotation.value();
       hasModule = true;
   }
   // modules
   String[] moduleNames = null;
   Set<? extends Element> modulesList = roundEnv.getElementsAnnotatedWith(Modules.class);
   if (modulesList != null && modulesList.size() > 0) {
       Element modules = modulesList.iterator().next();
       moduleNames = modules.getAnnotation(Modules.class).value();
       hasModules = true;
   }
   debug("generate modules RouterInit annotations=" + annotations + " roundEnv=" + roundEnv);
   debug("generate modules RouterInit hasModules=" + hasModules + " hasModule=" + hasModule);
   // RouterInit
   if (hasModules) { // 有使用 @Modules 注解,生成 RouterInit 文件,适用于多个 moudle
       debug("generate modules RouterInit");
       generateModulesRouterInit(moduleNames);
   } else if (!hasModule) { // 没有使用 @Modules 注解,并且有使用 @Module,生成相应的 RouterInit 文件,使用与单个 moudle
       debug("generate default RouterInit");
       generateDefaultRouterInit();
   }
   // 扫描 Route 注解
   Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
   List<TargetInfo> targetInfos = new ArrayList<>();
   for (Element element : elements) {
       System.out.println("elements =" + elements);
       // 检查类型
       if (!Utils.checkTypeValid(element)) continue;
       TypeElement typeElement = (TypeElement) element;
       Route route = typeElement.getAnnotation(Route.class);
       targetInfos.add(new TargetInfo(typeElement, route.path()));
   }
   // 根据 module 名字生成相应的 java 文件
   if (!targetInfos.isEmpty()) {
       generateCode(targetInfos, moduleName);
   }
   return false;
}

,首先判断是否有注解需要处理,没有的话直接返回 annotations == null || annotations.size() == 0 。


接着我们会判断是否有 @Modules 注解(这种情况是多个 moudle 使用),有的话会调用 generateModulesRouterInit(String[] moduleNames) 方法生成 RouterInit java 文件,当没有 @Modules 注解,并且没有 @Module (这种情况是单个 moudle 使用),会生成默认的 RouterInit 文件。


private void generateModulesRouterInit(String[] moduleNames) {
   MethodSpec.Builder initMethod = MethodSpec.methodBuilder("init")
           .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
   for (String module : moduleNames) {
       initMethod.addStatement("RouterMapping_" + module + ".map()");
   }
   TypeSpec routerInit = TypeSpec.classBuilder("RouterInit")
           .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
           .addMethod(initMethod.build())
           .build();
   try {
       JavaFile.builder(Constants.ROUTE_CLASS_PACKAGE, routerInit)
               .build()
               .writeTo(mFiler);
   } catch (Exception e) {
       e.printStackTrace();
   }
}

假设说我们有"app",“moudle1” 两个 moudle,那么我们最终生成的代码是这样的。


public final class RouterInit {
  public static final void init() {
    RouterMapping_app.map();
    RouterMapping_moudle1.map();
  }
}

如果我们都没有使用 @Moudles 和 @Module 注解,那么生成的 RouterInit 文件大概是这样的。


public final class RouterInit {
  public static final void init() {
    RouterMapping.map();
  }
}

这也就是为什么有 stub module 的原因。因为默认情况下,我们需要借助 RouterInit 去初始化 map。如果没有这两个文件,ide 编辑器 在 compile 的时候就会报错。


compileOnly project(path: ':stub')


3da2bff2d13e40bcb8fd61cea9236c83_format,png.png


我们引入的方式是使用 compileOnly,这样的话再生成 jar 的时候,不会包括这两个文件,但是可以在 ide 编辑器中运行。这也是一个小技巧。


相关文章
|
3月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
4月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台框架解析
在移动应用开发的广阔舞台上,安卓和iOS一直是两大主角。随着技术的进步,开发者们渴望能有一种方式,让他们的应用能同时在这两大平台上运行,而不必为每一个平台单独编写代码。这就是跨平台框架诞生的背景。本文将探讨几种流行的跨平台框架,包括它们的优势、局限性,以及如何根据项目需求选择合适的框架。我们将从技术的深度和广度两个维度,对这些框架进行比较分析,旨在为开发者提供一个清晰的指南,帮助他们在安卓和iOS的开发旅程中,做出明智的选择。
|
13天前
|
算法 JavaScript Android开发
|
22天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
88 1
|
2月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
45 2
|
2月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
346 3
|
1月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
24 0
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
77 8
|
3月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
3月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
如何使用Amlogic T972安卓9.0系统上的misc框架来简化驱动程序开发,通过misc框架自动分配设备号并创建设备文件,从而减少代码量并避免设备号冲突。
43 0
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单