dalvik.system.PathClassLoader没有加载多个JAR

时间:2011-02-15 08:09:48

标签: android jar classloader

我正在尝试加载一组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

2 个答案:

答案 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)

这个问题没有解决办法,我到目前为止已经解决了这个问题。