序、时间于同样的方式留经我们每个人,每个人却以不同的方式对待时间。
一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
在做启动优化的时候,发现第一次启动用用时,ARouter初始化耗费了2s时间。查询优化方案时,发现只需要通过一个插件就可以解决了。
通过
gradle插件进行自动注册,可以缩短初始化时间,解决应用加固导致无法直接访问dex文件
- apply plugin: 'com.alibaba.arouter'
-
- buildscript {
- repositories {
- mavenCentral()
- }
-
- dependencies {
- classpath "com.alibaba:arouter-register:?"
- }
- }
- public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
- // 判断是不是通过`arouter-register`插件自动加载路由表
- loadRouterMap();
- if (registerByPlugin) {
- // 1.插件逻辑
- }else{
- // 2.非插件逻辑
- Set
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
getFileNameByPackageName(Context context, final String packageName){ - final Set
classNames = new HashSet<>(); - // 获取所有的dex文件路径
- List
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
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
(64), - new DefaultThreadFactory());
- }
- }
- }
- return instance;
- }
- }
-
-
-
该线程池配置的线程个数和cpu个数相关联,cpu个数多,可以启动的线程数就多,那么扫描dex文件的速度就快,整个处理时间相对就短。
在高端多核心机器这么做速度还算快,但在低端机上就放大了该问题,尤其是对一些大的项目,它的dex文件多,再加上cpu性能差,整个耗时就更长了,对于初次启动的应用非常不友好。
通过
ARouter提供的注册插件进行路由表的自动加载,通过gradle插件进行自动注册可以缩短初始化时间解决应用加固导致无法直接访问dex文件。初始化失败的问题,需要注意的是,该插件必须搭配 api 1.3.0 以上版本使用!
我们知道apt框架是分module来进行处理的,因此我们也把问题分为在同一个module下和在不同module下:
1、如果SecondActivity和ThirdActivity在同一个module下:
RouteProcessor有一个成员变量groupMap,groupMap对生成ARouterGroup
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中因此不会再生成的文件中出现:
- public class ARouter$$Group$$a implements IRouteGroup {
- @Override
- public void loadInto(Map
atlas) { - atlas.put("/a/b", RouteMeta.build(RouteType.ACTIVITY, SecondActivity.class, "/a/b", "a", null, -1, -2147483648));
- }
- }
2、如果SecondActivity和ThirdActivity在不同的module下:
由于apt框架是分module编译,并且每个module都会生成ARouterRoot$modulename,ARouter