最近在做一个工作,把两个文件合并,Android中的两个R.java文件合并。看到这个需求时,想到两种方法: 1)逐行读文件,根据关键字区分哪些是内部类,哪些是内部类的成员变量;2)通过将.java文件编译并加载,通过反射得到内部类,和成员变量。第二种方法更直接一些,本文记录下编译并加载.java文件的方法和问题。
目的:编译并加载磁盘上任意位置的.java文件。
实现MyClassloader,继承自ClassLoader,重写findClass方法
package com.demo.RMerger;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassloader extends ClassLoader {
private String mPre;
public MyClassloader(String pre) {
mPre = pre;
}
// 读取一个文件的内容
private byte[] getBytes(String filename) throws IOException {
FileInputStream fi = null;
fi = new FileInputStream(filename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int read;
while ((read = fi.read(buffer)) > 0)
baos.write(buffer, 0, read);
byte[] classBytes = baos.toByteArray();
return classBytes;
}
// 定义编译指定Java文件的方法
public boolean compile(String javaFile) throws IOException {
System.out.println("CompileClassLoader:正在编译" + javaFile + "...");
// 调用系统的javac命令
Process p = Runtime.getRuntime().exec("javac " + javaFile);
try {
p.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取javac线程的退出值
int ret = p.exitValue();
// 返回编译是否成功
return ret == 0;
}
// 重写ClassLoader的findClass方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
// 将包路径中的点替换成斜线
String fileStub = name.replace(".", File.separator);
String javaFilename = mPre + fileStub + ".java";
String classFilename = mPre + fileStub + ".class";
File javaFile = new File(javaFilename);
File classFile = new File(classFilename);
if (javaFile.exists() && (!classFile.exists()
|| javaFile.lastModified() > classFile.lastModified())) {
try {
// 如果编译失败,或者该Class文件不存在
if (!compile(javaFilename) || !classFile.exists()) { // 这一步compile重新编译了一次
throw new ClassNotFoundException("ClassNotFoundException:" +
javaFilename);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 如果Class文件存在,系统负责将该文件转换成Class对象
if (classFile.exists()) {
try {
// 将Class文件的二进制数据读入数组
byte[] raw = getBytes(classFilename);
// 调用ClassLoader的defineClass方法将二进制数据成Class对象
clazz = defineClass(name, raw, 0, raw.length);
} catch (IOException e) {
e.printStackTrace();
}
}
// 如果clazz为null,则表明加载失败,抛出异常
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
}
调用方法:
MyClassloader myClassloader = new MyClassloader(System.getProperty("user.dir") + File.separatorChar + pre
+ File.separatorChar);
Class<?> R = myClassloader.loadClass(name.replace(File.separatorChar, '.'));
注意调用loadClass时候需要用包名+类名;比如com.android.R。
System.getProperty("user.dir")表示当前工程的路径。