【问题标题】:dalvik.system.PathClassLoader not loading multiple JARsdalvik.system.PathClassLoader 不加载多个 JAR
【发布时间】:2011-06-27 10:28:17
【问题描述】:

我正在尝试加载一组 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: Can't load this type of class file.

与:

    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

【问题讨论】:

标签: android jar classloader


【解决方案1】:

我不完全确定您要做什么,但我怀疑 Java 语言对类加载器的定义不支持您想要的。

类加载器按层次结构排列。如果您向类加载器 CL1 询问类 Foo 的副本,它会询问其父级是否知道 Foo 是什么。这会沿着链向上到达引导加载程序,并且当事情失败时它会返回,直到最终 CL1 有机会出去找到一个副本。 (它必须以这种方式工作,并且有一个测试用例故意“错误”地做它,但它几乎总是这样做。)

假设 CL1 确实定义了 Foo。 Foo 实现了 IBar,因此在准备该类时,VM 将要求 CL1 查找 IBar。通常的搜索就完成了。

如果 IBar 在 CL2 中定义,并且 CL2 不是 CL1 的父级,则 CL1 中的任何内容都无法看到 IBar。

因此,如果您要为各种 jar 文件创建一堆“对等”类加载器,则不能直接在它们之间引用类。

这不是 Android 独有的。

如果您创建一个类加载器并将整个 jar 集放在路径中,您可以随意混合搭配。

【讨论】:

  • 对不起,你完全错了。这正是该项目目前在 PC 上的运作方式。
  • ...再次抱歉,我重读了您的帖子,您在最后一段中所说的正是我要解决的问题 - 以上面列出的四种不同方式。
  • ...我还尝试将类加载器作为子级和父级链接在一起。
  • 上面列出的最后一次尝试失败的原因是什么? (设备没有 /tmp 目录,所以我想确保它不会因此而失败。)
  • 失败类似于“您无法加载那种类文件”。我知道二进制不兼容必须在某个地方起作用! :-)
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-02
    • 1970-01-01
    • 1970-01-01
    • 2016-10-19
    • 2023-01-29
    相关资源
    最近更新 更多