我正在尝试加载一组JAR文件,它们组成一个API。出于某种原因,我只能加载不依赖于其他JAR中定义的类。我开始怀疑Android类加载器根本不处理从另一个JAR文件中实现接口的问题。出于这个原因,我还将类解压缩到一个公共目录中,但是这也不起作用。
请参阅以下代码。对任何异常事件表示道歉,但我已经尝试确保如果将其粘贴到名为MyProj的ADT项目中,它将直接编译。
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import dalvik.system.PathClassLoader;
import android.content.Context;
// IPluginSource simply defines the method here at the top.
public class AndroidPluginSource implements IPluginSource
{
@Override
public void doSearching(ArrayList<ClassLoader> classLoaders, ArrayList<String> classNames)
{
String jarPaths = "";
// For each of the raw resources, JARs compiled into the 'res/raw' dir...
for (Field str : R.raw.class.getFields())
{
String resName = str.getName();
Logger.log(Level.FINE, "Resource: " + str);
try
{
// Copy the JAR file to the local FS.
InputStream is = MyProj.self.getResources().openRawResource(str.getInt(this));
OutputStream os = MyProj.self.openFileOutput(resName + ".jar", Context.MODE_PRIVATE);
copyData(is, os);
is.close();
os.close();
// Get JAR location.
String jarLoc = MyProj.self.getFilesDir() + File.separator + resName + ".jar";
// First attempt is just single classloaders, so we aren't suprised this won't work.
classLoaders.add(new PathClassLoader(jarLoc, MyProj.self.getClassLoader()));
//Logger.log(Level.FINE, " LOC: " + jarLoc);
// Keep running list of JAR paths, will that work?
if (jarPaths.length() > 0) jarPaths += File.pathSeparator;
jarPaths += jarLoc;
// We have to go through the JARs to get class names...
JarFile jar = new JarFile(jarLoc);
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements())
{
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.endsWith(".class"))
{
classNames.add(toClassName(entryName));
Logger.log(Level.FINE, " ENT: " + entryName);
// ...while we're here lets get the class out as a file.
String classLoc = MyProj.self.getFilesDir() + File.separator + entryName;
Logger.log(Level.FINER, " CLS: " + classLoc);
File classFile = new File(classLoc);
classFile.delete();
classFile.getParentFile().mkdirs();
InputStream jis = jar.getInputStream(entry);
//OutputStream jos = MyProj.self.openFileOutput(classLoc, Context.MODE_PRIVATE);
OutputStream jos = new FileOutputStream(classFile);
copyData(jis, jos);
jos.close();
jis.close();
}
}
}
catch (Exception ex)
{
Logger.log(Level.SEVERE, "Failed plugin search", ex);
}
}
File f = MyProj.self.getFilesDir();
recursiveList(f, 0);
// So we have a class loader loading classes...
PathClassLoader cl = new PathClassLoader(VermilionAndroid.self.getFilesDir().getAbsolutePath(), ClassLoader.getSystemClassLoader());
classLoaders.add(cl);
// A JAR loader loading all the JARs...
PathClassLoader jl = new PathClassLoader(jarPaths, ClassLoader.getSystemClassLoader());
classLoaders.add(jl);
// And if edited as below we also have a DexLoader and URLClassLoader.
}
// This is just so we can check the classes were all unpacked together.
private void recursiveList(File f, int indent)
{
StringBuilder sb = new StringBuilder();
for (int x = 0; x < indent; x++) sb.append(" ");
sb.append(f.toString());
Logger.log(Level.INFO, sb.toString());
File[] subs = f.listFiles();
if (subs != null)
{
for (File g : subs) recursiveList(g, indent+4);
}
}
// Android helper copy file function.
private void copyData(InputStream is, OutputStream os)
{
try
{
int bytesRead = 1;
byte[] buffer = new byte[4096];
while (bytesRead > 0)
{
bytesRead = is.read(buffer);
if (bytesRead > 0) os.write(buffer, 0, bytesRead);
}
}
catch (Exception ex) {}
}
// Goes from a file name or JAR entry name to a full classname.
private static String toClassName(String fileName)
{
// The JAR entry always has the directories as "/".
String className = fileName.replace(".class", "").replace(File.separatorChar, '.').replace('/', '.');
return className;
}
}
以下代码是从中调用它的地方。
public void enumeratePlugins(IPluginSource source)
{
ArrayList<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
ArrayList<String> classNames = new ArrayList<String>();
source.doSearching(classLoaders, classNames);
logger.log(Level.FINE, "Trying discovered classes");
logger.log(Level.INFO, "Listing plugins...");
StringBuilder sb = new StringBuilder();
// Try to load the classes we found.
for (String className : classNames)
{
//boolean loadedOK = false;
Throwable lastEx = null;
for (int x = 0; x < classLoaders.size(); x++)
{
ClassLoader classLoader = classLoaders.get(x);
try
{
Class dynamic = classLoader.loadClass(className);
if(PluginClassBase.class.isAssignableFrom(dynamic) &&
!dynamic.isInterface() && !Modifier.isAbstract(dynamic.getModifiers()))
{
PluginClassBase obj = (PluginClassBase) dynamic.newInstance();
String classType = obj.getType();
String typeName = obj.getName();
classes.put(typeName, new PluginClassDef(typeName, classType, dynamic));
logger.log(Level.FINE, "Loaded plugin: {0}, classType: {1}", new Object[] {typeName, classType});
sb.append(typeName).append(" [").append(classType).append("], ");
if (sb.length() > 70)
{
logger.log(Level.INFO, sb.toString());
sb.setLength(0);
}
}
lastEx = null;
break;
}
catch (Throwable ex)
{
lastEx = ex;
}
}
if (lastEx != null)
{
logger.log(Level.INFO, "Plugin instantiation exception", lastEx);
}
}
if (sb.length() > 0)
{
logger.log(Level.INFO, sb.substring(0, sb.length()-2));
sb.setLength(0);
}
logger.log(Level.FINE, "Finished examining classes");
}
感谢您的帮助。
编辑:我也尝试过添加
URLClassLoader ul = null;
try
{
URL[] contents = new URL[jarURLs.size()];
ul = new URLClassLoader(jarURLs.toArray(contents), ClassLoader.getSystemClassLoader());
}
catch (Exception e) {}
classLoaders.add(ul);
...引发新的异常--UnsupportedOperationException:无法加载此类类文件。
和
DexClassLoader dl = new DexClassLoader(jarPaths, "/tmp", null, getClass().getClassLoader());
classLoaders.add(dl);
也没有正常工作,但感谢您的建议 Peter Knego
我应该澄清一下,在JAR文件中我有:
JAR1:
public interface IThing
public class ThingA implements IThing
JAR2:
public class ThingB implements IThing
答案 0 :(得分:1)
我不完全确定你要做什么,但我怀疑Java语言对类加载器的定义不支持你想要的东西。
类加载器按层次结构排列。如果你要求类加载器CL1获取类Foo的副本,它会询问其父类是否知道Foo是什么。这是连接到引导加载程序的链,并且当事情失败时它会回落,直到最终CL1有机会出去找到副本。 (它不会 以这种方式工作,并且有一个故意将它做为“错误”的测试用例,但它几乎总是这样做。)
假设CL1确实定义了Foo。 Foo实现了IBar,因此在准备该类时,VM将要求CL1找到IBar。通常的搜索已经完成。
如果在CL2中定义了IBar,并且CL2不是CL1的父级,则CL1中的任何内容都不能看到IBar。
因此,如果您为各种jar文件创建了一堆“对等”类加载器,则无法直接在它们之间引用类。
这不是Android独有的。
如果您创建单个类加载器并将整组jar放入路径中,则可以根据需要进行混合和匹配。
答案 1 :(得分:1)
这个问题没有解决办法,我到目前为止已经解决了这个问题。