Вопрос по android, java – Найти все классы в пакете в Android

14

Как я могу найти все классы внутри пакета на Android? Я использую PathClassLoader, но он всегда возвращает пустое перечисление?

Дополнительная информация

Попробовал предложенный подход к размышлениям. Пара важных моментов о библиотеке размышлений. Библиотека, доступная через maven central, не совместима с Android и выдает ошибки dex. Мне пришлось включить исходный код и скомпилировать dom4j, java-assist.

Проблема с отражениями, и мое оригинальное решение состоит в том, что PathClassLoader в Android возвращает пустое перечисление для пакета.

Проблема с подходом заключается в том, что getResource в Android всегда возвращает пустое перечисление.

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();
        }
   }
Можете ли вы уточнить? не совсем понимаю ваш сценарий StarPinkER
Я хочу загрузить классы из пакета и сделать их доступными на основе конфигурации политики, полученной с сервера. Но я хочу сделать это общим способом, чтобы мне не нужно было знать классы, которые являются частью приложения. user210504
Допустимо ли сначала апк апк, а потом анализировать файл dex? Если это приемлемо в вашем случае, я мог бы написать ответ. StarPinkER
Нет, я пытаюсь представить некоторые методы для плагинов динамически, и я не знаю, какие классы присутствуют до этого. user210504

Ваш Ответ

7   ответов
14

На самом деле я нашел решение. Спасибодао Ответить.

private String[] getClassesOfPackage(String packageName) {
    ArrayList classes = new ArrayList();
    try {
        String packageCodePath = getPackageCodePath();
        DexFile df = new DexFile(packageCodePath);
        for (Enumeration 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()]);
}

Проверено на Android 5.0 Lolipop

Это не работает для меня, так как Gradle 2.0.0 anavarroma
DexFile устарел: этот API будет удален в следующем выпуске Android.developer.android.com/reference/dalvik/system/DexFile.html hrules6872
@lovesh это зависит от вашей цели. На самом деле вы можете изменить код для ваших нужд. Sergey Shustikov
Вы должны вернуть полное имя класса, какpackagename.ClassName потому что человек, использующий это, хотел бы использовать это такими способами.Class.forName(className) lovesh
Святая корова. Хотелось бы найти больше способов присуждать баллы за этот ответ. Я искалдней для этого и только нашел его, потому что кто-то связался с ним изgithub.com/ronmamo/reflections/issues/115, И это'так просто! Я'Я думаю вернуться ко всем остальным постам SO и связать здесь. Кстати, для тех, кто заботится, этот метод может вернуть все классы длявсе пакеты в приложении, если вы установитеpackageName к "com.myapp»при условии, что все пакеты начинаются с этого. Это's также легко изменить, чтобы вернуть полностью определенные имена и пропустить внутренние классы (дляClass.forName()). Mark Cramer
1

Это взято изhttp://mindtherobot.com/

Пример кода

Скажем, у нас есть аннотация Foo и несколько классов, украшенных ею:

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

@Foo("person")
public class Person {

}

@Foo("order")
public class Order {

}

Вот'Это простой класс, который просматривает все классы в вашем приложении во время выполнения, находит те, которые помечены @Foo, и получает значение аннотации. Не копируйте и не вставляйте этот код в свое приложение - это многое нужно исправить и добавить, как описано ниже в коде.

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 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);
    }
  }
}
0

Я нашел то же решение Сергея Шустикова, с именем пакета, полученным из контекста и обрабатывающим внутренние классы. К сожалению покаработает на Galaxy Nexus, этоне работает на Nexus 6 и Nexus 6p (не тестировался на других устройствах).

Вот's код:

private HashMap loadClassFullnames() {
    final HashMap ret = new HashMap();
    try {
        final String thisPackage = context.getPackageName();
        final DexFile tmp = new DexFile(context.getPackageCodePath());
        for (Enumeration 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('

Похоже, что в Galaxy Nexus DexFile содержит также пакеты приложений (тот, который мы пытаемся найти!), В то время как на Nexus 6 [p] не найдено ни одного пакета из соответствующего thisPackage, в то время как некоторые другие пакеты есть ... Кто-нибудь есть такая же проблема?

, '.'), 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; }

Похоже, что в Galaxy Nexus DexFile содержит также пакеты приложений (тот, который мы пытаемся найти!), В то время как на Nexus 6 [p] не найдено ни одного пакета из соответствующего thisPackage, в то время как некоторые другие пакеты есть ... Кто-нибудь есть такая же проблема?

0

Эти подходы, как правило, устарели и неработать с мультидекс приложениями. Для поддержки мультидекса вы можете получить все DexFiles, как это

internal fun getDexFiles(context: Context): Sequence {
    // Here we do some reflection to access the dex files from the class loader. These implementation details vary by platform version,
    // so we have to be a little careful, but not a huge deal since this is just for testing. It should work on 21+.
    // The source for reference is at:
    // https://android.googlesource.com/platform/libcore/+/oreo-release/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
    val classLoader = context.classLoader as BaseDexClassLoader

    val pathListField = field("dalvik.system.BaseDexClassLoader", "pathList")
    val pathList = pathListField.get(classLoader) // Type is DexPathList

    val dexElementsField = field("dalvik.system.DexPathList", "dexElements")
    @Suppress("UNCHECKED_CAST")
    val dexElements = dexElementsField.get(pathList) as Array // Type is Array

    val dexFileField = field("dalvik.system.DexPathList\$Element", "dexFile")
    return dexElements.map {
        dexFileField.get(it) as DexFile
    }.asSequence()
}

private fun field(className: String, fieldName: String): Field {
    val clazz = Class.forName(className)
    val field = clazz.getDeclaredField(fieldName)
    field.isAccessible = true
    return field
}

Оттуда вы можете использовать это, чтобы получить все классы в пакете

getDexFiles(context)
            .flatMap { it.entries().asSequence() }
            .filter { it.startsWith("my.package.name") }
            .map { context.classLoader.loadClass(it) }
3

Вот's решение, которое принесет вам не только имя класса,

но такжеClass объект.

И покаPathClassLoader.class.getDeclaredField("mDexs") часто терпит неудачу,

new DexFile(getContext().getPackageCodePath()) кажется гораздо более стабильным.

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 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(aCla,ss)) {
                    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);
}

и это пример, как использовать:

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();
24

Использование DexFile для вывода списка всех классов в вашем apk:

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

остальное использует регулярное выражение или что-то еще, чтобы отфильтровать желаемый класс.

Может кто-нибудь объяснить, каковы результаты, которые включают в себя$ условное обозначение? Nathan H
@PeterFox, зацените этот ответ на мой постstackoverflow.com/a/36594966/1387138Это может быть полезно. anavarroma
Знак доллара означает внутренние классы. УвидетьЭтот вопрос Tzlil Gavra
У нас та же проблема, что и у @anavarroma, эта проблема больше не работает, код работает, но невернуть любой из классов Peter Fox
1

попробуй это..

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

   Set allClasses 
                        = reflections.getSubTypesOf(Object.class);
Это не работает в Android, сбой библиотеки выпусков с неподдерживаемой ошибкой класса dex user210504
Проблема с подходом user210504

Похожие вопросы