zoukankan      html  css  js  c++  java
  • Eclipse版本android 65535解决方案(原理等同android studio现在的分包方式)

      由于工作的需要看了下Eclipse下android65535的解决方案,查了好多文档,真心的发自内心的说一句请不要再拷贝别人的博客了,害人,真害人。

      

      接下来我说下我的实现方式,首先说下65535的最可能的触发原因(三方jar用的太多了)

      首先:合并jar.

      这里合并到jar使用的事ant的脚本,如何你电脑安装了ant,那ok,如果没有安装这里也不啰嗦告诉你怎么安装了,百度一下吧,安装总的来说没啥技术含量。安装ant之后配置如下脚本文件。

      

    <?xml version="1.0" encoding="utf-8"?>  
    <project name="b" basedir="E:libsall" default="makeSuperJar">  
    <target name="makeSuperJar"  description="description">  
        <jar destfile="all.jar">  
        	<zipfileset src="Android_Location_V1.1.2.jar"/>   
            <zipfileset src="Android_Map_2.1.4.jar"/>          
            <zipfileset src="Android_Services_2.1.4.jar"/>
            <zipfileset src="commons-net-3.3.jar"/>
            <zipfileset src="gson-2.2.1.jar"/>
        </jar>  
    </target>  
    </project>
    

      这里你只需要改下你的basedir目录地址,destfile输出文件的名字和zipfileset你需要合并的jar即可。 

      然后将合并的jar转换成dex文件,怎么找到dx工具,见图

      直接在当前路径下执行cmd命令,然后输入dx --dex --output=E:libsclasses.dex E:libsall.jar,这里我写的是我自己的路径。输出文件为classes.dex,由于apk默认会将项目中的class文件编译成classes.dex,所以这里你需要更改下你的输出文件名,这里这个名字要有规范,严格的命名classes2.dex,classes3.dex.....,至于为什么,这是MultiDex的自己要求的,这里是仿Android sutudio的分包方式,请严格执行。

    之后将classes.dex文件放置到项目的src目录下即可。

      现在执行你还差最后一步导入MutiDex类库,你可以在网上去下载,或者直接copy我下边的代码,这里最主要想说的是如何使用

      在你的项目的Application类中配置如下代码:

     1 public class MyApplication extends Application{
     2     
     3     @Override
     4     protected void attachBaseContext(Context base) {
     5         // TODO Auto-generated method stub
     6         super.attachBaseContext(base);
     7         MultiDex.install(this);
     8         
     9     }
    10 }

       到这里基本上配置算是完成了,this all over.

      一下是类库MultiDex的类库Code,有需要的直接拷贝即可,这里不作为关键点来分析

      MultiDex类:

    /*
     * Copyright (C) 2013 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package android.support.multidex;
    
    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;
    
    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;
    
    /**
     * 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);
            }
        }
    
    }

    MultiDexApplication类

     1 /*
     2  * Copyright (C) 2014 The Android Open Source Project
     3  *
     4  * Licensed under the Apache License, Version 2.0 (the "License");
     5  * you may not use this file except in compliance with the License.
     6  * You may obtain a copy of the License at
     7  *
     8  *      http://www.apache.org/licenses/LICENSE-2.0
     9  *
    10  * Unless required by applicable law or agreed to in writing, software
    11  * distributed under the License is distributed on an "AS IS" BASIS,
    12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  * See the License for the specific language governing permissions and
    14  * limitations under the License.
    15  */
    16 
    17 package android.support.multidex;
    18 
    19 import android.app.Application;
    20 import android.content.Context;
    21 
    22 /**
    23  * Minimal MultiDex capable application. To use the legacy multidex library there is 3 possibility:
    24  * <ul>
    25  * <li>Declare this class as the application in your AndroidManifest.xml.</li>
    26  * <li>Have your {@link Application} extends this class.</li>
    27  * <li>Have your {@link Application} override attachBaseContext starting with<br>
    28  * <code>
    29   protected void attachBaseContext(Context base) {<br>
    30     super.attachBaseContext(base);<br>
    31     MultiDex.install(this);
    32     </code></li>
    33  *   <ul>
    34  */
    35 public class MultiDexApplication extends Application {
    36   @Override
    37   protected void attachBaseContext(Context base) {
    38     super.attachBaseContext(base);
    39     MultiDex.install(this);
    40   }
    41 }

    MultiDexExtractor类

      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.support.multidex;
     18 
     19 import android.content.Context;
     20 import android.content.SharedPreferences;
     21 import android.content.pm.ApplicationInfo;
     22 import android.os.Build;
     23 import android.util.Log;
     24 
     25 import java.io.BufferedOutputStream;
     26 import java.io.Closeable;
     27 import java.io.File;
     28 import java.io.FileFilter;
     29 import java.io.FileNotFoundException;
     30 import java.io.FileOutputStream;
     31 import java.io.IOException;
     32 import java.io.InputStream;
     33 import java.lang.reflect.InvocationTargetException;
     34 import java.lang.reflect.Method;
     35 import java.util.ArrayList;
     36 import java.util.List;
     37 import java.util.zip.ZipEntry;
     38 import java.util.zip.ZipException;
     39 import java.util.zip.ZipFile;
     40 import java.util.zip.ZipOutputStream;
     41 
     42 /**
     43  * Exposes application secondary dex files as files in the application data
     44  * directory.
     45  */
     46 final class MultiDexExtractor {
     47 
     48     private static final String TAG = MultiDex.TAG;
     49 
     50     /**
     51      * We look for additional dex files named {@code classes2.dex},
     52      * {@code classes3.dex}, etc.
     53      */
     54     private static final String DEX_PREFIX = "classes";
     55     private static final String DEX_SUFFIX = ".dex";
     56 
     57     private static final String EXTRACTED_NAME_EXT = ".classes";
     58     private static final String EXTRACTED_SUFFIX = ".zip";
     59     private static final int MAX_EXTRACT_ATTEMPTS = 3;
     60 
     61     private static final String PREFS_FILE = "multidex.version";
     62     private static final String KEY_TIME_STAMP = "timestamp";
     63     private static final String KEY_CRC = "crc";
     64     private static final String KEY_DEX_NUMBER = "dex.number";
     65 
     66     /**
     67      * Size of reading buffers.
     68      */
     69     private static final int BUFFER_SIZE = 0x4000;
     70     /* Keep value away from 0 because it is a too probable time stamp value */
     71     private static final long NO_VALUE = -1L;
     72 
     73     /**
     74      * Extracts application secondary dexes into files in the application data
     75      * directory.
     76      *
     77      * @return a list of files that were created. The list may be empty if there
     78      *         are no secondary dex files.
     79      * @throws IOException if encounters a problem while reading or writing
     80      *         secondary dex files
     81      */
     82     static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir,
     83             boolean forceReload) throws IOException {
     84         Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")");
     85         final File sourceApk = new File(applicationInfo.sourceDir);
     86 
     87         long currentCrc = getZipCrc(sourceApk);
     88 
     89         List<File> files;
     90         if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
     91             try {
     92                 files = loadExistingExtractions(context, sourceApk, dexDir);
     93             } catch (IOException ioe) {
     94                 Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
     95                         + " falling back to fresh extraction", ioe);
     96                 files = performExtractions(sourceApk, dexDir);
     97                 putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
     98 
     99             }
    100         } else {
    101             Log.i(TAG, "Detected that extraction must be performed.");
    102             files = performExtractions(sourceApk, dexDir);
    103             putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
    104         }
    105 
    106         Log.i(TAG, "load found " + files.size() + " secondary dex files");
    107         return files;
    108     }
    109 
    110     private static List<File> loadExistingExtractions(Context context, File sourceApk, File dexDir)
    111             throws IOException {
    112         Log.i(TAG, "loading existing secondary dex files");
    113 
    114         final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
    115         int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
    116         final List<File> files = new ArrayList<File>(totalDexNumber);
    117 
    118         for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
    119             String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
    120             File extractedFile = new File(dexDir, fileName);
    121             if (extractedFile.isFile()) {
    122                 files.add(extractedFile);
    123                 if (!verifyZipFile(extractedFile)) {
    124                     Log.i(TAG, "Invalid zip file: " + extractedFile);
    125                     throw new IOException("Invalid ZIP file.");
    126                 }
    127             } else {
    128                 throw new IOException("Missing extracted secondary dex file '" +
    129                         extractedFile.getPath() + "'");
    130             }
    131         }
    132 
    133         return files;
    134     }
    135 
    136     private static boolean isModified(Context context, File archive, long currentCrc) {
    137         SharedPreferences prefs = getMultiDexPreferences(context);
    138         return (prefs.getLong(KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive))
    139                 || (prefs.getLong(KEY_CRC, NO_VALUE) != currentCrc);
    140     }
    141 
    142     private static long getTimeStamp(File archive) {
    143         long timeStamp = archive.lastModified();
    144         if (timeStamp == NO_VALUE) {
    145             // never return NO_VALUE
    146             timeStamp--;
    147         }
    148         return timeStamp;
    149     }
    150 
    151 
    152     private static long getZipCrc(File archive) throws IOException {
    153         long computedValue = ZipUtil.getZipCrc(archive);
    154         if (computedValue == NO_VALUE) {
    155             // never return NO_VALUE
    156             computedValue--;
    157         }
    158         return computedValue;
    159     }
    160 
    161     private static List<File> performExtractions(File sourceApk, File dexDir)
    162             throws IOException {
    163 
    164         final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
    165 
    166         // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that
    167         // contains a secondary dex file in there is not consistent with the latest apk.  Otherwise,
    168         // multi-process race conditions can cause a crash loop where one process deletes the zip
    169         // while another had created it.
    170         prepareDexDir(dexDir, extractedFilePrefix);
    171 
    172         List<File> files = new ArrayList<File>();
    173 
    174         final ZipFile apk = new ZipFile(sourceApk);
    175         try {
    176 
    177             int secondaryNumber = 2;
    178 
    179             ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
    180             while (dexFile != null) {
    181                 String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
    182                 File extractedFile = new File(dexDir, fileName);
    183                 files.add(extractedFile);
    184 
    185                 Log.i(TAG, "Extraction is needed for file " + extractedFile);
    186                 int numAttempts = 0;
    187                 boolean isExtractionSuccessful = false;
    188                 while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {
    189                     numAttempts++;
    190 
    191                     // Create a zip file (extractedFile) containing only the secondary dex file
    192                     // (dexFile) from the apk.
    193                     extract(apk, dexFile, extractedFile, extractedFilePrefix);
    194 
    195                     // Verify that the extracted file is indeed a zip file.
    196                     isExtractionSuccessful = verifyZipFile(extractedFile);
    197 
    198                     // Log the sha1 of the extracted zip file
    199                     Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") +
    200                             " - length " + extractedFile.getAbsolutePath() + ": " +
    201                             extractedFile.length());
    202                     if (!isExtractionSuccessful) {
    203                         // Delete the extracted file
    204                         extractedFile.delete();
    205                         if (extractedFile.exists()) {
    206                             Log.w(TAG, "Failed to delete corrupted secondary dex '" +
    207                                     extractedFile.getPath() + "'");
    208                         }
    209                     }
    210                 }
    211                 if (!isExtractionSuccessful) {
    212                     throw new IOException("Could not create zip file " +
    213                             extractedFile.getAbsolutePath() + " for secondary dex (" +
    214                             secondaryNumber + ")");
    215                 }
    216                 secondaryNumber++;
    217                 dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
    218             }
    219         } finally {
    220             try {
    221                 apk.close();
    222             } catch (IOException e) {
    223                 Log.w(TAG, "Failed to close resource", e);
    224             }
    225         }
    226 
    227         return files;
    228     }
    229 
    230     private static void putStoredApkInfo(Context context, long timeStamp, long crc,
    231             int totalDexNumber) {
    232         SharedPreferences prefs = getMultiDexPreferences(context);
    233         SharedPreferences.Editor edit = prefs.edit();
    234         edit.putLong(KEY_TIME_STAMP, timeStamp);
    235         edit.putLong(KEY_CRC, crc);
    236         /* SharedPreferences.Editor doc says that apply() and commit() "atomically performs the
    237          * requested modifications" it should be OK to rely on saving the dex files number (getting
    238          * old number value would go along with old crc and time stamp).
    239          */
    240         edit.putInt(KEY_DEX_NUMBER, totalDexNumber);
    241         apply(edit);
    242     }
    243 
    244     private static SharedPreferences getMultiDexPreferences(Context context) {
    245         return context.getSharedPreferences(PREFS_FILE,
    246                 Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
    247                         ? Context.MODE_PRIVATE
    248                         : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
    249     }
    250 
    251     /**
    252      * This removes any files that do not have the correct prefix.
    253      */
    254     private static void prepareDexDir(File dexDir, final String extractedFilePrefix)
    255             throws IOException {
    256         dexDir.mkdirs();
    257         if (!dexDir.isDirectory()) {
    258             throw new IOException("Failed to create dex directory " + dexDir.getPath());
    259         }
    260 
    261         // Clean possible old files
    262         FileFilter filter = new FileFilter() {
    263 
    264             @Override
    265             public boolean accept(File pathname) {
    266                 return !pathname.getName().startsWith(extractedFilePrefix);
    267             }
    268         };
    269         File[] files = dexDir.listFiles(filter);
    270         if (files == null) {
    271             Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
    272             return;
    273         }
    274         for (File oldFile : files) {
    275             Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " +
    276                     oldFile.length());
    277             if (!oldFile.delete()) {
    278                 Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
    279             } else {
    280                 Log.i(TAG, "Deleted old file " + oldFile.getPath());
    281             }
    282         }
    283     }
    284 
    285     private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo,
    286             String extractedFilePrefix) throws IOException, FileNotFoundException {
    287 
    288         InputStream in = apk.getInputStream(dexFile);
    289         ZipOutputStream out = null;
    290         File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX,
    291                 extractTo.getParentFile());
    292         Log.i(TAG, "Extracting " + tmp.getPath());
    293         try {
    294             out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
    295             try {
    296                 ZipEntry classesDex = new ZipEntry("classes.dex");
    297                 // keep zip entry time since it is the criteria used by Dalvik
    298                 classesDex.setTime(dexFile.getTime());
    299                 out.putNextEntry(classesDex);
    300 
    301                 byte[] buffer = new byte[BUFFER_SIZE];
    302                 int length = in.read(buffer);
    303                 while (length != -1) {
    304                     out.write(buffer, 0, length);
    305                     length = in.read(buffer);
    306                 }
    307                 out.closeEntry();
    308             } finally {
    309                 out.close();
    310             }
    311             Log.i(TAG, "Renaming to " + extractTo.getPath());
    312             if (!tmp.renameTo(extractTo)) {
    313                 throw new IOException("Failed to rename "" + tmp.getAbsolutePath() +
    314                         "" to "" + extractTo.getAbsolutePath() + """);
    315             }
    316         } finally {
    317             closeQuietly(in);
    318             tmp.delete(); // return status ignored
    319         }
    320     }
    321 
    322     /**
    323      * Returns whether the file is a valid zip file.
    324      */
    325     static boolean verifyZipFile(File file) {
    326         try {
    327             ZipFile zipFile = new ZipFile(file);
    328             try {
    329                 zipFile.close();
    330                 return true;
    331             } catch (IOException e) {
    332                 Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath());
    333             }
    334         } catch (ZipException ex) {
    335             Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.", ex);
    336         } catch (IOException ex) {
    337             Log.w(TAG, "Got an IOException trying to open zip file: " + file.getAbsolutePath(), ex);
    338         }
    339         return false;
    340     }
    341 
    342     /**
    343      * Closes the given {@code Closeable}. Suppresses any IO exceptions.
    344      */
    345     private static void closeQuietly(Closeable closeable) {
    346         try {
    347             closeable.close();
    348         } catch (IOException e) {
    349             Log.w(TAG, "Failed to close resource", e);
    350         }
    351     }
    352 
    353     // The following is taken from SharedPreferencesCompat to avoid having a dependency of the
    354     // multidex support library on another support library.
    355     private static Method sApplyMethod;  // final
    356     static {
    357         try {
    358             Class<?> cls = SharedPreferences.Editor.class;
    359             sApplyMethod = cls.getMethod("apply");
    360         } catch (NoSuchMethodException unused) {
    361             sApplyMethod = null;
    362         }
    363     }
    364 
    365     private static void apply(SharedPreferences.Editor editor) {
    366         if (sApplyMethod != null) {
    367             try {
    368                 sApplyMethod.invoke(editor);
    369                 return;
    370             } catch (InvocationTargetException unused) {
    371                 // fall through
    372             } catch (IllegalAccessException unused) {
    373                 // fall through
    374             }
    375         }
    376         editor.commit();
    377     }
    378 }

    ZipUtil类

      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 /* Apache Harmony HEADER because the code in this class comes mostly from ZipFile, ZipEntry and
     18  * ZipConstants from android libcore.
     19  */
     20 
     21 package android.support.multidex;
     22 
     23 import java.io.File;
     24 import java.io.IOException;
     25 import java.io.RandomAccessFile;
     26 import java.util.zip.CRC32;
     27 import java.util.zip.ZipException;
     28 
     29 /**
     30  * Tools to build a quick partial crc of zip files.
     31  */
     32 final class ZipUtil {
     33     static class CentralDirectory {
     34         long offset;
     35         long size;
     36     }
     37 
     38     /* redefine those constant here because of bug 13721174 preventing to compile using the
     39      * constants defined in ZipFile */
     40     private static final int ENDHDR = 22;
     41     private static final int ENDSIG = 0x6054b50;
     42 
     43     /**
     44      * Size of reading buffers.
     45      */
     46     private static final int BUFFER_SIZE = 0x4000;
     47 
     48     /**
     49      * Compute crc32 of the central directory of an apk. The central directory contains
     50      * the crc32 of each entries in the zip so the computed result is considered valid for the whole
     51      * zip file. Does not support zip64 nor multidisk but it should be OK for now since ZipFile does
     52      * not either.
     53      */
     54     static long getZipCrc(File apk) throws IOException {
     55         RandomAccessFile raf = new RandomAccessFile(apk, "r");
     56         try {
     57             CentralDirectory dir = findCentralDirectory(raf);
     58 
     59             return computeCrcOfCentralDir(raf, dir);
     60         } finally {
     61             raf.close();
     62         }
     63     }
     64 
     65     /* Package visible for testing */
     66     static CentralDirectory findCentralDirectory(RandomAccessFile raf) throws IOException,
     67             ZipException {
     68         long scanOffset = raf.length() - ENDHDR;
     69         if (scanOffset < 0) {
     70             throw new ZipException("File too short to be a zip file: " + raf.length());
     71         }
     72 
     73         long stopOffset = scanOffset - 0x10000 /* ".ZIP file comment"'s max length */;
     74         if (stopOffset < 0) {
     75             stopOffset = 0;
     76         }
     77 
     78         int endSig = Integer.reverseBytes(ENDSIG);
     79         while (true) {
     80             raf.seek(scanOffset);
     81             if (raf.readInt() == endSig) {
     82                 break;
     83             }
     84 
     85             scanOffset--;
     86             if (scanOffset < stopOffset) {
     87                 throw new ZipException("End Of Central Directory signature not found");
     88             }
     89         }
     90         // Read the End Of Central Directory. ENDHDR includes the signature
     91         // bytes,
     92         // which we've already read.
     93 
     94         // Pull out the information we need.
     95         raf.skipBytes(2); // diskNumber
     96         raf.skipBytes(2); // diskWithCentralDir
     97         raf.skipBytes(2); // numEntries
     98         raf.skipBytes(2); // totalNumEntries
     99         CentralDirectory dir = new CentralDirectory();
    100         dir.size = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL;
    101         dir.offset = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL;
    102         return dir;
    103     }
    104 
    105     /* Package visible for testing */
    106     static long computeCrcOfCentralDir(RandomAccessFile raf, CentralDirectory dir)
    107             throws IOException {
    108         CRC32 crc = new CRC32();
    109         long stillToRead = dir.size;
    110         raf.seek(dir.offset);
    111         int length = (int) Math.min(BUFFER_SIZE, stillToRead);
    112         byte[] buffer = new byte[BUFFER_SIZE];
    113         length = raf.read(buffer, 0, length);
    114         while (length != -1) {
    115             crc.update(buffer, 0, length);
    116             stillToRead -= length;
    117             if (stillToRead == 0) {
    118                 break;
    119             }
    120             length = (int) Math.min(BUFFER_SIZE, stillToRead);
    121             length = raf.read(buffer, 0, length);
    122         }
    123         return crc.getValue();
    124     }
    125 }
  • 相关阅读:
    Less的用法
    有关cookie的内容
    有关BOM(Browser Object Model)的内容
    JSON的相关内容
    css3实现菜单栏选中时的过渡效果
    原生JavaScript实现jQuery的hasClass,removeClass,addClass,toggleClass
    JavaScript实现多个菜单的显示隐藏(JavaScript实现二级/三级菜单的显示隐藏)
    用css控制table td内文字超出隐藏
    Centos查看端口占用令
    mysql 远程访问不行解决方法 Host is not allowed to connect to this MySQL server
  • 原文地址:https://www.cnblogs.com/liemng/p/5982221.html
Copyright © 2011-2022 走看看