Android对第三方类库运行时加载

首先,把需要运行时加载的类库,放到项目的其他目录,如新建一个thirdlibs的目录。然后用dx命令,对下面的类库进行压缩,做成dex文件。

dx --dex --output=../assets/ *.jar

这会在assets下面生成一个classes.dex文件,压缩成classes.zip文件。

新建一个类,用来加载类库

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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.zip.ZipFile;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.os.Build;
import android.util.Log;
import dalvik.system.DexFile;

public class AssetsDex {

	static final String TAG = "AssetsDex";
	private static final int MAX_SUPPORTED_SDK_VERSION = 20;
	private static final int MIN_SDK_VERSION = 4;
	private static final Set<String> installedApk = new HashSet<String>();
	private static boolean installed = false;

	private AssetsDex() {
	}

	public static void install(Context context) {
		if (installed) {
			return;
		}
		ensureLibs(context);
		Log.i(TAG, "install");
		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;
				}
				File dexDir = context.getDir("outdex", Context.MODE_PRIVATE);
				File[] szFiles = dexDir.listFiles(new FilenameFilter() {

					@Override
					public boolean accept(File dir, String filename) {
						return filename.endsWith(".zip");
					}
				});
				List<File> files = new ArrayList<File>();
				for (File f : szFiles) {
					Log.i(TAG, "load file:" + f.getName());
					files.add(f);
				}
				Log.i(TAG, "loader before:" + context.getClassLoader());
				installSecondaryDexes(loader, dexDir, files);
				Log.i(TAG, "loader end:" + context.getClassLoader());
			}
		} catch (Exception e) {
			Log.e(TAG, "Multidex installation failure", e);
			throw new RuntimeException("Multi dex installation failed ("
					+ e.getMessage() + ").");
		}
		installed = true;
		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;
	}

	public static void ensureLibs(Context context) {

		AssetManager assetManager = context.getAssets();

		InputStream in = null;
		OutputStream out = null;
		try {
			File outdex = context.getDir("outdex", Context.MODE_PRIVATE);
			outdex.mkdir();
			File dex = context.getDir("outdex", Context.MODE_PRIVATE);
			dex.mkdir();
			in = assetManager.open("classes.zip");
			File f = new File(dex, "classes.zip");
			if (f.exists() && f.length() == in.available()) {
				Log.i(TAG, "classes.zip no change");
				return;
			}
			Log.i(TAG, "classes.zip chaneged");
			out = new FileOutputStream(f);

			byte[] buffer = new byte[102400];
			int read;
			while ((read = in.read(buffer)) != -1) {
				out.write(buffer, 0, read);
			}
			in.close();
			in = null;
			out.flush();
			out.close();
			out = null;
			Log.i(TAG, "classes.zip copy over");
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	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);
			}
		}
	}

	/**
	 * 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);
		Log.i(TAG, "install 4");
		jlrField.set(instance, combined);
		Log.i(TAG, "install 5");
	}

	/**
	 * 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.
			 */
			Log.i(TAG, "install 1");
			Field pathListField = findField(loader, "pathList");
			Object dexPathList = pathListField.get(loader);
			Log.i(TAG, "install 2");
			ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
			expandFieldArray(
					dexPathList,
					"dexElements",
					makeDexElements(dexPathList, new ArrayList<File>(
							additionalClassPathEntries), optimizedDirectory,
							suppressedExceptions));
			Log.i(TAG, "install 3");
			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 {
			Log.i(TAG, "install 9");
			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);
		}
	}

}

在程序初始化的地方,如Application或者启动的Activity上,用AssetsDex.install(context),加载类库,一定要在用到类库之前就加载完毕。如果类库过大,第一次加载可能会比较慢。用这种方法的好处是:1、突破64K限制 2、Eclipse下运行速度快(需要每次打包的lib减少了)

时间: 2024-08-11 07:42:20

Android对第三方类库运行时加载的相关文章

如何在运行时加载C++函数和类

如何在运行时加载C++函数和类 标签(空格分隔): 编程 Problem 有些时候你想在运行时加载一个lib或者function or class,这种事情经常发生在你开发一个plugin或者module时遇到. 在C语言里,你可以轻松的利用dlopen, dlsym, dlclose来做到,但是在C++的世界里却没那么简单了.困难就在C++语言的name mangling上,还有一部分就是dlopen函数是用纯C语言写的,不提供load classes功能. 在解析如何load functio

动态链接库DLL的加载:隐式加载(载入时加载)和显式加载(运行时加载)

静态链接库在链接时,编译器会将 .obj 文件和 .LIB 文件组织成一个 .exe 文件,程序运行时,将全部数据加载到内存. 如果程序体积较大,功能较为复杂,那么加载到内存中的时间就会比较长,最直接的一个例子就是双击打开一个软件,要很久才能看到界面.这是静态链接库的一个弊端. 动态链接库有两种加载方式:隐式加载和显示加载. 隐式加载又叫载入时加载,指在主程序载入内存时搜索DLL,并将DLL载入内存.隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受. 显式加载又叫运行

commonJs的运行时加载和es6的编译时加载

参考 : https://www.cnblogs.com/jerrypig/p/8145206.html 1.commonJs的运行时加载 2.ES6编译时加载 原文地址:https://www.cnblogs.com/wfblog/p/9589934.html

linux 运行时加载不上动态库 解决方法(转)

1. 连接和运行时库文件搜索路径到设置     库文件在连接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的.一般 Linux 系统把 /lib 和 /usr/lib 两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用.对于处于默认库搜索路径之外的库,需要将库的位置添加到库的搜索路径之中.设置库文件的搜索路径有下列两种方式,可任选其一使用: (1). 在 /etc/ld.so.conf 文件中添加库的搜索路径.(或

【Android】首次进入应用时加载引导界面

参考文章: [1]http://blog.csdn.net/wsscy2004/article/details/7611529 [2]http://www.androidlearner.net/android-use-viewflow-lift-right-slide.html 这个不同于上一篇文章[Android]每个Activity中加入引导界面 (每个Activity动态加载ImageView的方式).这个引导界面是在初次进入应用时,加载引导页面(采用Activity的方式),进入应用后,

Android插件化(三)加载插件apk中的Resource资源

Android加载插件apk中的Resource资源 简介 如何加载未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源码中发现,它有一个私有方法addAssetPath,只需要将apk的路径作为参数传入,我们就可以获得对应的AssetsManager对象,然后我们就可以使用AssetsManager对象,创建一个Resources对象,然后就可以从Resource对象中访问apk中的资源了.总结如下: 1.新建一个AssetManag

Android下面第三方类库资源文件的加载

有些第三方类库是基于J2SE开发的,内部有资源文件,如properties,这些文件在dex编译的时候会被过滤掉,导致类读取资源的时候无法访问. 解决办法是用运行时加载第三方类库. 1.用DX打包第三方类库 2.把打包后的DEX文件和带目录结构的资源文件,压缩到classes.zip文件里面 3.对Classes.zip进行运行时加载.加载方法见上一个博客

混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集

其调用的方法是从sqlite数据库中获取原来已经使用过的数据库连接,当时也没注意,就是准备设断点然后单步调试,结果竟然是断点无法进入方法体内,后来仔细看了一下方法体的时候发现了一个问题,就是现有的System.Data.Sqlite这个数据访问provider是针对.NET2.0环境开发(最新的版本是1.0.66.0,2010年4月18日发布的),而目前官方也没有给出最新的.NET4的数据访问支持. 既然出现这个问题,那肯定是上GOOGLE搜索解决方案,毕竟微软不可能因为升级到了.NET4.0的

【转】Sqlite 混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该...

开发环境: vs2010+.net framework 4.0+ System.Data.SQLite.DLL (2.0)今天在做Sqlite数据库测试,一运行程序在一处方法调用时报出了一个异常 混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集 其调用的方法是从sqlite数据库中获取原来已经使用过的数据库连接,当时也没注意,就是准备设断点然后单步调试,结果竟然是断点无法进入方法体内,后来仔细看了一下方法体的时候发现了