Есть ли способ получить имена классов в заданном файле class.dex?

Я создаю приложение для домашней автоматизации. Я пытаюсь добавить систему плагинов. В качестве теста я экспортировал тестовый класс (который является подклассом Button) в виде файла APK и поместил его в каталог файлов моего приложения. Я смог создать новый экземпляр этого класса и поместить его в свой вид, используя DexClassLoader и .loadClass.

Следующим шагом будет сканирование всех APK в этом каталоге и получение имен классов в них.

Я нашел класс DexFile, который делает именно это, но выдает следующие исключения:

04-18 17:26:15.697: E/dalvikvm(726): Can't open dex cache '/data/dalvik-cache/data@[email protected]@[email protected]@classes.dex': No such file or directory

04-18 17:26:15.705: I/dalvikvm(726): Unable to open or create cache for /data/data/com.strutton.android.testplugin/files/testloadclass.apk (/data/dalvik-cache/data@[email protected]@[email protected]@classes.dex)

Казалось бы, он пытается найти оптимизированный DexFile в системном кеше, но у меня нет прав доступа к каталогу кеша. Из того, что я вижу, это, вероятно, дизайн, и у меня нет проблем с этим. Есть ли другой способ проанализировать файл DEX и получить из него имена классов?

Я просмотрел исходный код некоторых проектов декомпилятора dex. Должен ли я свернуть свое собственное решение?

Вот код моего тестового приложения (из моей Activity OnCreate) на случай, если я что-то пропустил:

    try {
        ArrayList<String> UIPlugins = new ArrayList<String>();
        final File filesDir = this.getFilesDir();
        final File tmpDir = getDir("dex", 0);
        final DexClassLoader classloader = new DexClassLoader( filesDir.getAbsolutePath()+"/testloadclass.apk",
                tmpDir.getAbsolutePath(),
                null, this.getClass().getClassLoader());
        final Class<Button> classToLoad = 
                (Class<Button>) classloader.loadClass("com.strutton.android.testloadclass.MyTestClass_IRDroidUIPlugIn");
        Button mybutton = classToLoad.getDeclaredConstructor(Context.class).newInstance(this);
        mybutton.setId(2);
        mybutton.setOnClickListener(this);
        main.addView(mybutton);
        // Up to here everything works as expected
        // This line throws the exceptions
        DexFile mDexFile = new DexFile(filesDir.getAbsolutePath()+"/testloadclass.apk";);
        Enumeration<String> classNames = mDexFile.entries();
        while (classNames.hasMoreElements()){
            String className = classNames.nextElement();
            if (className.endsWith("IRDroidUIPlugIn")){
                UIPlugins.add(className);
            }
        }
        final Class<Button> classToLoad = 
                (Class<Button>) classloader.loadClass("com.strutton.android.testloadclass.MyTestClass_IRDroidUIPlugIn");
        Button mybutton = classToLoad.getDeclaredConstructor(Context.class).newInstance(this);
        mybutton.setId(2);
        mybutton.setOnClickListener(this);
        main.addView(mybutton);
        btn.setText(tmpDir.getAbsolutePath());
      } catch (Exception e) {
        e.printStackTrace();
    }

person cstrutton    schedule 18.04.2012    source источник


Ответы (2)


Вместо:

DexFile mDexFile = new DexFile(filesDir.getAbsolutePath()+"/testloadclass.apk";);

пытаться:

DexFile mDexFile = DexFile.loadDex(filesDir.getAbsolutePath()+"/testloadclass.apk", tmpDir.getAbsolutePath()+"/testloadclass.odex", 0);
person JesusFreke    schedule 18.04.2012
comment
Это бревенчатый кот из вашего предложения: 04-18 18:06:27.494: E/dalvikvm(833): DexOpt: failed reading opt header: Is a directory 04-18 18:06:27.494: W/dalvikvm(833): Cached DEX '/data/data/com.strutton.android.testplugin/files/testloadclass.apk'(/data/data/com.strutton.android.testplugin/app_dex) is stale and not writable 04-18 18:06:27.494: I/dalvikvm(833): Unable to open or create cache for /data/data/com.strutton.android.testplugin/files/testloadclass.apk (/data/data/com.strutton.android.testplugin/app_dex) - person cstrutton; 18.04.2012
comment
Обновлено. Похоже, второй путь к loadDex должен быть файлом, а не каталогом. - person JesusFreke; 18.04.2012

Другой подход, который вы могли бы рассмотреть, — установить плагины как отдельные приложения. Вы можете сделать так, чтобы подключаемый модуль предоставлял услугу, используя конкретное намерение, которое вы определяете. Вам также понадобится предопределенный вспомогательный интерфейс, который будет реализован службой. Затем в своем основном приложении вы можете разрешить это намерение службы, чтобы найти любые подключаемые службы.

person JesusFreke    schedule 18.04.2012
comment
Я мог бы использовать этот подход для бэкэнд-сервисов. Например, код, который будет общаться с контроллером X10 или другими устройствами. Однако все, что я видел по этому вопросу, говорит о том, что этот подход не очень хорошо работает для элементов пользовательского интерфейса. Проблемы были связаны с накладными расходами, необходимыми для межпроцессного взаимодействия, если я правильно помню (прошло некоторое время с тех пор, как я смотрел на это). - person cstrutton; 18.04.2012
comment
Если ваши потребности в пользовательском интерфейсе не слишком велики, вы можете использовать функциональность RemoteView для предоставления компонента интерфейса подключаемого модуля. Однако функциональные возможности RemoteView довольно ограничены, поэтому вам необходимо определить, будут ли они достаточно функциональны для удовлетворения ваших потребностей. - person JesusFreke; 18.04.2012
comment
Я посмотрю на это. Спасибо еще раз. - person cstrutton; 18.04.2012