2013-03-16 15 views
12

Jak mogę znaleźć wszystkie klasy w pakiecie na Androida? Używam PathClassLoader, ale zawsze zwraca puste wyliczenie?Znajdź wszystkie zajęcia w pakiecie w Androidzie

Dodatkowe informacje

Wypróbowałem sugerowane podejście Reflections. Kilka ważnych punktów dotyczących biblioteki odbić. Biblioteka dostępna przez centralę maven jest niezgodna z systemem Android i podaje błędy dex. Musiałem włączyć źródło i skompilować dom4j, java-assist.

Problem z odbiciem, a moje oryginalne rozwiązanie polega na tym, że PathClassLoader w Androidzie zwraca puste wyliczenie pakietu.

Problem z podejściem polega na tym, że getResource w systemie Android zawsze zwraca puste wyliczenie.

final String resourceName = aClass.getName().replace(".", "/") + ".class"; 

for (ClassLoader classLoader : loaders) { 
     try { 
      final URL url = classLoader.getResource(resourceName); 
      if (url != null) { 
       final String normalizedUrl = url.toExternalForm().substring(0, url.toExternalForm().lastIndexOf(aClass.getPackage().getName().replace(".", "/"))); 
       return new URL(normalizedUrl); 
      } 
     } catch (MalformedURLException e) { 
      e.printStackTrace(); 
     } 
    } 
+0

Czy można zaakceptować najpierw apk, a następnie przeanalizować plik DEX? Jeśli jest to w twoim przypadku do przyjęcia, mogę napisać odpowiedź. – StarPinkER

+0

Nie, próbuję ujawnić dynamicznie niektóre metody wtyczek i nie będę wiedział, jakie klasy są obecne przed rozdaniem. – user210504

+0

Czy możesz opracować? niezupełnie rozumiem twój scenariusz – StarPinkER

Odpowiedz

2

spróbować ..

Reflections reflections = new Reflections("my.project.prefix"); 

    Set<Class<? extends Object>> allClasses 
         = reflections.getSubTypesOf(Object.class); 
+9

To nie działa w systemie Android, awarie biblioteki relections z nieobsługiwanym błędem klasy dex – user210504

+0

Problem z podejściem to – user210504

24

Korzystanie DexFile listę wszystkich klas w apk:

try { 
     DexFile df = new DexFile(context.getPackageCodePath()); 
     for (Enumeration<String> iter = df.entries(); iter.hasMoreElements();) { 
      String s = iter.nextElement(); 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

reszta jest za pomocą wyrażenia regularnego lub coś innego, aby odfiltrować żądaną klasę.

+0

Użyłem [this] (http://mindtherobot.com/blog/737/android-hacks-scan-android-classpath /) jako punkt wyjścia. – c0nstruct0r

+3

To nie działa poprawnie od Androida Gradle Plugin 2.0.0 – anavarroma

+0

Mam ten sam problem co @anavarroma to już nie działa, kod działa, ale nie przywraca żadnej z klas –

0

To jest wzięte z przykładu http://mindtherobot.com/

Code

że mamy adnotację o nazwie Foo i kilka klas ozdobione nim:

@Retention(RetentionPolicy.RUNTIME) 
public @interface Foo { 
    String value(); 
} 

@Foo("person") 
public class Person { 

} 

@Foo("order") 
public class Order { 

} 

Oto proste klasy, która przechodzi nad wszystkimi klasami w Twoja aplikacja w środowisku wykonawczym znajduje te oznaczone jako @Foo i pobiera wartość adnotacji. Nie kopiuj i nie wklejaj tego kodu do swojej aplikacji - trzeba go naprawić i dodać, jak opisano poniżej.

public class ClasspathScanner { 
    private static final String TAG = ClasspathScanner.class.getSimpleName(); 

    private static Field dexField; 

    static { 
    try { 
     dexField = PathClassLoader.class.getDeclaredField("mDexs"); 
     dexField.setAccessible(true); 
    } catch (Exception e) { 
     // TODO (1): handle this case gracefully - nobody promised that this field will always be there 
     Log.e(TAG, "Failed to get mDexs field"); 
    } 
    } 

    public void run() { 
    try { 
     // TODO (2): check here - in theory, the class loader is not required to be a PathClassLoader 
     PathClassLoader classLoader = (PathClassLoader) Thread.currentThread().getContextClassLoader(); 

     DexFile[] dexs = (DexFile[]) dexField.get(classLoader); 
     for (DexFile dex : dexs) { 
     Enumeration<String> entries = dex.entries(); 
     while (entries.hasMoreElements()) { 
      // (3) Each entry is a class name, like "foo.bar.MyClass" 
      String entry = entries.nextElement(); 
      Log.d(TAG, "Entry: " + entry); 

      // (4) Load the class 
      Class<?> entryClass = dex.loadClass(entry, classLoader); 
      if (entryClass != null) { 
      Foo annotation = entryClass.getAnnotation(Foo.class); 
      if (annotation != null) { 
       Log.d(TAG, entry + ": " + annotation.value()); 
      } 
      } 
     } 
     } 
    } catch (Exception e) { 
     // TODO (5): more precise error handling 
     Log.e(TAG, "Error", e); 
    } 
    } 
} 
13

Właściwie znalazłem rozwiązanie. Dziękujemy za odpowiedź tao.

private String[] getClassesOfPackage(String packageName) { 
    ArrayList<String> classes = new ArrayList<String>(); 
    try { 
     String packageCodePath = getPackageCodePath(); 
     DexFile df = new DexFile(packageCodePath); 
     for (Enumeration<String> iter = df.entries(); iter.hasMoreElements();) { 
      String className = iter.nextElement(); 
      if (className.contains(packageName)) { 
       classes.add(className.substring(className.lastIndexOf(".") + 1, className.length())); 
      } 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

    return classes.toArray(new String[classes.size()]); 
} 

Testowane na Android 5.0 Lolipop

+0

Powinieneś zwrócić pełną nazwę klasy, taką jak 'nazwaklamowa.klasy.Klasa ', ponieważ osoba używająca tego chciałaby używać jej w taki sposób' Class.forName (className) '. – lovesh

+1

@lovesh to zależy od celu. Właściwie możesz zmienić kod do swoich potrzeb. –

+0

Święta krowa. Chciałbym móc znaleźć więcej sposobów przyznawania punktów tej odpowiedzi. Szukałem ** dni ** na to i znalazłem go tylko dlatego, że ktoś do niego linkował z https://github.com/ronmamo/reflections/issues/115. I to takie proste! Zastanawiam się nad powrotem do wszystkich innych wpisów SO i linkowania tutaj. Przy okazji, dla tych, którzy się tym przejmują, ta metoda może zwrócić wszystkie klasy dla _all_ pakietów w aplikacji, jeśli ustawisz "packageName" na "com.myapp", zakładając, że wszystkie pakiety zaczynają się od tego. Można go również łatwo modyfikować, aby zwracać w pełni kwalifikowane nazwy i pomijać klasy wewnętrzne (dla 'Class.forName()'). –

3

Oto rozwiązanie, które przyniesie Ci nie tylko nazwę klasy,
ale również przedmiot Class<?>.

Podczas gdy PathClassLoader.class.getDeclaredField("mDexs") często zawiedzie,
new DexFile(getContext().getPackageCodePath()) wydaje się być znacznie bardziej stabilne.

public abstract class ClassScanner { 

    private static final String TAG = "ClassScanner"; 
    private Context mContext; 

    public ClassScanner(Context context) { 
     mContext = context; 
    } 

    public Context getContext() { 
     return mContext; 
    } 

    void scan() throws IOException, ClassNotFoundException, NoSuchMethodException { 
     long timeBegin = System.currentTimeMillis(); 

     PathClassLoader classLoader = (PathClassLoader) getContext().getClassLoader(); 
     //PathClassLoader classLoader = (PathClassLoader) Thread.currentThread().getContextClassLoader();//This also works good 
     DexFile dexFile = new DexFile(getContext().getPackageCodePath()); 
     Enumeration<String> classNames = dexFile.entries(); 
     while (classNames.hasMoreElements()) { 
      String className = classNames.nextElement(); 
      if (isTargetClassName(className)) { 
       //Class<?> aClass = Class.forName(className);//java.lang.ExceptionInInitializerError 
       //Class<?> aClass = Class.forName(className, false, classLoader);//tested on 魅蓝Note(M463C)_Android4.4.4 and Mi2s_Android5.1.1 
       Class<?> aClass = classLoader.loadClass(className);//tested on 魅蓝Note(M463C)_Android4.4.4 and Mi2s_Android5.1.1 
       if (isTargetClass(aClass)) { 
        onScanResult(aClass); 
       } 
      } 
     } 

     long timeEnd = System.currentTimeMillis(); 
     long timeElapsed = timeEnd - timeBegin; 
     Log.d(TAG, "scan() cost " + timeElapsed + "ms"); 
    } 

    protected abstract boolean isTargetClassName(String className); 

    protected abstract boolean isTargetClass(Class clazz); 

    protected abstract void onScanResult(Class clazz); 
} 

i jest to przykład, jak używać:

new ClassScanner(context) { 

    @Override 
    protected boolean isTargetClassName(String className) { 
     return className.startsWith(getContext().getPackageName())//I want classes under my package 
       && !className.contains("$");//I don't need none-static inner classes 
    } 

    @Override 
    protected boolean isTargetClass(Class clazz) { 
     return AbsFactory.class.isAssignableFrom(clazz)//I want subclasses of AbsFactory 
       && !Modifier.isAbstract(clazz.getModifiers());//I don't want abstract classes 
    } 

    @Override 
    protected void onScanResult(Class clazz) { 
     Constructor constructor = null; 
     try { 
      constructor = clazz.getDeclaredConstructor(); 
      constructor.setAccessible(true); 
      constructor.newInstance(); 
     } catch (NoSuchMethodException e) { 
      e.printStackTrace(); 
     } catch (InstantiationException e) { 
      e.printStackTrace(); 
     } catch (IllegalAccessException e) { 
      e.printStackTrace(); 
     } catch (InvocationTargetException e) { 
      e.printStackTrace(); 
     } 
    } 

}.scan(); 
1

znalazłem to samo rozwiązanie Siergieja Shustikov z nazwą pakietu postaci pochodnej kontekst i uchwyty klas wewnętrznych. Niestety, gdy działa na Galaxy Nexus, nie działa on na Nexusie 6 i Nexusie 6p (nie testowany na innych urządzeniach).

Oto kod:

private HashMap<String, String> loadClassFullnames() { 
    final HashMap<String, String> ret = new HashMap<>(); 
    try { 
     final String thisPackage = context.getPackageName(); 
     final DexFile tmp = new DexFile(context.getPackageCodePath()); 
     for (Enumeration<String> iter = tmp.entries(); iter.hasMoreElements();) { 
      final String classFullname = iter.nextElement(); 
      if (classFullname.startsWith(thisPackage)) { 
       // TODO filter out also anonymous classes (es Class1$51) 
       final int index = classFullname.lastIndexOf("."); 
       final String c = (index >= 0) ? classFullname.substring(index + 1) : classFullname; 
       ret.put(c.replace('$', '.'), classFullname); 
      } 
     } 
    } catch (Throwable ex) { 
     Debug.w("Unable to collect class fullnames. Reason: " + ex.getCause() + "\n\rStack Trace:\n\r" + ((ex.getCause() != null)? ex.getCause().getStackTrace(): "none")); 
    } 

    return ret; 
} 

szwy, że na Galaxy Nexus The DexFile zawiera także pakiety aplikacji (ten staramy się znaleźć!), Podczas gdy na Nexus 6 [P] żadnego pakietu z pasujące do tego pakietu znajdują się, gdy są inne pakiety ... Czy ktoś ma ten sam problem?

Powiązane problemy