天天看點

android dex中method及field 65536打包問題

 涉足這個行業多年,總想寫點什麼,但又不知道寫什麼。于是靜下心想想,有什麼可以總結的經驗。目前做Android方面的應用很多人都會遇到dex 65536 的問題,雖然網上有很多這方面的解決方案,但是我感覺不是很完美,因為65536的問題不僅僅隻是method,feild也是65536的限制。那麼我先說說方法數超限的解決方法(本文的亮點在最後哦,希望對你有幫助)。其實這是一種分包方式,然後通過動态加載dex,這個的優點很明顯:不需要定義什麼接口去newInstance,工程裡該怎麼引用還是怎麼引用。但是也有一個小小要求:Application裡不能直接引用分包的dex内的class(這個有解決辦法,後面提到)。

          說到它的優點請看下面兩段code:

<span style="font-size:14px;">try {
      Class clazz = Class.forName("com.jejun.dex.simple.Test");
      ITest instance = (ITest) clazz.newInstance();
      instance.test();
 } catch (Exception e) {
      e.printStackTrace();
 }</span>
           

以上的code在android裡是一般的加載方式,這個缺點很明顯,限制很多,如果是第三方jar包想弄成動态加載,必須得改寫源碼,這樣很麻煩,那麼我們要說的方式就是下面的code:

Test test = new Test();
 test.test();
           

這段code中的Test類已經分包出去,但是寫法跟以前沒分包前沒有差別。同樣的如果要引用第三方jar包,并且把第三方jar包進行動态加載,我們的code不需要做任何改變。下面就詳細介紹下實作過程:

1.在此我用的是一個開源的分包jar(pathtool.jar,請參考:https://github.com/mmin18/Dex65536),首選我們建立一個工程,裡面寫入超過65536個方法的類,并打成jar包,再建立另一工程作為主工程,把剛打的jar放入libs目錄 下,然後在主工程根目錄 下建立一個xml檔案,檔案名随意,隻需要在build.xml裡對應上就要可以,在此我全名為custom_rules.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project name="custom_rules">
    <dirname property="custom_rules.basedir" file="${ant.file.custom_rules}"/>
    <path id="pathtool.antlibs">
        <pathelement path="${custom_rules.basedir}/pathtool.jar" />
    </path>
    <taskdef resource="anttasks.properties" classpathref="pathtool.antlibs" target="_blank" rel="external nofollow" />

    <target name="-post-compile">
        <!--
        如果想要把主工程libs目錄下的所有jar都打到第二個dex裡則libs="libs/",如果需要指定某個或某些jar打入第二個dex則libs="/lockSDK_5.1.jar"或以英文逗号分隔lis="locSDK_5.1.jar,/baidumapapi_v3_4_0.jar"
        -->
        <pathtool
            libs="/locSDK_5.1.jar,/baidumapapi_v3_4_0.jar"
            refid="project.all.jars.path"
            excludeRefid="out.dex.jar.input.ref"
            includeRefid="out.dex.jar.assets" />
        <mkdir dir="${out.absolute.dir}/libs.apk"/>
        <dex executable="${dx}"
                output="${out.absolute.dir}/libs.apk/classes.dex"
                dexedlibs="${out.absolute.dir}/libs.apk"
                nolocals="true"
                forceJumbo="false"
                disableDexMerger="false">
            <path refid="out.dex.jar.assets" />
        </dex>
        <zip destfile="${out.absolute.dir}/libs-unaligned.apk"
            basedir="${out.absolute.dir}/libs.apk"
            includes="classes.dex"
            update="true"/>
        <zipalign
            executable="${zipalign}"
            input="${out.absolute.dir}/libs-unaligned.apk"
            output="${asset.absolute.dir}/libs.apk"
            verbose="${verbose}" />
    </target>

    <target name="-post-package">
        <delete file="${asset.absolute.dir}/libs.apk"/>
    </target>

    <target name="run">
        <xpath input="${manifest.abs.file}" expression="/manifest/@package" output="manifest.package" />
        <xpath input="${manifest.abs.file}" expression="/manifest/application/activity[intent-filter/action/@android:name="android.intent.action.MAIN" and intent-filter/category/@android:name="android.intent.category.LAUNCHER"]/@android:name"
            output="manifest.activity"/>
        <echo>component: ${manifest.package}/${manifest.activity}</echo>
        <exec executable="${adb}" failοnerrοr="false">
            <arg line="${adb.device.arg}" />
            <arg value="shell" />
            <arg value="am" />
            <arg value="force-stop" />
            <arg value="${manifest.package}" />
        </exec>
        <exec executable="${adb}" failοnerrοr="true">
            <arg line="${adb.device.arg}" />
            <arg value="shell" />
            <arg value="am" />
            <arg value="start" />
            <arg value="-n" />
            <arg value="${manifest.package}/${manifest.activity}" />
            <arg value="-W" />
        </exec>
    </target>
    <target name="rund">
        <xpath input="${manifest.abs.file}" expression="/manifest/@package" output="manifest.package" />
        <xpath input="${manifest.abs.file}" expression="/manifest/application/activity[intent-filter/action/@android:name="android.intent.action.MAIN" and intent-filter/category/@android:name="android.intent.category.LAUNCHER"]/@android:name"
            output="manifest.activity"/>
        <echo>component: ${manifest.package}/${manifest.activity}</echo>
        <echo>Debug package ${mainfest.package}. You should prepare your eclipse.</echo>
        <echo>Keep your project open, and if you get a red bug icon in DDMS, you</echo>
        <echo>should stop and manually debug it once.</echo>
        <exec executable="${adb}" failοnerrοr="false">
            <arg line="${adb.device.arg}" />
            <arg value="shell" />
            <arg value="am" />
            <arg value="force-stop" />
            <arg value="${manifest.package}" />
        </exec>
        <exec executable="${adb}" failοnerrοr="true">
            <arg line="${adb.device.arg}" />
            <arg value="shell" />
            <arg value="am" />
            <arg value="set-debug-app" />
            <arg value="${manifest.package}" />
        </exec>
        <exec executable="${adb}" failοnerrοr="true">
            <arg line="${adb.device.arg}" />
            <arg value="shell" />
            <arg value="am" />
            <arg value="start" />
            <arg value="-n" />
            <arg value="${manifest.package}/${manifest.activity}" />
            <arg value="-W" />
            <arg value="-D" />
        </exec>
    </target>

    <target name="help">
        <!-- displays starts at col 13
              |13                                                              80| -->
        <echo>Android Ant Build. Available targets:</echo>
        <echo>   help:      Displays this help.</echo>
        <echo>   clean:     Removes output files created by other targets.</echo>
        <echo>              This calls the same target on all dependent projects.</echo>
        <echo>              Use 'ant nodeps clean' to only clean the local project</echo>
        <echo>   debug:     Builds the application and signs it with a debug key.</echo>
        <echo>              The 'nodeps' target can be used to only build the</echo>
        <echo>              current project and ignore the libraries using:</echo>
        <echo>              'ant nodeps debug'</echo>
        <echo>   release:   Builds the application. The generated apk file must be</echo>
        <echo>              signed before it is published.</echo>
        <echo>              The 'nodeps' target can be used to only build the</echo>
        <echo>              current project and ignore the libraries using:</echo>
        <echo>              'ant nodeps release'</echo>
        <echo>   instrument:Builds an instrumented package and signs it with a</echo>
        <echo>              debug key.</echo>
        <echo>   test:      Runs the tests. Project must be a test project and</echo>
        <echo>              must have been built. Typical usage would be:</echo>
        <echo>                  ant [emma] debug install test</echo>
        <echo>   emma:      Transiently enables code coverage for subsequent</echo>
        <echo>              targets.</echo>
        <echo>   install:   Installs the newly build package. Must either be used</echo>
        <echo>              in conjunction with a build target (debug/release/</echo>
        <echo>              instrument) or with the proper suffix indicating</echo>
        <echo>              which package to install (see below).</echo>
        <echo>              If the application was previously installed, the</echo>
        <echo>              application is reinstalled if the signature matches.</echo>
        <echo>   installd:  Installs (only) the debug package.</echo>
        <echo>   installr:  Installs (only) the release package.</echo>
        <echo>   installi:  Installs (only) the instrumented package.</echo>
        <echo>   installt:  Installs (only) the test and tested packages (unless</echo>
        <echo>              nodeps is used as well.</echo>
        <echo>   uninstall: Uninstalls the application from a running emulator or</echo>
        <echo>              device. Also uninstall tested package if applicable</echo>
        <echo>              unless 'nodeps' is used as well.</echo>
        <echo></echo>
        <echo>Custom targets:</echo>
        <echo>   run:       Run your application.</echo>
        <echo>   rund:      Run and attach to debugger.</echo>
        <echo></echo>
        <echo>--> Example:</echo>
        <echo>-->    ant debug install run</echo>
        <echo>-->    ant rund</echo>
    </target>

</project>
           

完後再打開build.xml确定是否有加入

<import file="custom_rules.xml" optional="true" />
           

如果沒有則手動添加進去,最好是在

<import file="${sdk.dir}/tools/ant/build.xml" />
           

這句話的前面,即:

<import file="custom_rules.xml" optional="true" />
<import file="${sdk.dir}/tools/ant/build.xml" />
           

一切準備工作好像完成,接下來就要用ant指令來執行我們寫的指令了。喔,忘了ant指令是需要有ant編譯環境的,ant相關的知識就不補充了。在此運作ant debug,打成一個debug包,我們剛才打成的dex在asset目錄下,在一個apk的壓縮檔案libs.apk内,當然這個打成的包你是無法調用libs.apk内dex的方法的。接下來就講講怎麼做到無逢的調用(直接new對象)

2.其實大家有去了解classloader的加載原理就會明白,android應用的dex的加載是由一個PathClassLoader加載,這個類加載器有一個父類加載器就是虛拟機的加載器(這個類加載器是加載android系統的class),那麼網上有一種做法是在應用類加載器(PathClassLoader)與虛拟機類加載器之間強行插入一個我們自己的DexClassLoader,直接上Code:

private void dexTool() {  
         FiledexDir = new File(getFilesDir(), "dlibs");  
         dexDir.mkdir();  
         FiledexFile = new File(dexDir, "libs.apk");  
         FiledexOpt = getCacheDir();  
         try{  
                InputStreamins = getAssets().open("libs.apk");  
                if(dexFile.length() != ins.available()) {  
                       FileOutputStreamfos = new FileOutputStream(dexFile);  
                       byte[]buf = new byte[4096];  
                       intl;  
                       while((l = ins.read(buf)) != -1) {  
                              fos.write(buf,0, l);  
                       }  
                       fos.close();  
                }  
                ins.close();  
         }catch (Exception e) {  
                thrownew RuntimeException(e);  
         }  
   
         ClassLoadercl = getClassLoader();  
         ApplicationInfoai = getApplicationInfo();  
         StringnativeLibraryDir = null;  
         if(Build.VERSION.SDK_INT > 8) {  
                nativeLibraryDir= ai.nativeLibraryDir;  
         }else {  
                nativeLibraryDir= "/data/data/" + ai.packageName + "/lib/";  
         }  
         DexClassLoaderdcl = new DexClassLoader(dexFile.getAbsolutePath(),  
                       dexOpt.getAbsolutePath(),nativeLibraryDir, cl.getParent());  
   
         try{  
                Fieldf = ClassLoader.class.getDeclaredField("parent");  
                f.setAccessible(true);  
                f.set(cl,dcl);  
         }catch (Exception e) {  
                thrownew RuntimeException(e);  
         }  
  }  
           

但是,這種做法有一種局限性,就是分包的dex中的類不能引用到沒有分包的dex中的類或方法,這種局限性是由于類的加載機制的限制(樹加載),如圖

android dex中method及field 65536打包問題

此圖表明,類的查找隻有一個方向,是以強行插入的類加載器裡的類如果有引用到PathClassLoader的類則會報找不到某某類(class not find exception)。為了消除這個限制,想到一個辦法,如果有看google官方提供的android-support-multidex的源碼就會知道,如下圖解釋了此種方案的類加載器結構:

android dex中method及field 65536打包問題

下面就提供下兩個檔案的源碼

MultiDex.java

/*
 * File Name:MultiDex.java
 * Copyright:Copyright Jejun All Rights Reserved. 
 * Description:MultiDex.java
 * Modify By:jejun
 * Modify Date:2015-5-24
 * Modify Type:Add
 */
package com.jejun.dex.simple.multidex;

import java.io.File;
import java.io.FilenameFilter;
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.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import dalvik.system.DexFile;

/**
 * 
 * @author jejun
 * @version jejun v.1.0 2015-6-24
 * @since jejun v.1.0
 */
public 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();
 

	public static void install(Context context) {
		Log.i("MultiDex", "install");

		if (Build.VERSION.SDK_INT < 4) {
			throw new RuntimeException("Multi dex installation failed. SDK "
					+ Build.VERSION.SDK_INT
					+ " is unsupported. Min SDK version is " + 4 + ".");
		}
		try {

			ClassLoader loader;
			try {
				loader = context.getClassLoader();
			} catch (RuntimeException e) {
				Log.w("MultiDex",
						"Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.",
						e);
				return;
			}
			if (loader == null) {
				Log.e("MultiDex",
						"Context class loader is null. Must be running in test mode. Skip patching.");

				return;
			}
			File dexDir = MultiDexExtractor.load(context);
			File[] files = dexDir.listFiles(new FilenameFilter() {

				@Override
				public boolean accept(File dir, String filename) {
					return filename.endsWith(".apk");
				}
			});
			Log.i(TAG, "loader before:" + context.getClassLoader());
			installSecondaryDexes(loader, dexDir, Arrays.asList(files));
			Log.i(TAG, "loader end:" + context.getClassLoader());
		} catch (Exception e) {
			Log.e("MultiDex", "Multidex installation failure", e);
			throw new RuntimeException("Multi dex installation failed ("
					+ e.getMessage() + ").");
		}
		Log.i("MultiDex", "install done");
	}

	private static ApplicationInfo getApplicationInfo(Context context)
			throws PackageManager.NameNotFoundException {
		String packageName;
		PackageManager pm;
		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;
		}
		ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
				PackageManager.GET_META_DATA);

		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 localNumberFormatException) {
				}
		}
		Log.i("MultiDex", "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);
		}
	}

	private static Field findField(Object instance, String name)
			throws NoSuchFieldException {
		for (Class clazz = instance.getClass(); clazz != null;) {
			try {
				Field field = clazz.getDeclaredField(name);

				if (!field.isAccessible()) {
					field.setAccessible(true);
				}

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

		}

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

	private static Method findMethod(Object instance, String name,
			Class<?>[] parameterTypes) throws NoSuchMethodException {
		for (Class clazz = instance.getClass(); clazz != null;) {
			try {
				Method method = clazz.getDeclaredMethod(name, parameterTypes);

				if (!method.isAccessible()) {
					method.setAccessible(true);
				}

				return method;
			} catch (NoSuchMethodException localNoSuchMethodException) {
				clazz = clazz.getSuperclass();
			}

		}

		throw new NoSuchMethodException("Method " + name + " with parameters "
				+ Arrays.asList(parameterTypes) + " not found in "
				+ instance.getClass());
	}

	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(), "secondary-dexes");
		if (dexDir.isDirectory()) {
			Log.i("MultiDex",
					"Clearing old secondary dex dir (" + dexDir.getPath()
							+ ").");
			File[] files = dexDir.listFiles();
			if (files == null) {
				Log.w("MultiDex", "Failed to list secondary dex dir content ("
						+ dexDir.getPath() + ").");
				return;
			}
			for (File oldFile : files) {
				Log.i("MultiDex",
						"Trying to delete old file " + oldFile.getPath()
								+ " of size " + oldFile.length());
				if (!oldFile.delete())
					Log.w("MultiDex",
							"Failed to delete old file " + oldFile.getPath());
				else {
					Log.i("MultiDex", "Deleted old file " + oldFile.getPath());
				}
			}
			if (!dexDir.delete())
				Log.w("MultiDex", "Failed to delete secondary dex dir "
						+ dexDir.getPath());
			else
				Log.i("MultiDex",
						"Deleted old secondary dex dir " + dexDir.getPath());
		}
	}

	private static final class V14 {
		private static void install(ClassLoader loader,
				List<File> 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<File> files, File optimizedDirectory)
				throws IllegalAccessException, InvocationTargetException,
				NoSuchMethodException {
			Method makeDexElements = MultiDex.findMethod(dexPathList,
					"makeDexElements", new Class[] { ArrayList.class,
							File.class });

			return (Object[]) makeDexElements.invoke(dexPathList, new Object[] {
					files, optimizedDirectory });
		}
	}

	private static final class V19 {
		private static void install(ClassLoader loader,
				List<File> additionalClassPathEntries, File optimizedDirectory)
				throws IllegalArgumentException, IllegalAccessException,
				NoSuchFieldException, InvocationTargetException,
				NoSuchMethodException {
			Field pathListField = MultiDex.findField(loader, "pathList");
			Object dexPathList = pathListField.get(loader);
			ArrayList<IOException> suppressedExceptions = new ArrayList();
			MultiDex.expandFieldArray(
					dexPathList,
					"dexElements",
					makeDexElements(dexPathList, new ArrayList(
							additionalClassPathEntries), optimizedDirectory,
							suppressedExceptions));
			if (suppressedExceptions.size() > 0) {
				for (IOException e : suppressedExceptions) {
					Log.w("MultiDex", "Exception in makeDexElement", e);
				}
				Field suppressedExceptionsField = MultiDex.findField(loader,
						"dexElementsSuppressedExceptions");
				IOException[] dexElementsSuppressedExceptions = (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<File> files, File optimizedDirectory,
				ArrayList<IOException> suppressedExceptions)
				throws IllegalAccessException, InvocationTargetException,
				NoSuchMethodException {
			Method makeDexElements = MultiDex.findMethod(dexPathList,
					"makeDexElements", new Class[] { ArrayList.class,
							File.class, ArrayList.class });

			return (Object[]) makeDexElements.invoke(dexPathList, new Object[] {
					files, optimizedDirectory, suppressedExceptions });
		}
	}

	private static final class V4 {
		private static void install(ClassLoader loader,
				List<File> 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];
			ListIterator iterator = additionalClassPathEntries.listIterator();
			while (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, entryPath
						+ ".dex", 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);
		}
	}
}
           

MultiDexExtractor.java

/*
 * File Name:MultiDexExtractor.java
 * Copyright:Copyright 1987-2015 Jejun All Rights Reserved. 
 * Description: MultiDexExtractor.java
 * Modify By:l00327619
 * Modify Date:2015-6-24
 * Modify Type:Add
 */
package com.jejun.dex.simple.multidex;


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log; 


final class MultiDexExtractor { 


<span style="white-space:pre">	</span>@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
<span style="white-space:pre">	</span>static File load(Context context) {
<span style="white-space:pre">		</span>File dexDir = new File(context.getCacheDir(), "dlibs"); 
<span style="white-space:pre">		</span>if (!dexDir.exists()) {
<span style="white-space:pre">			</span>dexDir.mkdir();
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>// Log.i("debug", "dex load");
<span style="white-space:pre">		</span>File dexFile = new File(dexDir, "libs.apk");
<span style="white-space:pre">		</span>boolean isExists = dexFile.exists();
<span style="white-space:pre">		</span>if (!isExists || (LepusAppConfiguration.getInstance().isProduce() && curVersionName != null && !curVersionName.equals(lastVersionName)) || LepusAppConfiguration.getInstance().isBeta() || LepusAppConfiguration.getInstance().isSit()) {
<span style="white-space:pre">			</span>InputStream ins = null;
<span style="white-space:pre">			</span>FileOutputStream fos = null;
<span style="white-space:pre">			</span>Log.i("debug", "dex file copy");
<span style="white-space:pre">			</span>try {
<span style="white-space:pre">				</span>ins = context.getAssets().open("libs.apk");
<span style="white-space:pre">				</span>if (dexFile.length() != ins.available()) {
<span style="white-space:pre">					</span>fos = new FileOutputStream(dexFile);
<span style="white-space:pre">					</span>byte[] buf = new byte[4096];
<span style="white-space:pre">					</span>int l;
<span style="white-space:pre">					</span>while ((l = ins.read(buf)) != -1) {
<span style="white-space:pre">						</span>fos.write(buf, 0, l);
<span style="white-space:pre">					</span>}
<span style="white-space:pre">					</span>fos.close();
<span style="white-space:pre">				</span>}
<span style="white-space:pre">				</span>ins.close();
<span style="white-space:pre">			</span>} catch (Exception e) {
<span style="white-space:pre">				</span>// throw new RuntimeException(e);
<span style="white-space:pre">			</span>} finally {
<span style="white-space:pre">				</span>if (fos != null) {
<span style="white-space:pre">					</span>try {
<span style="white-space:pre">						</span>fos.close();
<span style="white-space:pre">					</span>} catch (IOException e) {
<span style="white-space:pre">						</span>e.printStackTrace();
<span style="white-space:pre">					</span>}
<span style="white-space:pre">				</span>}
<span style="white-space:pre">				</span>if (ins != null) {
<span style="white-space:pre">					</span>try {
<span style="white-space:pre">						</span>ins.close();
<span style="white-space:pre">					</span>} catch (IOException e) {
<span style="white-space:pre">						</span>// e.printStackTrace();
<span style="white-space:pre">					</span>}
<span style="white-space:pre">				</span>}
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>return dexDir;
<span style="white-space:pre">	</span>}


}
           
</pre><pre>
           

有了這些後,你隻要在Application中重寫attachBaseContext,在方法中加入MultiDex.install(this)。如果你想在Application裡初始化一些東西,并且初始化中有引用分包中的類(如果直接在onCreate中填寫code會報找不到類的錯誤,因為你在attachBaseContext設定了自己的類加載器,但此時并沒有把分包的dex中class加載到記憶體),告訴你一個方法可以避免(有時候會很好用哦),把你要初始化的代碼用一個Handler包裹起來:

new Handler().post(new Runnable(){

	@Override
	public void run() {
             //初始化code
       }
})
           

後續寫上在一些情景中由于R.java中資源檔案重複導緻field超65536的解決辦法。^_^