Android热编译技术——运行时动态处理和生成代码,初入殿堂!

你有没有这样的想法

-想将编译时不存在的类在运行时动态创建并加载。

-想要使一个类动态的继承不同的父类,实现不同的接口。

-在不知道代码的情况下,在类中添加字段,方法。

-在一个方法的前后插入自己的代码。

  呵呵,也许对你来说,这一切曾经只是浮夸的幻想,但我可以负责任的告诉你,当你看完这篇文章,这一切将成为现实!

在介绍这门技术之前,我们有必要来了解一下需需要用到的概念

一、compile-time

所谓的compile-time,顾名思义,就是编译时。我们的一切工作将在compile-time处理,而在Run-time(运行时)被调用和执行或其他操作。


二、dex文件的由来

  我们知道,在Java中使用的是 .class文件作为字节码文件,而在Android中,则是 .dex文件,他们之间有什么关系?查阅资料,我们可以知道,Android的 .dex文件其实是由 .class文件优化而成,而加载代码,使用的是Dalvik虚拟机(在java的实现是DexClassLoader)。


知道了以上这些概念,恭喜你,你已经踏入了成功殿堂的半步,现在我们就来学习具体的实现。

  在Java平台,有一个很强大的开源库,名为:JavaSsist,然而,它是针对JAVA平台的,也就是说,它的实现全部是基于jvm的栈结构字节码,因此,在android中是无法使用的,那么我们又应该怎么办呢?

笔者既然提到了它,自然会就使用到它,下面就来介绍笔者想到的解决方案。

我们用到的是Google 提供的 Dx工具,Dx工具能将.class文件转换为.dex文件,最值得欣慰的是,这个工具是使用纯java编写的,因此我们完全可以使用在android平台。事实证明,的确如此。

  于是得出方案,在生成class字节码后,紧接着使用dx转换一次,这样看似效率底下,而实际上却并非如此。笔者在这里就不再写  JavaSsist  移植到 Android平台的具体过程,我会将移植完成的jar库放在文章的尾部,欢迎下载和学习!

如何使用这个库?

首先,第一步是申明 池:

final ClassPool cp = ClassPool
                        .getDefault(getApplicationContext());
final CtClass cls = cp.makeClass("LodyActivity");//LodyActivity是这里要生成的类名

然后,我们设置它的父类 :

cls.setSuperclass(cp.get("android.app.Activity"));


  在这一步,我们将他的父类设置为:

android.app.Activity

这一部是可以省略的,如果没有设置父类,则它默认是一个Object的子类。

然后,我们为这个类创建构造器:

  CtConstructor constructor = new CtConstructor(null, cls);  
  constructor.setModifiers(Modifier.PUBLIC);  
  constructor.setBody("{System.out.println(\" 调用构造器!\");}");  
  cls.addConstructor(constructor);

这一步,也是可以省略的,因为默认会创建一个无参数构造器,当然,在这一步,我申明的构造器也是无参构造器,如果读者想要生命有参构造器,则可以使用这个API:

CtConstructor(CtClass[] parameters, CtClass declaring)

其中的 parameters(参数)用cp.get(完整包名+类名),然后放入一个CtClass数组实现。  

CtMethod method = new CtMethod(CtClass.voidType, "run", null, cls); 
method.setBody("{System.out.println(\"调用了方法:run!!\" );}  ");  
cls.addMethod(method);

这一步,将会为类添加一个名为run,返回值为vold的方法,你可能会震惊的发现,setBody可以直接写入java代码?!

  没错,这就是亮点,因为JavaSsist内置一个java编译器,它将会以字段和方法为单位,将你插入的代码编译出来。


当你完成了一个类的创建,你需要将它写入到存储器(其实在java不需要这么做,因为你可以使用toClass方法直接得到纂改后的类,使用Class.newInstance()来得到实例,但是由于dex限制,在android中没办法这么做,忍忍吧!)

cls.writeFile(getFilesDir().getAbsolutePath());
做完这一步,将会在你的存储器的相应位置生成xxxxx.class文件(这里是LodyActivity.class),下一步是class2dex的过程:
 
final DexFile df = new DexFile();
final String dexFilePath = "/sdcard/myclasses.dex";
df.addClass(new File(getFilesDir(), "LodyActivity.class"));
df.writeFile(dexFilePath);

完成这一步,就会在/sdcard下生成 myclasses.dex文件,最后,我们可以使用DexClassLoader调用它,关于如何加载一个dex的方法,网上有大量的文章介绍这个,这里不再细谈,最后我们看看生成的dex文件的代码:

Android热编译技术——运行时动态处理和生成代码,初入殿堂!_第1张图片

可以看到,代码成功生成了!关于这款软件,我想说,你并不能在网上找到,因为这是笔者正在开发的一款软件,能够让使用者直接查看一个apk/dex/jar的源码,并且不怕混淆,能够加载resource.arsc,关于这款软件,相信读者未来有一天有机会将会见到的,呵呵。

最后送上笔者做好的JavaSsist库:

http://pan.baidu.com/s/1c02FKGS

你可能感兴趣的:(Android热编译技术——运行时动态处理和生成代码,初入殿堂!)