Android Multidex перечисляет все классы

я хотел бы использовать подход ApplicationWrapepr для использования multidex в моем приложении, как описано здесь https://plus.google.com/104023661970539138053/posts/YTMf8ADTcFg

Я использовал параметр --minimal-main-dex вместе с файлом сохранения, например:

android/support/multidex/ZipUtil.class
android/support/multidex/ZipUtil$CentralDirectory.class
android/support/multidex/MultiDex$V14.class
android/support/multidex/MultiDexExtractor$1.class
android/support/multidex/MultiDexExtractor.class
com/<mypackage>/common/MyApplication.class
com/<mypackage>/common/MyApplicationWrapper.class
com/<mypackage>/common/ui/DashboardActivity.class
android/support/multidex/MultiDexApplication.class
android/support/multidex/MultiDex.class
android/support/multidex/MultiDex$V19.class
android/support/multidex/MultiDex$V4.class

Это приводит к перечисленным классам в моем основном файле dex, что нормально. Затем я использую библиотеку, которая использует следующий код для перечисления всех классов в файле dex, но просто получает записи основного «clesses.dex», а не всех других загруженных файлов dex, потому что новый DexFile проверяет только «classes.dex» :

private static List<String> getPaths(final String[] sourcePaths) {
    List<String> result = new ArrayList<String>();

    for (String s : sourcePaths) {
      try {
        DexFile dexfile = new DexFile(s);
        Enumeration<String> entries = dexfile.entries();

        while (entries.hasMoreElements()) {
          result.add(entries.nextElement());
        }
      } catch (IOException ioe) {
        Log.w(TAG, "cannot open file=" + s + ";Exception=" + ioe.getMessage());
      }
    }

    return result;
}

на данный момент единственный путь определяется с помощью:

application.getApplicationContext().getApplicationInfo().sourceDir;

что приводит к чему-то вроде /data/../myapplicationname.apk

Есть ли другая возможность получить все классы в перечисленных файлах dex? Или все классы в настоящее время в ClassLoaders? Библиотека необходима для проекта и использует этот подход для поиска реализаций компонентов посредством отражения позже.

EDIT1: если обнаружено, что файл class2.dex находится в папке: /data/data/com./code_cache/secondary-dexes/com.-1.apk.classes2.dex

однако при использовании new DexFile() с этим путем IOEsxception выдается с сообщением «невозможно открыть dexfile».


person Denny1989    schedule 29.10.2014    source источник


Ответы (3)


DexFile принимает путь к файлу zip/apk и извлекает его, чтобы найти файл .dex. Поэтому, если вы используете .dex в качестве пути, это вызовет ошибку.

Также Google опубликовал статью Создание приложений с использованием более чем 65 тыс. методов для решения проблемы --multi-dex. .

Я пишу класс для загрузки всех классов. Вы можете прочитать больше на: http://xudshen.info/2014/11/12/list-all-classes-after-multidex/

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import dalvik.system.DexFile;

/**
 * Created by [email protected] on 14/11/13.
 */
public class MultiDexHelper {
    private static final String EXTRACTED_NAME_EXT = ".classes";
    private static final String EXTRACTED_SUFFIX = ".zip";

    private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator +
            "secondary-dexes";

    private static final String PREFS_FILE = "multidex.version";
    private static final String KEY_DEX_NUMBER = "dex.number";

    private static SharedPreferences getMultiDexPreferences(Context context) {
        return context.getSharedPreferences(PREFS_FILE,
                Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
                        ? Context.MODE_PRIVATE
                        : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
    }

    /**
     * get all the dex path
     *
     * @param context the application context
     * @return all the dex path
     * @throws PackageManager.NameNotFoundException
     * @throws IOException
     */
    public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
        File sourceApk = new File(applicationInfo.sourceDir);
        File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

        List<String> sourcePaths = new ArrayList<String>();
        sourcePaths.add(applicationInfo.sourceDir); //add the default apk path

        //the prefix of extracted file, ie: test.classes
        String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
        //the total dex numbers
        int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);

        for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
            //for each dex file, ie: test.classes2.zip, test.classes3.zip...
            String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
            File extractedFile = new File(dexDir, fileName);
            if (extractedFile.isFile()) {
                sourcePaths.add(extractedFile.getAbsolutePath());
                //we ignore the verify zip part
            } else {
                throw new IOException("Missing extracted secondary dex file '" +
                        extractedFile.getPath() + "'");
            }
        }

        return sourcePaths;
    }

    /**
     * get all the classes name in "classes.dex", "classes2.dex", ....
     *
     * @param context the application context
     * @return all the classes name
     * @throws PackageManager.NameNotFoundException
     * @throws IOException
     */
    public static List<String> getAllClasses(Context context) throws PackageManager.NameNotFoundException, IOException {
        List<String> classNames = new ArrayList<String>();
        for (String path : getSourcePaths(context)) {
            try {
                DexFile dexfile = null;
                if (path.endsWith(EXTRACTED_SUFFIX)) {
                    //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                    dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                } else {
                    dexfile = new DexFile(path);
                }
                Enumeration<String> dexEntries = dexfile.entries();
                while (dexEntries.hasMoreElements()) {
                    classNames.add(dexEntries.nextElement());
                }
            } catch (IOException e) {
                throw new IOException("Error at loading dex file '" +
                        path + "'");
            }
        }
        return classNames;
    }
}
person austin.s    schedule 12.11.2014
comment
Привет. Я думаю, это сработает. из-за соображений производительности мы решили использовать явную конфигурацию зависимостей. - person Denny1989; 18.11.2014

Если вы запускаете приложение с мгновенным запуском, путь к файлу DEX необходимо добавить:

// handle dex files built by instant run
        File instantRunFilePath = new File(applicationInfo.dataDir,
                                           "files" + File.separator + "instant-run" + File.separator + "dex");

        if (instantRunFilePath.exists() && instantRunFilePath.isDirectory()) {
            File[] sliceFiles = instantRunFilePath.listFiles();
            for (File sliceFile : sliceFiles) {
                if (null != sliceFile && sliceFile.exists() && sliceFile.isFile() && sliceFile.getName().endsWith(".dex")) {
                    sourcePaths.add(sliceFile.getAbsolutePath());
                }
            }
        }
person Rambo    schedule 07.06.2016

Мое решение работает почти во всех случаях, таких как обычный apk, apk с несколькими индексами или apk с мгновенным запуском.

Идея исходит из исходного кода multi-dex. Я использую отражение, чтобы получить все DexElement из Thread.currentThread().getContextClassLoader(), на самом деле это может быть любой другой classLoader. Способ отражения очень сложен, потому что поле pathList не принадлежит PathClassLoader, а принадлежит его суперклассу. И независимо от того, является ли APK-файлом с несколькими индексами или APK-файлом с мгновенным запуском, поле classLoader->pathList содержит все DexFile, которые вам нужно найти. И перечислить все классы в DexFile не так уж и сложно.

Вот код:

    public static ArrayList<String> findClassesStartWith(String prefix) {
        try {
            ArrayList<String> result = new ArrayList<>();
            ArrayList<DexFile> dexFiles = findAllDexFiles(Thread.currentThread().getContextClassLoader());
            for (DexFile dexFile : dexFiles) {
                Enumeration<String> classNames = dexFile.entries();
                while (classNames.hasMoreElements()) {
                    String className = classNames.nextElement();
                    if (className.startsWith(prefix)) {
                        result.add(className);
                    }
                }
            }
            return result;
        } catch (Exception ignored) {
        }
        return null;
    }

    public static ArrayList<DexFile> findAllDexFiles(ClassLoader classLoader) {
        ArrayList<DexFile> dexFiles = new ArrayList<>();
        try {
            Field pathListField = findField(classLoader, "pathList");
            Object pathList = pathListField.get(classLoader);
            Field dexElementsField = findField(pathList, "dexElements");
            Object[] dexElements = (Object[]) dexElementsField.get(pathList);
            Field dexFileField = findField(dexElements[0], "dexFile");

            for (Object dexElement : dexElements) {
                Object dexFile = dexFileField.get(dexElement);
                dexFiles.add((DexFile) dexFile);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dexFiles;
    }

    private static Field findField(Object instance, String name) throws NoSuchFieldException {
        Class clazz = instance.getClass();

        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(name);
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                return field;
            } catch (NoSuchFieldException var4) {
                clazz = clazz.getSuperclass();
            }
        }

        throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
    }
person KFJK    schedule 27.02.2019