我相信大家都用过 APT ,特别是在组件化这一块,我看到很多开源的组件化项目都是使用 APT 去做路由表这一块的,我最近做的时候也出现了一些问题,在这里记录一下。
我在下图中主要列出了几个 module 来演示,分别为
- 登录module
- 直播module
- 社交module
我想达到的要求是在每一个 module 都会生成一个对外暴露的路由类,然后在主 module 中去汇总这些类来生成一个表,然后各个module之间是没有相互依赖的,并且可以通过路由去通讯(当然这个不是本文的重点),这就是我理解的路由表。
image-20200321200543563我大致绘制了一下草图,从上图可以看到 APT 只会在每一个 Module 指定包名 com.example.router 下去生成对应的类,那我们怎么去在主 Module 中去收集这些通过 APT 在每一个 module 中生成的类呢?
(我想记录的重点在这里)
这里可以借助著名开源框架 ARouter 的做法,它是应用启动的时候,去扫描 dex 文件,找到某一个包下的这些类,注意这个包下的类就是我们通过 APT 去生成的类,然后得到一个集合,然后我们再根据反射去创建对应的对象就好了。
下面的代码可以在 ARouter Github 中可以找到的
public static List<String> getFileNameByPackageName(Context context, String packageName) throws PackageManager.NameNotFoundException, IOException {
List<String> classNames = new ArrayList<>();
for (String path : getSourcePaths(context)) {
DexFile dexfile = null;
try {
if (path.endsWith(EXTRACTED_SUFFIX)) {
//NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
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.contains(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
Log.e("ARouter", "Scan map file in dex files made error.", ignore);
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
}
}
Log.d("ARouter", "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
return classNames;
}
这种方案是有弊端的,就是运行时需要去扫描 dex ,目前我们项目也是用到这种方案,不过 ARouter 也提供的第二种解决方案中那就是可以使用 Gradle 插件去解决这个问题,原理使用到的就是 ASM
技术,在 Gradle 打包编译成 class 后和生成 dex 的中间去往 class 中插入对应的代码
。这种方案是在打包阶段就完成的,因此就不会有什么运行时性能问题了。这里要注意的是,你使用 ASM 插桩之后,如果要验证的话,记得去解压 APK 验证哦,不能直接去 build 目录下看 class 代码,因为 class 文件是没有你插桩的代码。
有时间的话,我去改造一下。
本文是笔者学习之后的总结,方便日后查看学习,有任何不对的地方请指正。
记录于 2020年3月21号
网友评论