序、时间于同样的方式留经我们每个人,每个人却以不同的方式对待时间。
前言
一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
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的时候肯定会出错,事实上也是这样的。