• ARouter 面试题


    序、时间于同样的方式留经我们每个人,每个人却以不同的方式对待时间。

    前言

    一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

    ARouter

    1.ARouter启动优化

    在做启动优化的时候,发现第一次启动用用时,ARouter初始化耗费了2s时间。查询优化方案时,发现只需要通过一个插件就可以解决了。

    通过 gradle插件进行自动注册,可以缩短初始化时间,解决应用加固导致无法直接访问dex文件

    1. apply plugin: 'com.alibaba.arouter'
    2. buildscript {
    3.    repositories {
    4.        mavenCentral()
    5.   }
    6.    dependencies {
    7.        classpath "com.alibaba:arouter-register:?"
    8.   }
    9. }

    1.1为什么启动这么耗时

    初始化入口
    1. public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    2.  // 判断是不是通过`arouter-register`插件自动加载路由表
    3.  loadRouterMap();
    4.  if (registerByPlugin) {
    5.    // 1.插件逻辑
    6. }else{
    7.    // 2.非插件逻辑
    8.    Set routerMap;
    9.    // 获取到apk中前缀为com.alibaba.android.arouter.routes的类
    10.    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    11.    // 加载前缀为com.alibaba.android.arouter.routes的class,放到set集合里面
    12.    if (!routerMap.isEmpty()) {
    13.          context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).
    14.            edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
    15.   }
    16.    // 将不同前缀的class类放到不同路径下下
    17.    for (String className : routerMap) {
    18.       if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
    19.          // This one of root elements, load root.
    20.         ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
    21.       } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
    22.       // Load interceptorMeta
    23.         ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
    24.       } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
    25.       // Load providerIndex
    26.         ((IProviderGroup)(Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
    27.       }
    28.     }
    29. }
    30. }

    loadRouterMap方法判断是不是通过arouter-register自动加载路由表,如果是通过自动加载的则registerByPlugin=true,这里先不关心通过arouter-register自动加载的方式。

    非插件方式
    1. public static Set getFileNameByPackageName(Context context, final String packageName){
    2.  final Set classNames = new HashSet<>();
    3.  // 获取所有的dex文件路径
    4.  List paths = getSourcePaths(context);
    5.  final CountDownLatch parserCtl = new CountDownLatch(paths.size());
    6.  // ⭐️开启线程池扫描dex文件
    7.  for (final String path : paths) {
    8.    DefaultPoolExecutor.getInstance().execute(new Runnable() {
    9.      @Override
    10.      public void run() {
    11.        DexFile dexfile = null;
    12.        // EXTRACTED_SUFFIX = ".zip";
    13.        if (path.endsWith(EXTRACTED_SUFFIX)) {
    14.          dexfile = DexFile.loadDex(path, path + ".tmp", 0);
    15.       } else {
    16.          dexfile = new DexFile(path);
    17.       }
    18.        Enumeration dexEntries = dexfile.entries();
    19.        while (dexEntries.hasMoreElements()) {
    20.          String className = dexEntries.nextElement();
    21.          if (className.startsWith(packageName)) {
    22.              classNames.add(className);
    23.         }
    24.       }
    25.     }
    26.   });
    27. }
    28.  parserCtl.await();
    29. }

    开启一个线程池取扫描dex文件,线程池的配置:

    1. private static final int INIT_THREAD_COUNT = CPU_COUNT + 1;
    2. private static final int MAX_THREAD_COUNT = INIT_THREAD_COUNT;
    3. private static final long SURPLUS_THREAD_LIFE = 30L;
    4. public class DefaultPoolExecutor extends ThreadPoolExecutor {
    5.    public static DefaultPoolExecutor getInstance() {
    6.        if (null == instance) {
    7.            synchronized (DefaultPoolExecutor.class) {
    8.                if (null == instance) {
    9.                    // 核心线程数是cpu个数+1;最大线程=核心线程;工作队列最多是64个任务的阻塞队列
    10.                    instance = new DefaultPoolExecutor(
    11.                            INIT_THREAD_COUNT,
    12.                            MAX_THREAD_COUNT,
    13.                            SURPLUS_THREAD_LIFE,
    14.                            TimeUnit.SECONDS,
    15.                            new ArrayBlockingQueue(64),
    16.                            new DefaultThreadFactory());
    17.               }
    18.           }
    19.       }
    20.        return instance;
    21.   }
    22. }

    该线程池配置的线程个数和cpu个数相关联,cpu个数多,可以启动的线程数就多,那么扫描dex文件的速度就快,整个处理时间相对就短。

    在高端多核心机器这么做速度还算快,但在低端机上就放大了该问题,尤其是对一些大的项目,它的dex文件多,再加上cpu性能差,整个耗时就更长了,对于初次启动的应用非常不友好。

    使用插件优化

    通过 ARouter提供的注册插件进行路由表的自动加载,通过 gradle插件进行自动注册可以缩短初始化时间解决应用加固导致无法直接访问 dex文件。初始化失败的问题,需要注意的是,该插件必须搭配 api 1.3.0 以上版本使用!

    2.如果我们注解相同的path会怎么样?即有一个SecondActivity使用/a/b的path,而另一个ThirdActivity也使用/a/b的path,那么编译通得过吗?如果通得过的话,通过path获取的又是哪一个Activity呢?

    我们知道apt框架是分module来进行处理的,因此我们也把问题分为在同一个module下和在不同module下:

    1、如果SecondActivity和ThirdActivity在同一个module下:

    RouteProcessor有一个成员变量groupMap,groupMap对生成ARouterGroup

    Group
    group_name文件起到了非常重要的作用。
    private Map> groupMap = new HashMap<>();
    

    groupMap的key是string,即group的名字,value是一个Set,我们都知道Set的一个特性,当试图放入一个Set中已有的元素时,会放入不了,并且不会抛异常。由此我们猜测,如果我们在同一个module中注解相同的path,那么排在字母表后面的元素会无效。即如果有一个SecondActivity使用/a/b的path,而另一个ThirdActivity也使用/a/b的path。那么ARouter生成的group类文件将会是下面这样,ThirdActivity由于没有被添加到Set中因此不会再生成的文件中出现:

    1. public class ARouter$$Group$$a implements IRouteGroup {
    2. @Override
    3. public void loadInto(Map atlas) {
    4. atlas.put("/a/b", RouteMeta.build(RouteType.ACTIVITY, SecondActivity.class, "/a/b", "a", null, -1, -2147483648));
    5. }
    6. }

    2、如果SecondActivity和ThirdActivity在不同的module下:

    由于apt框架是分module编译,并且每个module都会生成ARouterRoot$modulename,ARouter

    Root$modulename,ARouter
    GroupgroupnameSecondActivityThirdActivityARouter
    Group$$a的文件,那么在合并到dex的时候肯定会出错,事实上也是这样的。

    参考

    ARouter启动优化引发的探索 - 掘金

    阿里ARouter全面全面全面解析(使用介绍+源码分析+设计思路)_arouter init-CSDN博客

  • 相关阅读:
    AlGaN/GaN HFET 五参数模型
    规则调优必备技能——捞取更多好人,卡住更多坏人
    【C语言】C语言从入门到精通 | 第3章 实践与练习以及答案
    释放数据生产力,数据治理要“即时”
    QT+OSG/osgEarth编译之十七:proj+Qt编译(一套代码、一套框架,跨平台编译,版本:通用坐标转换库proj-9.1.0)
    Lnmp架构之mysql数据库实战2
    MySQL数据类型介绍——小数型,字符串和日期类型
    [C++随想录] 二叉搜索树
    linux入门---命名管道
    IO作业:1. 用fgets计算一个文件的大小 2. 用fgets计算一个文件有几行;提示:计算一个文件有几个‘\n‘
  • 原文地址:https://blog.csdn.net/qq_37492806/article/details/133860478