随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误:
UNEXPECTED TOP-LEVEL EXCEPTION: java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536 at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501) at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282) at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490) at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167) at com.android.dx.merge.DexMerger.merge(DexMerger.java:188) at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287) at com.android.dx.command.dexer.Main.run(Main.java:230) at com.android.dx.command.dexer.Main.main(Main.java:199) at com.android.dx.command.Main.main(Main.java:103)
没错,你的应用中的Dex 文件方法数超过了最大值65536的上限,简单来说,应用爆棚了.
那么让我们看一下为什么会引起这种错误:
在Android系统中,一个App的所有代码都在一个Dex文件里面。Dex是一个类似Jar的存储了多有Java编译字节码的归档文件。因为Android系统使用Dalvik虚拟机,所以需要把使用Java Compiler编译之后的class文件转换成Dalvik能够执行的class文件。这里需要强调的是,Dex和Jar一样是一个归档文件,里面仍然是Java代码对应的字节码文件。当Android系统启动一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载Dex文件的时候执行的。这个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文件的效率要高很多。但是在早期的Android系统中,DexOpt有一个问题,也就是这篇文章想要说明并解决的问题。DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的Android系统中,DexOpt修复了这个问题,但是我们仍然需要对低版本的Android系统做兼容.
那么问题来了怎么做兼容呢?我自己的实践是这样的:
1、把一些jar包做成classesX.dex(这里的X代表一个整数从2开始例如:classes2.dex、classes3.dex),把做成的dex文件放到src目录下。这里的命名和存放路径都是由android-support-multidex.jar这个包规定的,我本人也是经过反编译后读程序了解到的。
2、在自己的项目下新建lib目录把需要的jar包放在里边然后在Order and Export里边手动选择jar包,不要勾选已经制作成dex的jar就行了,这一步应该了解Order and Export的作用是什么(解释一下吧作用就是勾选的jar会打进你的应用程序里边,不勾选当然就不会打进去了,这个可以自己反编译apk文件看看就清楚了)。
3、让项目里边原来继承Application的类继承MultiDexApplication就可以了。至于为啥这样做看了android-support-multidex.jar里边的源码就很清楚了,当然了java基础要过关哦。
下面是android-support-multidex.jar的源码:
//MultiDexApplication.java package android.support.multidex; import android.app.Application; import android.content.Context; // Referenced classes of package android.support.multidex: // MultiDex public class MultiDexApplication extends Application { public MultiDexApplication() { } protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
//MultiDexExtractor.java package android.support.multidex; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.os.Build; import android.util.Log; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.zip.*; // Referenced classes of package android.support.multidex: // ZipUtil final class MultiDexExtractor { private static final String TAG = "MultiDex"; private static final String DEX_PREFIX = "classes"; private static final String DEX_SUFFIX = ".dex"; private static final String EXTRACTED_NAME_EXT = ".classes"; private static final String EXTRACTED_SUFFIX = ".zip"; private static final int MAX_EXTRACT_ATTEMPTS = 3; private static final String PREFS_FILE = "multidex.version"; private static final String KEY_TIME_STAMP = "timestamp"; private static final String KEY_CRC = "crc"; private static final String KEY_DEX_NUMBER = "dex.number"; private static final int BUFFER_SIZE = 16384; private static final long NO_VALUE = -1L; private static Method sApplyMethod; MultiDexExtractor() { } static List load(Context context, ApplicationInfo applicationInfo, File dexDir, boolean forceReload) throws IOException { Log.i("MultiDex", (new StringBuilder()).append("MultiDexExtractor.load(").append(applicationInfo.sourceDir).append(", ").append(forceReload).append(")").toString()); File sourceApk = new File(applicationInfo.sourceDir); long currentCrc = getZipCrc(sourceApk); List files; if (!forceReload && !isModified(context, sourceApk, currentCrc)) { try { files = loadExistingExtractions(context, sourceApk, dexDir); } catch (IOException ioe) { Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction", ioe); files = performExtractions(sourceApk, dexDir); putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1); } } else { Log.i("MultiDex", "Detected that extraction must be performed."); files = performExtractions(sourceApk, dexDir); putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1); } Log.i("MultiDex", (new StringBuilder()).append("load found ").append(files.size()).append(" secondary dex files").toString()); return files; } private static List loadExistingExtractions(Context context, File sourceApk, File dexDir) throws IOException { Log.i("MultiDex", "loading existing secondary dex files"); String extractedFilePrefix = (new StringBuilder()).append(sourceApk.getName()).append(".classes").toString(); int totalDexNumber = getMultiDexPreferences(context).getInt("dex.number", 1); List files = new ArrayList(totalDexNumber); for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) { String fileName = (new StringBuilder()).append(extractedFilePrefix).append(secondaryNumber).append(".zip").toString(); File extractedFile = new File(dexDir, fileName); if (extractedFile.isFile()) { files.add(extractedFile); if (!verifyZipFile(extractedFile)) { Log.i("MultiDex", (new StringBuilder()).append("Invalid zip file: ").append(extractedFile).toString()); throw new IOException("Invalid ZIP file."); } } else { throw new IOException((new StringBuilder()).append("Missing extracted secondary dex file '").append(extractedFile.getPath()).append("'").toString()); } } return files; } private static boolean isModified(Context context, File archive, long currentCrc) { SharedPreferences prefs = getMultiDexPreferences(context); return prefs.getLong("timestamp", -1L) != getTimeStamp(archive) || prefs.getLong("crc", -1L) != currentCrc; } private static long getTimeStamp(File archive) { long timeStamp = archive.lastModified(); if (timeStamp == -1L) timeStamp--; return timeStamp; } private static long getZipCrc(File archive) throws IOException { long computedValue = ZipUtil.getZipCrc(archive); if (computedValue == -1L) computedValue--; return computedValue; } private static List performExtractions(File sourceApk, File dexDir) throws IOException { String extractedFilePrefix; List files; ZipFile apk; extractedFilePrefix = (new StringBuilder()).append(sourceApk.getName()).append(".classes").toString(); prepareDexDir(dexDir, extractedFilePrefix); files = new ArrayList(); apk = new ZipFile(sourceApk); int secondaryNumber = 2; for (ZipEntry dexFile = apk.getEntry((new StringBuilder()).append("classes").append(secondaryNumber).append(".dex").toString()); dexFile != null; dexFile = apk.getEntry((new StringBuilder()).append("classes").append(secondaryNumber).append(".dex").toString())) { String fileName = (new StringBuilder()).append(extractedFilePrefix).append(secondaryNumber).append(".zip").toString(); File extractedFile = new File(dexDir, fileName); files.add(extractedFile); Log.i("MultiDex", (new StringBuilder()).append("Extraction is needed for file ").append(extractedFile).toString()); int numAttempts = 0; boolean isExtractionSuccessful = false; do { if (numAttempts >= 3 || isExtractionSuccessful) break; numAttempts++; extract(apk, dexFile, extractedFile, extractedFilePrefix); isExtractionSuccessful = verifyZipFile(extractedFile); Log.i("MultiDex", (new StringBuilder()).append("Extraction ").append(isExtractionSuccessful ? "success" : "failed").append(" - length ").append(extractedFile.getAbsolutePath()).append(": ").append(extractedFile.length()).toString()); if (!isExtractionSuccessful) { extractedFile.delete(); if (extractedFile.exists()) Log.w("MultiDex", (new StringBuilder()).append("Failed to delete corrupted secondary dex '").append(extractedFile.getPath()).append("'").toString()); } } while (true); if (!isExtractionSuccessful) throw new IOException((new StringBuilder()).append("Could not create zip file ").append(extractedFile.getAbsolutePath()).append(" for secondary dex (").append(secondaryNumber).append(")").toString()); secondaryNumber++; } try { apk.close(); } catch (IOException e) { Log.w("MultiDex", "Failed to close resource", e); } break MISSING_BLOCK_LABEL_451; Exception exception; exception; try { apk.close(); } catch (IOException e) { Log.w("MultiDex", "Failed to close resource", e); } throw exception; return files; } private static void putStoredApkInfo(Context context, long timeStamp, long crc, int totalDexNumber) { SharedPreferences prefs = getMultiDexPreferences(context); android.content.SharedPreferences.Editor edit = prefs.edit(); edit.putLong("timestamp", timeStamp); edit.putLong("crc", crc); edit.putInt("dex.number", totalDexNumber); apply(edit); } private static SharedPreferences getMultiDexPreferences(Context context) { return context.getSharedPreferences("multidex.version", android.os.Build.VERSION.SDK_INT >= 11 ? 4 : 0); } private static void prepareDexDir(File dexDir, String extractedFilePrefix) throws IOException { File cache = dexDir.getParentFile(); mkdirChecked(cache); mkdirChecked(dexDir); FileFilter filter = new FileFilter(extractedFilePrefix) { final String val$extractedFilePrefix; public boolean accept(File pathname) { return !pathname.getName().startsWith(extractedFilePrefix); } { extractedFilePrefix = s; super(); } }; File files[] = dexDir.listFiles(filter); if (files == null) { Log.w("MultiDex", (new StringBuilder()).append("Failed to list secondary dex dir content (").append(dexDir.getPath()).append(").").toString()); return; } File arr$[] = files; int len$ = arr$.length; for (int i$ = 0; i$ < len$; i$++) { File oldFile = arr$[i$]; Log.i("MultiDex", (new StringBuilder()).append("Trying to delete old file ").append(oldFile.getPath()).append(" of size ").append(oldFile.length()).toString()); if (!oldFile.delete()) Log.w("MultiDex", (new StringBuilder()).append("Failed to delete old file ").append(oldFile.getPath()).toString()); else Log.i("MultiDex", (new StringBuilder()).append("Deleted old file ").append(oldFile.getPath()).toString()); } } private static void mkdirChecked(File dir) throws IOException { dir.mkdir(); if (!dir.isDirectory()) { File parent = dir.getParentFile(); if (parent == null) Log.e("MultiDex", (new StringBuilder()).append("Failed to create dir ").append(dir.getPath()).append(". Parent file is null.").toString()); else Log.e("MultiDex", (new StringBuilder()).append("Failed to create dir ").append(dir.getPath()).append(". parent file is a dir ").append(parent.isDirectory()).append(", a file ").append(parent.isFile()).append(", exists ").append(parent.exists()).append(", readable ").append(parent.canRead()).append(", writable ").append(parent.canWrite()).toString()); throw new IOException((new StringBuilder()).append("Failed to create cache directory ").append(dir.getPath()).toString()); } else { return; } } private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, String extractedFilePrefix) throws IOException, FileNotFoundException { InputStream in; ZipOutputStream out; File tmp; in = apk.getInputStream(dexFile); out = null; tmp = File.createTempFile(extractedFilePrefix, ".zip", extractTo.getParentFile()); Log.i("MultiDex", (new StringBuilder()).append("Extracting ").append(tmp.getPath()).toString()); out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp))); ZipEntry classesDex = new ZipEntry("classes.dex"); classesDex.setTime(dexFile.getTime()); out.putNextEntry(classesDex); byte buffer[] = new byte[16384]; for (int length = in.read(buffer); length != -1; length = in.read(buffer)) out.write(buffer, 0, length); out.closeEntry(); out.close(); break MISSING_BLOCK_LABEL_170; Exception exception; exception; out.close(); throw exception; Log.i("MultiDex", (new StringBuilder()).append("Renaming to ").append(extractTo.getPath()).toString()); if (!tmp.renameTo(extractTo)) throw new IOException((new StringBuilder()).append("Failed to rename \"").append(tmp.getAbsolutePath()).append("\" to \"").append(extractTo.getAbsolutePath()).append("\"").toString()); closeQuietly(in); tmp.delete(); break MISSING_BLOCK_LABEL_285; Exception exception1; exception1; closeQuietly(in); tmp.delete(); throw exception1; } static boolean verifyZipFile(File file) { ZipFile zipFile = new ZipFile(file); zipFile.close(); return true; IOException e; e; Log.w("MultiDex", (new StringBuilder()).append("Failed to close zip file: ").append(file.getAbsolutePath()).toString()); break MISSING_BLOCK_LABEL_115; ZipException ex; ex; Log.w("MultiDex", (new StringBuilder()).append("File ").append(file.getAbsolutePath()).append(" is not a valid zip file.").toString(), ex); break MISSING_BLOCK_LABEL_115; ex; Log.w("MultiDex", (new StringBuilder()).append("Got an IOException trying to open zip file: ").append(file.getAbsolutePath()).toString(), ex); return false; } private static void closeQuietly(Closeable closeable) { try { closeable.close(); } catch (IOException e) { Log.w("MultiDex", "Failed to close resource", e); } } private static void apply(android.content.SharedPreferences.Editor editor) { if (sApplyMethod != null) try { sApplyMethod.invoke(editor, new Object[0]); return; } catch (InvocationTargetException unused) { } catch (IllegalAccessException unused) { } editor.commit(); } static { try { Class cls = android/content/SharedPreferences$Editor; sApplyMethod = cls.getMethod("apply", new Class[0]); } catch (NoSuchMethodException unused) { sApplyMethod = null; } } }
//MultiDex.java package android.support.multidex; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; import dalvik.system.DexFile; import java.io.File; import java.io.IOException; import java.lang.reflect.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipFile; // Referenced classes of package android.support.multidex: // MultiDexExtractor public final class MultiDex { private static final class V4 { private static void install(ClassLoader loader, List additionalClassPathEntries) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, IOException { int extraSize = additionalClassPathEntries.size(); Field pathField = MultiDex.findField(loader, "path"); StringBuilder path = new StringBuilder((String)pathField.get(loader)); String extraPaths[] = new String[extraSize]; File extraFiles[] = new File[extraSize]; ZipFile extraZips[] = new ZipFile[extraSize]; DexFile extraDexs[] = new DexFile[extraSize]; for (ListIterator iterator = additionalClassPathEntries.listIterator(); iterator.hasNext();) { File additionalEntry = (File)iterator.next(); String entryPath = additionalEntry.getAbsolutePath(); path.append(':').append(entryPath); int index = iterator.previousIndex(); extraPaths[index] = entryPath; extraFiles[index] = additionalEntry; extraZips[index] = new ZipFile(additionalEntry); extraDexs[index] = DexFile.loadDex(entryPath, (new StringBuilder()).append(entryPath).append(".dex").toString(), 0); } pathField.set(loader, path.toString()); MultiDex.expandFieldArray(loader, "mPaths", extraPaths); MultiDex.expandFieldArray(loader, "mFiles", extraFiles); MultiDex.expandFieldArray(loader, "mZips", extraZips); MultiDex.expandFieldArray(loader, "mDexs", extraDexs); } private V4() { } } private static final class V14 { private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException { Field pathListField = MultiDex.findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory)); } private static Object[] makeDexElements(Object dexPathList, ArrayList files, File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", new Class[] { java/util/ArrayList, java/io/File }); return (Object[])(Object[])makeDexElements.invoke(dexPathList, new Object[] { files, optimizedDirectory }); } private V14() { } } private static final class V19 { private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException { Field pathListField = MultiDex.findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ArrayList suppressedExceptions = new ArrayList(); MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions)); if (suppressedExceptions.size() > 0) { IOException e; for (Iterator i$ = suppressedExceptions.iterator(); i$.hasNext(); Log.w("MultiDex", "Exception in makeDexElement", e)) e = (IOException)i$.next(); Field suppressedExceptionsField = MultiDex.findField(loader, "dexElementsSuppressedExceptions"); IOException dexElementsSuppressedExceptions[] = (IOException[])(IOException[])suppressedExceptionsField.get(loader); if (dexElementsSuppressedExceptions == null) { dexElementsSuppressedExceptions = (IOException[])suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); } else { IOException combined[] = new IOException[suppressedExceptions.size() + dexElementsSuppressedExceptions.length]; suppressedExceptions.toArray(combined); System.arraycopy(dexElementsSuppressedExceptions, 0, combined, suppressedExceptions.size(), dexElementsSuppressedExceptions.length); dexElementsSuppressedExceptions = combined; } suppressedExceptionsField.set(loader, dexElementsSuppressedExceptions); } } private static Object[] makeDexElements(Object dexPathList, ArrayList files, File optimizedDirectory, ArrayList suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", new Class[] { java/util/ArrayList, java/io/File, java/util/ArrayList }); return (Object[])(Object[])makeDexElements.invoke(dexPathList, new Object[] { files, optimizedDirectory, suppressedExceptions }); } private V19() { } } static final String TAG = "MultiDex"; private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes"; private static final String SECONDARY_FOLDER_NAME; private static final int MAX_SUPPORTED_SDK_VERSION = 20; private static final int MIN_SDK_VERSION = 4; private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2; private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1; private static final Set installedApk = new HashSet(); private static final boolean IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version")); private MultiDex() { } public static void install(Context context) { Log.i("MultiDex", "install"); if (IS_VM_MULTIDEX_CAPABLE) { Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled."); return; } if (android.os.Build.VERSION.SDK_INT < 4) throw new RuntimeException((new StringBuilder()).append("Multi dex installation failed. SDK ").append(android.os.Build.VERSION.SDK_INT).append(" is unsupported. Min SDK version is ").append(4).append(".").toString()); ApplicationInfo applicationInfo; String apkPath; ClassLoader loader; RuntimeException e; Throwable t; File dexDir; List files; try { applicationInfo = getApplicationInfo(context); if (applicationInfo == null) return; } catch (Exception e) { Log.e("MultiDex", "Multidex installation failure", e); throw new RuntimeException((new StringBuilder()).append("Multi dex installation failed (").append(e.getMessage()).append(").").toString()); } label0: { synchronized (installedApk) { apkPath = applicationInfo.sourceDir; if (!installedApk.contains(apkPath)) break label0; } return; } installedApk.add(apkPath); if (android.os.Build.VERSION.SDK_INT > 20) Log.w("MultiDex", (new StringBuilder()).append("MultiDex is not guaranteed to work in SDK version ").append(android.os.Build.VERSION.SDK_INT).append(": SDK version higher than ").append(20).append(" should be backed by ").append("runtime with built-in multidex capabilty but it's not the ").append("case here: java.vm.version=\"").append(System.getProperty("java.vm.version")).append("\"").toString()); try { loader = context.getClassLoader(); break MISSING_BLOCK_LABEL_216; } // Misplaced declaration of an exception variable catch (RuntimeException e) { Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.", e); } set; JVM INSTR monitorexit ; return; if (loader != null) break MISSING_BLOCK_LABEL_232; Log.e("MultiDex", "Context class loader is null. Must be running in test mode. Skip patching."); set; JVM INSTR monitorexit ; return; try { clearOldDexDir(context); } // Misplaced declaration of an exception variable catch (Throwable t) { Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", t); } dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME); files = MultiDexExtractor.load(context, applicationInfo, dexDir, false); if (checkValidZipFiles(files)) { installSecondaryDexes(loader, dexDir, files); } else { Log.w("MultiDex", "Files were not valid zip files. Forcing a reload."); files = MultiDexExtractor.load(context, applicationInfo, dexDir, true); if (checkValidZipFiles(files)) installSecondaryDexes(loader, dexDir, files); else throw new RuntimeException("Zip files were not valid."); } set; JVM INSTR monitorexit ; goto _L1 exception; throw exception; _L1: Log.i("MultiDex", "install done"); return; } private static ApplicationInfo getApplicationInfo(Context context) throws android.content.pm.PackageManager.NameNotFoundException { PackageManager pm; String packageName; try { pm = context.getPackageManager(); packageName = context.getPackageName(); } catch (RuntimeException e) { Log.w("MultiDex", "Failure while trying to obtain ApplicationInfo from Context. Must be running in test mode. Skip patching.", e); return null; } if (pm == null || packageName == null) { return null; } else { ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, 128); return applicationInfo; } } static boolean isVMMultidexCapable(String versionString) { boolean isMultidexCapable = false; if (versionString != null) { Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); if (matcher.matches()) try { int major = Integer.parseInt(matcher.group(1)); int minor = Integer.parseInt(matcher.group(2)); isMultidexCapable = major > 2 || major == 2 && minor >= 1; } catch (NumberFormatException e) { } } Log.i("MultiDex", (new StringBuilder()).append("VM with version ").append(versionString).append(isMultidexCapable ? " has multidex support" : " does not have multidex support").toString()); return isMultidexCapable; } private static void installSecondaryDexes(ClassLoader loader, File dexDir, List files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException { if (!files.isEmpty()) if (android.os.Build.VERSION.SDK_INT >= 19) V19.install(loader, files, dexDir); else if (android.os.Build.VERSION.SDK_INT >= 14) V14.install(loader, files, dexDir); else V4.install(loader, files); } private static boolean checkValidZipFiles(List files) { for (Iterator i$ = files.iterator(); i$.hasNext();) { File file = (File)i$.next(); if (!MultiDexExtractor.verifyZipFile(file)) return false; } return true; } private static Field findField(Object instance, String name) throws NoSuchFieldException { Class clazz = instance.getClass(); _L2: if (clazz == null) break; /* Loop/switch isn't completed */ Field field; field = clazz.getDeclaredField(name); if (!field.isAccessible()) field.setAccessible(true); return field; NoSuchFieldException e; e; clazz = clazz.getSuperclass(); if (true) goto _L2; else goto _L1 _L1: throw new NoSuchFieldException((new StringBuilder()).append("Field ").append(name).append(" not found in ").append(instance.getClass()).toString()); } private static transient Method findMethod(Object instance, String name, Class parameterTypes[]) throws NoSuchMethodException { Class clazz = instance.getClass(); _L2: if (clazz == null) break; /* Loop/switch isn't completed */ Method method; method = clazz.getDeclaredMethod(name, parameterTypes); if (!method.isAccessible()) method.setAccessible(true); return method; NoSuchMethodException e; e; clazz = clazz.getSuperclass(); if (true) goto _L2; else goto _L1 _L1: throw new NoSuchMethodException((new StringBuilder()).append("Method ").append(name).append(" with parameters ").append(Arrays.asList(parameterTypes)).append(" not found in ").append(instance.getClass()).toString()); } private static void expandFieldArray(Object instance, String fieldName, Object extraElements[]) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field jlrField = findField(instance, fieldName); Object original[] = (Object[])(Object[])jlrField.get(instance); Object combined[] = (Object[])(Object[])Array.newInstance(((Object) (original)).getClass().getComponentType(), original.length + extraElements.length); System.arraycopy(((Object) (original)), 0, ((Object) (combined)), 0, original.length); System.arraycopy(((Object) (extraElements)), 0, ((Object) (combined)), original.length, extraElements.length); jlrField.set(instance, ((Object) (combined))); } private static void clearOldDexDir(Context context) throws Exception { File dexDir = new File(context.getFilesDir(), "secondary-dexes"); if (dexDir.isDirectory()) { Log.i("MultiDex", (new StringBuilder()).append("Clearing old secondary dex dir (").append(dexDir.getPath()).append(").").toString()); File files[] = dexDir.listFiles(); if (files == null) { Log.w("MultiDex", (new StringBuilder()).append("Failed to list secondary dex dir content (").append(dexDir.getPath()).append(").").toString()); return; } File arr$[] = files; int len$ = arr$.length; for (int i$ = 0; i$ < len$; i$++) { File oldFile = arr$[i$]; Log.i("MultiDex", (new StringBuilder()).append("Trying to delete old file ").append(oldFile.getPath()).append(" of size ").append(oldFile.length()).toString()); if (!oldFile.delete()) Log.w("MultiDex", (new StringBuilder()).append("Failed to delete old file ").append(oldFile.getPath()).toString()); else Log.i("MultiDex", (new StringBuilder()).append("Deleted old file ").append(oldFile.getPath()).toString()); } if (!dexDir.delete()) Log.w("MultiDex", (new StringBuilder()).append("Failed to delete secondary dex dir ").append(dexDir.getPath()).toString()); else Log.i("MultiDex", (new StringBuilder()).append("Deleted old secondary dex dir ").append(dexDir.getPath()).toString()); } } static { SECONDARY_FOLDER_NAME = (new StringBuilder()).append("code_cache").append(File.separator).append("secondary-dexes").toString(); } }
//ZipUtil.java package android.support.multidex; import java.io.*; import java.util.zip.CRC32; import java.util.zip.ZipException; final class ZipUtil { static class CentralDirectory { long offset; long size; CentralDirectory() { } } private static final int ENDHDR = 22; private static final int ENDSIG = 0x6054b50; private static final int BUFFER_SIZE = 16384; ZipUtil() { } static long getZipCrc(File apk) throws IOException { RandomAccessFile raf = new RandomAccessFile(apk, "r"); long l; CentralDirectory dir = findCentralDirectory(raf); l = computeCrcOfCentralDir(raf, dir); raf.close(); return l; Exception exception; exception; raf.close(); throw exception; } static CentralDirectory findCentralDirectory(RandomAccessFile raf) throws IOException, ZipException { long scanOffset = raf.length() - 22L; if (scanOffset < 0L) throw new ZipException((new StringBuilder()).append("File too short to be a zip file: ").append(raf.length()).toString()); long stopOffset = scanOffset - 0x10000L; if (stopOffset < 0L) stopOffset = 0L; int endSig = Integer.reverseBytes(0x6054b50); do { raf.seek(scanOffset); if (raf.readInt() != endSig) { scanOffset--; if (scanOffset < stopOffset) throw new ZipException("End Of Central Directory signature not found"); } else { raf.skipBytes(2); raf.skipBytes(2); raf.skipBytes(2); raf.skipBytes(2); CentralDirectory dir = new CentralDirectory(); dir.size = (long)Integer.reverseBytes(raf.readInt()) & 0xffffffffL; dir.offset = (long)Integer.reverseBytes(raf.readInt()) & 0xffffffffL; return dir; } } while (true); } static long computeCrcOfCentralDir(RandomAccessFile raf, CentralDirectory dir) throws IOException { CRC32 crc = new CRC32(); long stillToRead = dir.size; raf.seek(dir.offset); int length = (int)Math.min(16384L, stillToRead); byte buffer[] = new byte[16384]; length = raf.read(buffer, 0, length); do { if (length == -1) break; crc.update(buffer, 0, length); stillToRead -= length; if (stillToRead == 0L) break; length = (int)Math.min(16384L, stillToRead); length = raf.read(buffer, 0, length); } while (true); return crc.getValue(); } }