我:我有个大胆的方案可以提高ARouter和WMRouter的编译速度!---老板:有多大胆?

当一个module代码发生变化的情况下,plugin只会通知我们Jar包发生了变化,module内的代码到底发生了什么变化对于我们来说是黑盒的。对于路由注册plugin来说,我们只关心jar内的class是否发生了增减,但是一个puglin的只会通知我们文件发生了修改。如何获取到class的增减呢?

private void diffJar(File dest, JarInput jarInput) {
try {
HashSet oldJarFileName = JarUtils.scanJarFile(dest);
HashSet newJarFileName = JarUtils.scanJarFile(jarInput.getFile());
SetDiff diff = new SetDiff<>(oldJarFileName, newJarFileName);
List removeList = diff.getRemovedList();
Log.info(“diffList:” + removeList);
if (removeList.size() > 0) {
JarUtils.deleteJarScan(dest, removeList, deleteCallBack);
}
foreachJar(dest, jarInput);
} catch (Exception e) {
e.printStackTrace();
}
}

简单的分析下上面的操作逻辑。

  1. 先扫描上次编译的文件,将所有的class名字都读取出来。
  2. 读取这次输入的jar包,同时把class名字都读取出来。
  3. 用最简单的dif算法,把被删除的class都拿出来。
  4. 然后扫描删除的class中是否存在路由注册类,用一个HashSet去持有。
  5. 扫描剩下来的jar包,并修改class。

字节码操作

private void generateInitClass(String directory, HashSet items, HashSet deleteItems) {
String className = Constant.REGISTER_CLASS_CONST.replace(‘.’, ‘/’);
File dest = new File(directory, className + SdkConstants.DOT_CLASS);
if (!dest.exists()) {
try {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new ClassVisitor(Opcodes.ASM6, writer) {
};
cv.visit(50, Opcodes.ACC_PUBLIC, className, null, “java/lang/Object”, null);
TryCatchMethodVisitor mv = new TryCatchMethodVisitor(cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
Constant.REGISTER_FUNCTION_NAME_CONST, “()V”, null, null), null, deleteItems);
mv.visitCode();
for (String clazz : items) {
String input = clazz.replace(“.class”, “”);
input = input.replace(“.”, “/”);
Log.info(“item:” + input);
mv.addTryCatchMethodInsn(Opcodes.INVOKESTATIC, input, “init”, “()V”, false);
}
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
cv.visitEnd();
dest.getParentFile().mkdirs();
new FileOutputStream(dest).write(writer.toByteArray());
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
modifyClass(dest, items, deleteItems);
} catch (IOException e) {
e.printStackTrace();
}
}
}

我把Arouter和WMrouter的plugin的优点都结合了一下,当然也有点投机取巧的成分在。

  1. 首先我在路由组件内部用compileOnly的方式引入了一个注册类,这个注册类在合并的时候并不会被合并到代码内。
  2. transform的扫描完成之后,去生成好这个类的实现,这样就不会出现项目运行时的classNotFound异常了。

如果将注册类像ARouter一样放在基础库内部,我就要在编译的最后阶段去寻找那个包含有注册类的jar包,然后定位到那个类,对其进行修改。这要需要对所有jar包的进行扫描,这个过程相对来说是耗时的,而且我修改了整个jar包内的class,需要重新覆盖output的jar包。另外我也不需要像美团组件一样,用反射的方式去调用注册类,因为这个类会在最后编译时被生成和修改,而且类名,方法名和compileOnly的完全一样。

回到增编的问题来,当增量编译触发的情况下,这个时候output已经存在了注册类,我们会将新增的HashSet和删除的HashSet,都以参数传输到ClassVisitor上。

class ClassFilterVisitor extends ClassVisitor {
private HashSet classItems
private HashSet deleteItems

ClassFilterVisitor(ClassVisitor classVisitor, HashSet classItems, HashSet deleteItems) {
super(Opcodes.ASM6, classVisitor)
this.classItems = classItems
this.deleteItems = deleteItems
}

@Override
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name == “register” && desc == “()V”) {
TryCatchMethodVisitor methodVisitor = new TryCatchMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions),
classItems, deleteItems)
return methodVisitor
}
return super.visitMethod(access, name, desc, signature, exceptions)
}
}

当register方法被触发的时候,替换成我们的MethodVisitor,对这个MethodVisitor进行修改。

public class TryCatchMethodVisitor extends MethodVisitor {
private HashSet deleteItems;
private HashSet addItems;

public TryCatchMethodVisitor(MethodVisitor mv, HashSet addItems, HashSet deleteItems) {
super(Opcodes.ASM5, mv);
this.deleteItems = deleteItems;
this.addItems = addItems;
if (this.addItems == null) {
this.addItems = new HashSet<>();
}
if (this.deleteItems == null) {
this.deleteItems = new HashSet<>();
}
Log.info(“deleteItems:” + deleteItems);
}

@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
Log.info("visit owner : " + owner);
String className = owner + “.class”;
if (!deleteItems.contains(className)) {
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
}

@Override
public void visitCode() {
super.visitCode();
for (String input : addItems) {
input = input.replace(“.class”, “”);
input = input.replace(“.”, “/”);
deleteItems.add(input + “.class”);
addTryCatchMethodInsn(Opcodes.INVOKESTATIC, input, “init”, “()V”, false);
Log.info(“visitInsn”);
}
Log.info(“onCodeInsert”);
}

public void addTryCatchMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
/* Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, “java/lang/Exception”);*/

最后

针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

image

oid开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。**

[外链图片转存中…(img-EUA8H8Wk-1726031345038)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值