Android 框架设计与初始化与配置

01.看阿里ARouter整体设计

  • ARouter主要由三部分组成,包括对外提供的api调用模块、注解模块以及编译时通过注解生产相关的类模块。
    • arouter-annotation注解的声明和信息存储类的模块
    • arouter-compiler编译期解析注解信息并生成相应类以便进行注入的模块
    • arouter-api核心调用Api功能的模块
  • annotation模块
    • Route、Interceptor、Autowired都是在开发是需要的注解。
    • image
  • compiler模块
    • AutoWiredProcessor、InterceptorProcessor、RouteProcessor分别为annotation模块对应的Autowired、Interceptor、Route在项目编译时产生相关的类文件。
  • api模块
    • 主要是ARouter具体实现和对外暴露使用的api。

02.这样设计框架目的

  • 路由框架作用的两个时期,第一个时期是编译期,第二个时期是app运行的时期。
  • 编译期是在你的项目编译的时候,这个时候还没有开始打包,也就是你没有生成apk呢!路由框架在这个时期根据注解去扫描所有文件,然后生成路由映射文件。这些文件都会统一打包到apk里,app运行时期做的东西也不少,但总而言之都是对映射信息的处理,如执行执行路由跳转等。

03.仿照ARouter设计自己框架

  • 将框架从功能角度分成了三个library
  • 第一个是注解模块,主要放置注解,
  • 第二个是compiler模块,里面存放着注解处理器和处理注解的工具类,
  • 第三个是路由框架的核心,存放着路由的api等等。
  • 其中,AptCompiler 模块和 AptApi 模块都依赖了 AptAnnotation 模块,因为它们都需要用到注解。AptCompiler 和 AptApi 没有依赖关系。
  • 为啥这样设计,因为是参考阿里ARouter路由的框架设计。

04.为何需要初始化操作

  • 首先需要在用户使用路由跳转之前把这些路由映射关系拿到手,拿到这些路由关系最好的时机就是应用程序初始化的时候,通过apt生成的路由映射关系文件,在程序启动的时候扫描这些生成的类文件,然后获取到映射关系信息,保存起来。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class ARouter_Root_app implements IRouteRoot {
    @Override
    public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("main", ARouter_Group_main.class);
    routes.put("yc", ARouter_Group_yc.class);
    }
    }

    public class ARouter_Group_main implements IRouteGroup {
    @Override
    public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/main/FiveActivity",RouteMeta.build(RouteMeta.Type.ACTIVITY,FiveActivity.class,"/main/FiveActivity","main"));
    }
    }

    public class ARouter_Group_yc implements IRouteGroup {
    @Override
    public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/yc/SixActivity",RouteMeta.build(RouteMeta.Type.ACTIVITY,SixActivity.class,"/yc/SixActivity","yc"));
    }
    }
  • 如图所示

    • image
  • 得出结论

    • 实现了IRouteRoot接口的类都是保存了group分组映射信息,实现了IRouteGroup接口的类都保存了单个分组下的路由映射信息。

05.初始化操作逻辑

  • 上面已经说了,在启动的时候初始化,并且拿到这些类是最好的。看一下初始化的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void init(Application application) {
    mContext = application;
    try {
    loadInfo();
    } catch (Exception e) {
    e.printStackTrace();
    Log.e(TAG, "初始化失败!", e);
    }
    }
  • 在初始化的时候,得到实现IRouteRoot接口的所有类文件,便能通过循环调用它的loadInfo()方法得到所有实现IRouteGroup接口的类,而所有实现IRouteGroup接口的类里面保存了项目的所有路由信息。IRouteGroup的loadInfo()方法,通过传入一个map,便会将这个分组里的映射信息存入map里。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * 分组表制作
    */
    private static void loadInfo() throws PackageManager.NameNotFoundException,
    InterruptedException, ClassNotFoundException,
    NoSuchMethodException, IllegalAccessException,
    InvocationTargetException, InstantiationException {
    //获得所有 apt生成的路由类的全类名 (路由表)
    Set<String> routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PACKAGE);
    for (String className : routerMap) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(ROUTE_ROOT_PACKAGE);
    stringBuilder.append(".");
    stringBuilder.append(SDK_NAME);
    stringBuilder.append(SEPARATOR);
    stringBuilder.append(SUFFIX_ROOT);
    String s = stringBuilder.toString();
    Log.d(TAG,"className-----------"+s);
    //root中注册的是分组信息 将分组信息加入仓库中
    if (className.startsWith(s)) {
    //拿到反射字节码
    Class<?> aClass = Class.forName(className);
    //获取对象
    Object o = aClass.getConstructor().newInstance();
    ((IRouteRoot) o).loadInto(Warehouse.groupsIndex);
    }
    }

    Set<Map.Entry<String, Class<? extends IRouteGroup>>> entries =
    Warehouse.groupsIndex.entrySet();
    for (Map.Entry<String, Class<? extends IRouteGroup>> stringClassEntry : entries) {
    Log.d(TAG, "Root映射表[ " + stringClassEntry.getKey() + " : "
    + stringClassEntry.getValue() + "]");
    }
    }
  • 打印映射表日志可知

    • map集合key:”/yc/SixActivity”
    • map集合value:RouteMeta.build(RouteMeta.Type.ACTIVITY,SixActivity.class,”/yc/SixActivity”,”yc”)
  • 看一下RouteMeta.build源码

    • RouteMeta里面便保存着ActivityClass的所有信息。第一个功能需求,便是在app进程启动的时候进行框架的初始化(或者在你开始用路由跳转之前进行初始化都可以),在初始化中拿到映射关系信息,保存在map里,以便程序运行中可以快速找到路由映射信息实现跳转。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static RouteMeta build(Type type, Class<?> destination, String path, String group) {
    return new RouteMeta(type, null, destination, path, group);
    }

    private RouteMeta(Type type, Element element, Class<?> destination, String path, String group) {
    this.type = type;
    this.destination = destination;
    this.element = element;
    this.path = path;
    this.group = group;
    }

06.如何得到得到路由表的类名

  • 通过ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE)得到apt生成的所有实现IRouteRoot接口的类文件集合

  • 拿到这些类后,便可以得到所有的routerAddress—activityClass映射关系。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    /**
    * 得到路由表的类名
    * @param context 上下文
    * @param packageName 包名
    * @return 集合
    * @throws PackageManager.NameNotFoundException
    * @throws InterruptedException
    */
    public static Set<String> getFileNameByPackageName(Application context,
    @NonNull final String packageName)
    throws PackageManager.NameNotFoundException, InterruptedException {
    //创建一个set集合,set集合元素不会重复
    final Set<String> classNames = new HashSet<>();
    //获得程序所有的apk
    List<String> paths = getSourcePaths(context);
    //使用同步计数器判断均处理完成
    final CountDownLatch countDownLatch = new CountDownLatch(paths.size());
    //创建线程池
    ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor
    .newDefaultPoolExecutor(paths.size());
    for (final String path : paths) {
    if (threadPoolExecutor != null) {
    threadPoolExecutor.execute(new Runnable() {
    @Override
    public void run() {
    DexFile dexFile = null;
    try {
    //加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类
    dexFile = new DexFile(path);
    Enumeration<String> dexEntries = dexFile.entries();
    while (dexEntries.hasMoreElements()) {
    String className = dexEntries.nextElement();
    if (!TextUtils.isEmpty(className) &&
    className.startsWith(packageName)) {
    classNames.add(className);
    }
    }
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    if (null != dexFile) {
    try {
    dexFile.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    //释放一个
    countDownLatch.countDown();
    }
    }
    });
    }
    }
    //等待执行完成
    countDownLatch.await();
    return classNames;
    }