title: dalvik.system.PathClassLoader not loading multiple JARs
body: <p>I am attempting to load a set of JAR files that go together to make up an API. For some reason I can only load classes not dependent on definitions in other JARs. I am beginning to suspect that the Android classloaders simply do not handle implementing an interface from one JAR file in another. For this reason I've also unpacked the classes into a common dir however this doesn't work either.</p>
  
  <p>Please see the following code. Apologies for any anomalies, but I've tried to ensure it will compile straight up if pasted into an ADT project called MyProj.</p>
  
  <pre><code>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&lt;ClassLoader&gt; classLoaders, ArrayList&lt;String&gt; 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() &gt; 0) jarPaths += File.pathSeparator;
                  jarPaths += jarLoc;
  
                                      // We have to go through the JARs to get class names...
                  JarFile jar = new JarFile(jarLoc);
                  Enumeration&lt;JarEntry&gt; 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 &lt; 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 &gt; 0)
              {
                  bytesRead = is.read(buffer);
                  if (bytesRead &gt; 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;
      }   
  }
  </code></pre>
  
  <p>The following code is where this is called from.</p>
  
  <pre><code>public void enumeratePlugins(IPluginSource source)
  {
      ArrayList&lt;ClassLoader&gt; classLoaders = new ArrayList&lt;ClassLoader&gt;();
      ArrayList&lt;String&gt; classNames = new ArrayList&lt;String&gt;();
  
      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 &lt; classLoaders.size(); x++)
          {
              ClassLoader classLoader = classLoaders.get(x);
  
              try
              {
                  Class dynamic = classLoader.loadClass(className);
                  if(PluginClassBase.class.isAssignableFrom(dynamic) &amp;&amp;
                          !dynamic.isInterface() &amp;&amp; !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() &gt; 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() &gt; 0)
      {
          logger.log(Level.INFO, sb.substring(0, sb.length()-2));
          sb.setLength(0);
      }
  
      logger.log(Level.FINE, "Finished examining classes");
  }
  </code></pre>
  
  <p>Thanks for your help.</p>
  
  <p>EDIT: I have also tried adding</p>
  
  <pre><code>    URLClassLoader ul = null;
      try 
      {
          URL[] contents = new URL[jarURLs.size()];
          ul = new URLClassLoader(jarURLs.toArray(contents), ClassLoader.getSystemClassLoader());
      } 
      catch (Exception e) {} 
      classLoaders.add(ul);        
  </code></pre>
  
  <p>...which gives rise to a new exception - UnsupportedOperationException: Can't load this type of class file.</p>
  
  <p>AND:</p>
  
  <pre><code>    DexClassLoader dl = new DexClassLoader(jarPaths, "/tmp", null, getClass().getClassLoader());
      classLoaders.add(dl);
  </code></pre>
  
  <p>Also didn't work correctly, but thanks for the suggestion <strong>Peter Knego</strong></p>
  
  <p>I should clarify that in the JAR files I have:</p>
  
  <pre><code>JAR1:
      public interface IThing
      public class ThingA implements IThing
  
  JAR2:
      public class ThingB implements IThing
  </code></pre>