转载from : http://blog.csdn.net/qq_19764133/article/details/54020508
开发了一个项目,不断加功能,终于遇到了个问题,就是方法数量超过65536个了。这就尴尬了,这个功能有必须要,其他的该删的都删了。从网上找了很多方法,试了感觉不完整,不知道是个人理解不够还是别的原因,反正这次记录一下,以便下次需要,又忘记了。
1、首先得装好ant,这个ant配置安装网上一大把,就过了;
2、然后弄个xml文件,格式如下:
<?xml version="1.0" encoding="utf-8"?>
<project name="libs" basedir="D:\all" default="makeSuperJar">
<target name="makeSuperJar" description="description">
<jar destfile="libs.jar">
<zipfileset src="alipaySdk-20160803.jar"/>
<zipfileset src="BaiduLBS_Android.jar"/>
<zipfileset src="gson-2.2.2.jar"/>
<zipfileset src="httpmime-4.1.2.jar"/>
<zipfileset src="javacv.jar"/>
<zipfileset src="javacpp.jar"/>
<zipfileset src="jg_filter_sdk_1.1.jar"/>
<zipfileset src="libammsdk.jar"/>
<zipfileset src="MobCommons-2016.1107.1809.jar"/>
<zipfileset src="MobTools-2016.1107.1809.jar"/>
<zipfileset src="Msc.jar"/>
<zipfileset src="ormlite-android-4.48.jar"/>
<zipfileset src="ormlite-core-4.48.jar"/>
<zipfileset src="ormlite-jdbc-4.48.jar"/>
<zipfileset src="ShareSDK-Core-2.7.10.jar"/>
<zipfileset src="ShareSDK-QQ-2.7.10.jar"/>
<zipfileset src="ShareSDK-QZone-2.7.10.jar"/>
<zipfileset src="ShareSDK-ShortMessage-2.7.10.jar"/>
<zipfileset src="ShareSDK-SinaWeibo-2.7.10.jar"/>
<zipfileset src="ShareSDK-Wechat-2.7.10.jar"/>
<zipfileset src="ShareSDK-Wechat-Core-2.7.10.jar"/>
<zipfileset src="ShareSDK-Wechat-Moments-2.7.10.jar"/>
<zipfileset src="Xg_sdk_v2.43_20160308_1031.jar"/>
<zipfileset src="zxing.jar"/>
</jar>
</target>
</project>
这里你只需要改下你的basedir目录地址,destfile输出文件的名字和zipfileset你需要合并的jar即可。这里一定要注意上面的配置文件和要合并的包在同一个目录下,不然会报无法找到project all的错
3、准备条件做好了以后,打开dos,输入ant -buildfile D:libs\b.xml 这时如果成功了就会D盘all目录下就会出现libs.jar包
4、将打好jar包 项目--右键--Build Path --Configure Build Path --- Libraries ---AddExternal JARs导入,删除以前libs下面的包,前提是这些包都在2中xml里有,
5、生成dex文件,先用dos进入到sdk\build-tools\21.0.0目录下,后面的21.0.0是可以不同的,然后输入dx --dex --output=D:\classes.dex D:\all\libs.jar ,这里libs.jar 是3生成的jar包,成功了就会生成classes.dex文件在D盘根目录下,将文件重命名为classes2.dex直接复制到项目src目录即可。
6、OK,这里到执行还差重要的一步了就是导入MutiDex类库,这个网上一大把,这个文章中有这个类库代码,也是我参照的文章http://www.mamicode.com/info-detail-1578920.html。我这里就多此一举了:
MultiDex类
- import java.io.File;
- import java.io.IOException;
- import java.lang.reflect.Array;
- import java.lang.reflect.Field;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.HashSet;
- import java.util.List;
- import java.util.ListIterator;
- import java.util.Set;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import java.util.zip.ZipFile;
- import android.app.Application;
- import android.content.Context;
- import android.content.pm.ApplicationInfo;
- import android.content.pm.PackageManager;
- import android.content.pm.PackageManager.NameNotFoundException;
- import android.os.Build;
- import android.util.Log;
- import dalvik.system.DexFile;
- /**
- * Monkey patches {@link Context#getClassLoader() the application context class
- * loader} in order to load classes from more than one dex file. The primary
- * {@code classes.dex} must contain the classes necessary for calling this
- * class methods. Secondary dex files named classes2.dex, classes3.dex... found
- * in the application apk will be added to the classloader after first call to
- * {@link #install(Context)}.
- *
- * <p/>
- * This library provides compatibility for platforms with API level 4 through 20. This library does
- * nothing on newer versions of the platform which provide built-in support for secondary dex files.
- */
- public final class MultiDex {
- static final String TAG = "MultiDex";
- private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes";
- private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator +
- "secondary-dexes";
- 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<String> installedApk = new HashSet<String>();
- private static final boolean IS_VM_MULTIDEX_CAPABLE =
- isVMMultidexCapable(System.getProperty("java.vm.version"));
- private MultiDex() {}
- /**
- * Patches the application context class loader by appending extra dex files
- * loaded from the application apk. This method should be called in the
- * attachBaseContext of your {@link Application}, see
- * {@link MultiDexApplication} for more explanation and an example.
- *
- * @param context application context.
- * @throws RuntimeException if an error occurred preventing the classloader
- * extension.
- */
- public static void install(Context context) {
- Log.i(TAG, "install");
- if (IS_VM_MULTIDEX_CAPABLE) {
- Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
- return;
- }
- if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
- throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT
- + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
- }
- try {
- ApplicationInfo applicationInfo = getApplicationInfo(context);
- if (applicationInfo == null) {
- // Looks like running on a test Context, so just return without patching.
- return;
- }
- synchronized (installedApk) {
- String apkPath = applicationInfo.sourceDir;
- if (installedApk.contains(apkPath)) {
- return;
- }
- installedApk.add(apkPath);
- if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
- Log.w(TAG, "MultiDex is not guaranteed to work in SDK version "
- + Build.VERSION.SDK_INT + ": SDK version higher than "
- + MAX_SUPPORTED_SDK_VERSION + " should be backed by "
- + "runtime with built-in multidex capabilty but it‘s not the "
- + "case here: java.vm.version=\""
- + System.getProperty("java.vm.version") + "\"");
- }
- /* The patched class loader is expected to be a descendant of
- * dalvik.system.BaseDexClassLoader. We modify its
- * dalvik.system.DexPathList pathList field to append additional DEX
- * file entries.
- */
- ClassLoader loader;
- try {
- loader = context.getClassLoader();
- } catch (RuntimeException e) {
- /* Ignore those exceptions so that we don‘t break tests relying on Context like
- * a android.test.mock.MockContext or a android.content.ContextWrapper with a
- * null base Context.
- */
- Log.w(TAG, "Failure while trying to obtain Context class loader. " +
- "Must be running in test mode. Skip patching.", e);
- return;
- }
- if (loader == null) {
- // Note, the context class loader is null when running Robolectric tests.
- Log.e(TAG,
- "Context class loader is null. Must be running in test mode. "
- + "Skip patching.");
- return;
- }
- try {
- clearOldDexDir(context);
- } catch (Throwable t) {
- Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "
- + "continuing without cleaning.", t);
- }
- File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
- List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
- if (checkValidZipFiles(files)) {
- installSecondaryDexes(loader, dexDir, files);
- } else {
- Log.w(TAG, "Files were not valid zip files. Forcing a reload.");
- // Try again, but this time force a reload of the zip file.
- files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
- if (checkValidZipFiles(files)) {
- installSecondaryDexes(loader, dexDir, files);
- } else {
- // Second time didn‘t work, give up
- throw new RuntimeException("Zip files were not valid.");
- }
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "Multidex installation failure", e);
- throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");
- }
- Log.i(TAG, "install done");
- }
- private static ApplicationInfo getApplicationInfo(Context context)
- throws NameNotFoundException {
- PackageManager pm;
- String packageName;
- try {
- pm = context.getPackageManager();
- packageName = context.getPackageName();
- } catch (RuntimeException e) {
- /* Ignore those exceptions so that we don‘t break tests relying on Context like
- * a android.test.mock.MockContext or a android.content.ContextWrapper with a null
- * base Context.
- */
- Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " +
- "Must be running in test mode. Skip patching.", e);
- return null;
- }
- if (pm == null || packageName == null) {
- // This is most likely a mock context, so just return without patching.
- return null;
- }
- ApplicationInfo applicationInfo =
- pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
- return applicationInfo;
- }
- /**
- * Identifies if the current VM has a native support for multidex, meaning there is no need for
- * additional installation by this library.
- * @return true if the VM handles multidex
- */
- /* package visible for test */
- 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 > VM_WITH_MULTIDEX_VERSION_MAJOR)
- || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
- && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
- } catch (NumberFormatException e) {
- // let isMultidexCapable be false
- }
- }
- }
- Log.i(TAG, "VM with version " + versionString +
- (isMultidexCapable ?
- " has multidex support" :
- " does not have multidex support"));
- return isMultidexCapable;
- }
- private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)
- throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
- InvocationTargetException, NoSuchMethodException, IOException {
- if (!files.isEmpty()) {
- if (Build.VERSION.SDK_INT >= 19) {
- V19.install(loader, files, dexDir);
- } else if (Build.VERSION.SDK_INT >= 14) {
- V14.install(loader, files, dexDir);
- } else {
- V4.install(loader, files);
- }
- }
- }
- /**
- * Returns whether all files in the list are valid zip files. If {@code files} is empty, then
- * returns true.
- */
- private static boolean checkValidZipFiles(List<File> files) {
- for (File file : files) {
- if (!MultiDexExtractor.verifyZipFile(file)) {
- return false;
- }
- }
- return true;
- }
- /**
- * Locates a given field anywhere in the class inheritance hierarchy.
- *
- * @param instance an object to search the field into.
- * @param name field name
- * @return a field object
- * @throws NoSuchFieldException if the field cannot be located
- */
- private static Field findField(Object instance, String name) throws NoSuchFieldException {
- for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
- try {
- Field field = clazz.getDeclaredField(name);
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
- return field;
- } catch (NoSuchFieldException e) {
- // ignore and search next
- }
- }
- throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
- }
- /**
- * Locates a given method anywhere in the class inheritance hierarchy.
- *
- * @param instance an object to search the method into.
- * @param name method name
- * @param parameterTypes method parameter types
- * @return a method object
- * @throws NoSuchMethodException if the method cannot be located
- */
- private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
- throws NoSuchMethodException {
- for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
- try {
- Method method = clazz.getDeclaredMethod(name, parameterTypes);
- if (!method.isAccessible()) {
- method.setAccessible(true);
- }
- return method;
- } catch (NoSuchMethodException e) {
- // ignore and search next
- }
- }
- throw new NoSuchMethodException("Method " + name + " with parameters " +
- Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
- }
- /**
- * Replace the value of a field containing a non null array, by a new array containing the
- * elements of the original array plus the elements of extraElements.
- * @param instance the instance whose field is to be modified.
- * @param fieldName the field to modify.
- * @param extraElements elements to append at the end of the array.
- */
- private static void expandFieldArray(Object instance, String fieldName,
- Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
- IllegalAccessException {
- Field jlrField = findField(instance, fieldName);
- Object[] original = (Object[]) jlrField.get(instance);
- Object[] combined = (Object[]) Array.newInstance(
- original.getClass().getComponentType(), original.length + extraElements.length);
- System.arraycopy(original, 0, combined, 0, original.length);
- System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
- jlrField.set(instance, combined);
- }
- private static void clearOldDexDir(Context context) throws Exception {
- File dexDir = new File(context.getFilesDir(), OLD_SECONDARY_FOLDER_NAME);
- if (dexDir.isDirectory()) {
- Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath() + ").");
- File[] files = dexDir.listFiles();
- if (files == null) {
- Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
- return;
- }
- for (File oldFile : files) {
- Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size "
- + oldFile.length());
- if (!oldFile.delete()) {
- Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
- } else {
- Log.i(TAG, "Deleted old file " + oldFile.getPath());
- }
- }
- if (!dexDir.delete()) {
- Log.w(TAG, "Failed to delete secondary dex dir " + dexDir.getPath());
- } else {
- Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath());
- }
- }
- }
- /**
- * Installer for platform versions 19.
- */
- private static final class V19 {
- private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
- File optimizedDirectory)
- throws IllegalArgumentException, IllegalAccessException,
- NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
- /* The patched class loader is expected to be a descendant of
- * dalvik.system.BaseDexClassLoader. We modify its
- * dalvik.system.DexPathList pathList field to append additional DEX
- * file entries.
- */
- Field pathListField = findField(loader, "pathList");
- Object dexPathList = pathListField.get(loader);
- ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
- expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
- new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
- suppressedExceptions));
- if (suppressedExceptions.size() > 0) {
- for (IOException e : suppressedExceptions) {
- Log.w(TAG, "Exception in makeDexElement", e);
- }
- Field suppressedExceptionsField =
- findField(loader, "dexElementsSuppressedExceptions");
- IOException[] dexElementsSuppressedExceptions =
- (IOException[]) suppressedExceptionsField.get(loader);
- if (dexElementsSuppressedExceptions == null) {
- dexElementsSuppressedExceptions =
- 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);
- }
- }
- /**
- * A wrapper around
- * {@code private static final dalvik.system.DexPathList#makeDexElements}.
- */
- private static Object[] makeDexElements(
- Object dexPathList, ArrayList<File> files, File optimizedDirectory,
- ArrayList<IOException> suppressedExceptions)
- throws IllegalAccessException, InvocationTargetException,
- NoSuchMethodException {
- Method makeDexElements =
- findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
- ArrayList.class);
- return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
- suppressedExceptions);
- }
- }
- /**
- * Installer for platform versions 14, 15, 16, 17 and 18.
- */
- private static final class V14 {
- private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
- File optimizedDirectory)
- throws IllegalArgumentException, IllegalAccessException,
- NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
- /* The patched class loader is expected to be a descendant of
- * dalvik.system.BaseDexClassLoader. We modify its
- * dalvik.system.DexPathList pathList field to append additional DEX
- * file entries.
- */
- Field pathListField = findField(loader, "pathList");
- Object dexPathList = pathListField.get(loader);
- expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
- new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
- }
- /**
- * A wrapper around
- * {@code private static final dalvik.system.DexPathList#makeDexElements}.
- */
- private static Object[] makeDexElements(
- Object dexPathList, ArrayList<File> files, File optimizedDirectory)
- throws IllegalAccessException, InvocationTargetException,
- NoSuchMethodException {
- Method makeDexElements =
- findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
- return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
- }
- }
- /**
- * Installer for platform versions 4 to 13.
- */
- private static final class V4 {
- private static void install(ClassLoader loader, List<File> additionalClassPathEntries)
- throws IllegalArgumentException, IllegalAccessException,
- NoSuchFieldException, IOException {
- /* The patched class loader is expected to be a descendant of
- * dalvik.system.DexClassLoader. We modify its
- * fields mPaths, mFiles, mZips and mDexs to append additional DEX
- * file entries.
- */
- int extraSize = additionalClassPathEntries.size();
- Field pathField = 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<File> iterator = additionalClassPathEntries.listIterator();
- iterator.hasNext();) {
- File additionalEntry = 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, entryPath + ".dex", 0);
- }
- pathField.set(loader, path.toString());
- expandFieldArray(loader, "mPaths", extraPaths);
- expandFieldArray(loader, "mFiles", extraFiles);
- expandFieldArray(loader, "mZips", extraZips);
- expandFieldArray(loader, "mDexs", extraDexs);
- }
- }
- }
- import android.app.Application;
- import android.content.Context;
- /**
- * * Minimal MultiDex capable application. To use the legacy multidex library
- * there is 3 possibility: 24 *
- * <ul>
- * *
- * <li>Declare this class as the application in your AndroidManifest.xml.</li>
- * *
- * <li>Have your {@link Application} extends this class.</li>
- * *
- * <li>Have your {@link Application} override attachBaseContext starting with<br>
- * * <code>
- protected void attachBaseContext(Context base) {<br>
- super.attachBaseContext(base);<br>
- MultiDex.install(this);
- </code></li> 3 *
- * <ul>
- */
- public class MultiDexApplication extends Application {
- @Override
- protected void attachBaseContext(Context base) {
- super.attachBaseContext(base);
- MultiDex.install(this);
- }
- }
- import java.io.BufferedOutputStream;
- import java.io.Closeable;
- import java.io.File;
- import java.io.FileFilter;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipException;
- import java.util.zip.ZipFile;
- import java.util.zip.ZipOutputStream;
- import android.content.Context;
- import android.content.SharedPreferences;
- import android.content.pm.ApplicationInfo;
- import android.os.Build;
- import android.util.Log;
- /**
- * Exposes application secondary dex files as files in the application data
- * directory.
- */
- final class MultiDexExtractor {
- private static final String TAG = MultiDex.TAG;
- /**
- * We look for additional dex files named {@code classes2.dex},
- * {@code classes3.dex}, etc.
- */
- 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";
- /**
- * Size of reading buffers.
- */
- private static final int BUFFER_SIZE = 0x4000;
- /* Keep value away from 0 because it is a too probable time stamp value */
- private static final long NO_VALUE = -1L;
- /**
- * Extracts application secondary dexes into files in the application data
- * directory.
- *
- * @return a list of files that were created. The list may be empty if there
- * are no secondary dex files.
- * @throws IOException
- * if encounters a problem while reading or writing secondary
- * dex files
- */
- static List<File> load(Context context, ApplicationInfo applicationInfo,
- File dexDir, boolean forceReload) throws IOException {
- Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", "
- + forceReload + ")");
- final File sourceApk = new File(applicationInfo.sourceDir);
- long currentCrc = getZipCrc(sourceApk);
- List<File> files;
- if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
- try {
- files = loadExistingExtractions(context, sourceApk, dexDir);
- } catch (IOException ioe) {
- Log.w(TAG,
- "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(TAG, "Detected that extraction must be performed.");
- files = performExtractions(sourceApk, dexDir);
- putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc,
- files.size() + 1);
- }
- Log.i(TAG, "load found " + files.size() + " secondary dex files");
- return files;
- }
- private static List<File> loadExistingExtractions(Context context,
- File sourceApk, File dexDir) throws IOException {
- Log.i(TAG, "loading existing secondary dex files");
- final String extractedFilePrefix = sourceApk.getName()
- + EXTRACTED_NAME_EXT;
- int totalDexNumber = getMultiDexPreferences(context).getInt(
- KEY_DEX_NUMBER, 1);
- final List<File> files = new ArrayList<File>(totalDexNumber);
- for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
- String fileName = extractedFilePrefix + secondaryNumber
- + EXTRACTED_SUFFIX;
- File extractedFile = new File(dexDir, fileName);
- if (extractedFile.isFile()) {
- files.add(extractedFile);
- if (!verifyZipFile(extractedFile)) {
- Log.i(TAG, "Invalid zip file: " + extractedFile);
- throw new IOException("Invalid ZIP file.");
- }
- } else {
- throw new IOException("Missing extracted secondary dex file ‘"
- + extractedFile.getPath() + "‘");
- }
- }
- return files;
- }
- private static boolean isModified(Context context, File archive,
- long currentCrc) {
- SharedPreferences prefs = getMultiDexPreferences(context);
- return (prefs.getLong(KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive))
- || (prefs.getLong(KEY_CRC, NO_VALUE) != currentCrc);
- }
- private static long getTimeStamp(File archive) {
- long timeStamp = archive.lastModified();
- if (timeStamp == NO_VALUE) {
- // never return NO_VALUE
- timeStamp--;
- }
- return timeStamp;
- }
- private static long getZipCrc(File archive) throws IOException {
- long computedValue = ZipUtil.getZipCrc(archive);
- if (computedValue == NO_VALUE) {
- // never return NO_VALUE
- computedValue--;
- }
- return computedValue;
- }
- private static List<File> performExtractions(File sourceApk, File dexDir)
- throws IOException {
- final String extractedFilePrefix = sourceApk.getName()
- + EXTRACTED_NAME_EXT;
- // Ensure that whatever deletions happen in prepareDexDir only happen if
- // the zip that
- // contains a secondary dex file in there is not consistent with the
- // latest apk. Otherwise,
- // multi-process race conditions can cause a crash loop where one
- // process deletes the zip
- // while another had created it.
- prepareDexDir(dexDir, extractedFilePrefix);
- List<File> files = new ArrayList<File>();
- final ZipFile apk = new ZipFile(sourceApk);
- try {
- int secondaryNumber = 2;
- ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber
- + DEX_SUFFIX);
- while (dexFile != null) {
- String fileName = extractedFilePrefix + secondaryNumber
- + EXTRACTED_SUFFIX;
- File extractedFile = new File(dexDir, fileName);
- files.add(extractedFile);
- Log.i(TAG, "Extraction is needed for file " + extractedFile);
- int numAttempts = 0;
- boolean isExtractionSuccessful = false;
- while (numAttempts < MAX_EXTRACT_ATTEMPTS
- && !isExtractionSuccessful) {
- numAttempts++;
- // Create a zip file (extractedFile) containing only the
- // secondary dex file
- // (dexFile) from the apk.
- extract(apk, dexFile, extractedFile, extractedFilePrefix);
- // Verify that the extracted file is indeed a zip file.
- isExtractionSuccessful = verifyZipFile(extractedFile);
- // Log the sha1 of the extracted zip file
- Log.i(TAG, "Extraction "
- + (isExtractionSuccessful ? "success" : "failed")
- + " - length " + extractedFile.getAbsolutePath()
- + ": " + extractedFile.length());
- if (!isExtractionSuccessful) {
- // Delete the extracted file
- extractedFile.delete();
- if (extractedFile.exists()) {
- Log.w(TAG,
- "Failed to delete corrupted secondary dex ‘"
- + extractedFile.getPath() + "‘");
- }
- }
- }
- if (!isExtractionSuccessful) {
- throw new IOException("Could not create zip file "
- + extractedFile.getAbsolutePath()
- + " for secondary dex (" + secondaryNumber + ")");
- }
- secondaryNumber++;
- dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber
- + DEX_SUFFIX);
- }
- } finally {
- try {
- apk.close();
- } catch (IOException e) {
- Log.w(TAG, "Failed to close resource", e);
- }
- }
- return files;
- }
- private static void putStoredApkInfo(Context context, long timeStamp,
- long crc, int totalDexNumber) {
- SharedPreferences prefs = getMultiDexPreferences(context);
- SharedPreferences.Editor edit = prefs.edit();
- edit.putLong(KEY_TIME_STAMP, timeStamp);
- edit.putLong(KEY_CRC, crc);
- /*
- * SharedPreferences.Editor doc says that apply() and commit()
- * "atomically performs the requested modifications" it should be OK to
- * rely on saving the dex files number (getting old number value would
- * go along with old crc and time stamp).
- */
- edit.putInt(KEY_DEX_NUMBER, totalDexNumber);
- apply(edit);
- }
- 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);
- }
- /**
- * This removes any files that do not have the correct prefix.
- */
- private static void prepareDexDir(File dexDir,
- final String extractedFilePrefix) throws IOException {
- dexDir.mkdirs();
- if (!dexDir.isDirectory()) {
- throw new IOException("Failed to create dex directory "
- + dexDir.getPath());
- }
- // Clean possible old files
- FileFilter filter = new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return !pathname.getName().startsWith(extractedFilePrefix);
- }
- };
- File[] files = dexDir.listFiles(filter);
- if (files == null) {
- Log.w(TAG,
- "Failed to list secondary dex dir content ("
- + dexDir.getPath() + ").");
- return;
- }
- for (File oldFile : files) {
- Log.i(TAG, "Trying to delete old file " + oldFile.getPath()
- + " of size " + oldFile.length());
- if (!oldFile.delete()) {
- Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
- } else {
- Log.i(TAG, "Deleted old file " + oldFile.getPath());
- }
- }
- }
- private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo,
- String extractedFilePrefix) throws IOException,
- FileNotFoundException {
- InputStream in = apk.getInputStream(dexFile);
- ZipOutputStream out = null;
- File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX,
- extractTo.getParentFile());
- Log.i(TAG, "Extracting " + tmp.getPath());
- try {
- out = new ZipOutputStream(new BufferedOutputStream(
- new FileOutputStream(tmp)));
- try {
- ZipEntry classesDex = new ZipEntry("classes.dex");
- // keep zip entry time since it is the criteria used by Dalvik
- classesDex.setTime(dexFile.getTime());
- out.putNextEntry(classesDex);
- byte[] buffer = new byte[BUFFER_SIZE];
- int length = in.read(buffer);
- while (length != -1) {
- out.write(buffer, 0, length);
- length = in.read(buffer);
- }
- out.closeEntry();
- } finally {
- out.close();
- }
- Log.i(TAG, "Renaming to " + extractTo.getPath());
- if (!tmp.renameTo(extractTo)) {
- throw new IOException("Failed to rename \""
- + tmp.getAbsolutePath() + "\" to \""
- + extractTo.getAbsolutePath() + "\"");
- }
- } finally {
- closeQuietly(in);
- tmp.delete(); // return status ignored
- }
- }
- /**
- * Returns whether the file is a valid zip file.
- */
- static boolean verifyZipFile(File file) {
- try {
- ZipFile zipFile = new ZipFile(file);
- try {
- zipFile.close();
- return true;
- } catch (IOException e) {
- Log.w(TAG,
- "Failed to close zip file: " + file.getAbsolutePath());
- }
- } catch (ZipException ex) {
- Log.w(TAG, "File " + file.getAbsolutePath()
- + " is not a valid zip file.", ex);
- } catch (IOException ex) {
- Log.w(TAG,
- "Got an IOException trying to open zip file: "
- + file.getAbsolutePath(), ex);
- }
- return false;
- }
- /**
- * Closes the given {@code Closeable}. Suppresses any IO exceptions.
- */
- private static void closeQuietly(Closeable closeable) {
- try {
- closeable.close();
- } catch (IOException e) {
- Log.w(TAG, "Failed to close resource", e);
- }
- }
- // The following is taken from SharedPreferencesCompat to avoid having a
- // dependency of the
- // multidex support library on another support library.
- private static Method sApplyMethod; // final
- static {
- try {
- Class<?> cls = SharedPreferences.Editor.class;
- sApplyMethod = cls.getMethod("apply");
- } catch (NoSuchMethodException unused) {
- sApplyMethod = null;
- }
- }
- private static void apply(SharedPreferences.Editor editor) {
- if (sApplyMethod != null) {
- try {
- sApplyMethod.invoke(editor);
- return;
- } catch (InvocationTargetException unused) {
- // fall through
- } catch (IllegalAccessException unused) {
- // fall through
- }
- }
- editor.commit();
- }
- }
- import java.io.File;
- import java.io.IOException;
- import java.io.RandomAccessFile;
- import java.util.zip.CRC32;
- import java.util.zip.ZipException;
- /**
- * Tools to build a quick partial crc of zip files.
- */
- final class ZipUtil {
- static class CentralDirectory {
- long offset;
- long size;
- }
- /* redefine those constant here because of bug 13721174 preventing to compile using the
- * constants defined in ZipFile */
- private static final int ENDHDR = 22;
- private static final int ENDSIG = 0x6054b50;
- /**
- * Size of reading buffers.
- */
- private static final int BUFFER_SIZE = 0x4000;
- /**
- * Compute crc32 of the central directory of an apk. The central directory contains
- * the crc32 of each entries in the zip so the computed result is considered valid for the whole
- * zip file. Does not support zip64 nor multidisk but it should be OK for now since ZipFile does
- * not either.
- */
- static long getZipCrc(File apk) throws IOException {
- RandomAccessFile raf = new RandomAccessFile(apk, "r");
- try {
- CentralDirectory dir = findCentralDirectory(raf);
- return computeCrcOfCentralDir(raf, dir);
- } finally {
- raf.close();
- }
- }
- /* Package visible for testing */
- static CentralDirectory findCentralDirectory(RandomAccessFile raf) throws IOException,
- ZipException {
- long scanOffset = raf.length() - ENDHDR;
- if (scanOffset < 0) {
- throw new ZipException("File too short to be a zip file: " + raf.length());
- }
- long stopOffset = scanOffset - 0x10000 /* ".ZIP file comment"‘s max length */;
- if (stopOffset < 0) {
- stopOffset = 0;
- }
- int endSig = Integer.reverseBytes(ENDSIG);
- while (true) {
- raf.seek(scanOffset);
- if (raf.readInt() == endSig) {
- break;
- }
- scanOffset--;
- if (scanOffset < stopOffset) {
- throw new ZipException("End Of Central Directory signature not found");
- }
- }
- // Read the End Of Central Directory. ENDHDR includes the signature
- // bytes,
- // which we‘ve already read.
- // Pull out the information we need.
- raf.skipBytes(2); // diskNumber
- raf.skipBytes(2); // diskWithCentralDir
- raf.skipBytes(2); // numEntries
- raf.skipBytes(2); // totalNumEntries
- CentralDirectory dir = new CentralDirectory();
- dir.size = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL;
- dir.offset = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL;
- return dir;
- }
- /* Package visible for testing */
- 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(BUFFER_SIZE, stillToRead);
- byte[] buffer = new byte[BUFFER_SIZE];
- length = raf.read(buffer, 0, length);
- while (length != -1) {
- crc.update(buffer, 0, length);
- stillToRead -= length;
- if (stillToRead == 0) {
- break;
- }
- length = (int) Math.min(BUFFER_SIZE, stillToRead);
- length = raf.read(buffer, 0, length);
- }
- return crc.getValue();
- }
- }
最后还是感谢这http://www.mamicode.com/info-detail-1578920.html、http://blog.csdn.net/lxlyhm/article/details/52608190这两篇文章博主