ARouter 面试题

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

前言

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

ARouter

1.ARouter启动优化

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

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

apply plugin: 'com.alibaba.arouter'
​
buildscript {
    repositories {
        mavenCentral()
    }
​
    dependencies {
        classpath "com.alibaba:arouter-register:?"
    }
}

1.1为什么启动这么耗时

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

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

非插件方式
public static Set<String> getFileNameByPackageName(Context context, final String packageName){
  final Set<String> classNames = new HashSet<>();
  // 获取所有的dex文件路径
  List<String> paths = getSourcePaths(context);
  final CountDownLatch parserCtl = new CountDownLatch(paths.size());
  // ⭐️开启线程池扫描dex文件
  for (final String path : paths) {
    DefaultPoolExecutor.getInstance().execute(new Runnable() {
      @Override
      public void run() {
        DexFile dexfile = null;
        //  EXTRACTED_SUFFIX = ".zip";
        if (path.endsWith(EXTRACTED_SUFFIX)) {
          dexfile = DexFile.loadDex(path, path + ".tmp", 0);
        } else {
          dexfile = new DexFile(path);
        }
        Enumeration<String> dexEntries = dexfile.entries();
        while (dexEntries.hasMoreElements()) {
          String className = dexEntries.nextElement();
          if (className.startsWith(packageName)) {
              classNames.add(className);
          }
        }
      }
    });
  }
  parserCtl.await();
}

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

private static final int INIT_THREAD_COUNT = CPU_COUNT + 1;
private static final int MAX_THREAD_COUNT = INIT_THREAD_COUNT;
private static final long SURPLUS_THREAD_LIFE = 30L;
public class DefaultPoolExecutor extends ThreadPoolExecutor {
    public static DefaultPoolExecutor getInstance() {
        if (null == instance) {
            synchronized (DefaultPoolExecutor.class) {
                if (null == instance) {
                    // 核心线程数是cpu个数+1;最大线程=核心线程;工作队列最多是64个任务的阻塞队列
                    instance = new DefaultPoolExecutor(
                            INIT_THREAD_COUNT,
                            MAX_THREAD_COUNT,
                            SURPLUS_THREAD_LIFE,
                            TimeUnit.SECONDS,
                            new ArrayBlockingQueue<Runnable>(64),
                            new DefaultThreadFactory());
                }
            }
        }
        return instance;
    }
}



该线程池配置的线程个数和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对生成ARouter$$Group$$group_name文件起到了非常重要的作用。

private Map<String, Set<RouteMeta>> 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中因此不会再生成的文件中出现:

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

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

由于apt框架是分module编译,并且每个module都会生成ARouter$$Root$module_name,ARouter$$Group$$group_name等文件,那么毫无疑问,会生成两个相同的文件。还是以上面的SecondActivity和ThirdActivity为例子,那么它们都会生成ARouter$$Group$$a的文件,那么在合并到dex的时候肯定会出错,事实上也是这样的。

参考

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值