我想使用ApplicationWrapepr方法在我的应用程序中使用multidex,如https://plus.google.com/104023661970539138053/posts/YTMf8ADTcFg所述
我使用了--minimal-main-dex选项以及这样的keep文件:
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文件中列出的类是可以的。我使用一个使用以下代码的库来列出dexfile中的所有类,但只获取主“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/../application name.apk
还有另一种可能性来列出dex文件中的所有类吗?或者目前在ClassLoaders中的所有课程?该库对项目至关重要,并使用此方法稍后通过反射查找组件实现。
EDIT1:如果发现classes2.dex文件位于:/data/data/com./code_cache/secondary-dexes/com.-1.apk.classes2.dex
但是当使用带有此路径的新DexFile()时,将抛出IOEsxception,并显示消息“无法打开dexfile”。
DexFile接受zip / apk文件的路径,并提取它以查找.dex文件。因此,如果您使用.dex作为路径,则会引发错误。
谷歌也发布了一篇文章Building Apps with Over 65K Methods来解决--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;
}
}
如果使用即时运行运行应用程序,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());
}
}
}
我的解决方案几乎适用于所有情况,例如普通apk,multi-dex apk或即时运行apk。
这个想法来自多索引源代码。我使用反射来获取DexElement
的所有Thread.currentThread().getContextClassLoader()
,实际上它可以是任何其他classLoader。反射方式非常棘手,因为pathList
字段不属于PathClassLoader
,但属于其超类。而且无论apk是multi-dex 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());
}