android中apk/dex的动态加载

本文深入探讨了应用插件化技术的方法,包括两种主要方式及其优缺点。详细介绍了如何在Application中通过反射替换PathClassLoader来实现插件化,并提供了代码示例。此外,文章还讨论了资源ID的处理方法以及如何在主应用和插件之间共享资源。最后,文章鼓励读者访问作者的独立域名博客以获取更多更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明出处。

文章地址:http://blog.youkuaiyun.com/lephones/article/details/40357935

作者:lephones     小窝:www.lefo.me

       终于忍不住开博了,本来自己拿github+hexo搭了一个BLOG,但是,一直没有量啊,没办法,技术性的东西还是得在技术网站交流。当然,我这渣渣技术当优快云专家博是不可能了,偶尔有人进来看看,我就很知足了。所以借优快云的宝地写个文章推广一下,与我的另一个BLOG换个量。

常用插件化的方法介绍

       目前,市场上很多的软件都有了插件化的功能,比如QQ,微信,支付宝,淘宝,还有新浪微博。其使用的方法,不外乎有两种:

(一)   在application中通过反射替换掉pathClassloader,当需要加载插件的类的时候,从自定义的classloader中加载。用此方法的有QQ,支付宝。(本文只介绍该方法)

  • 优点:轻量级,使用的反射相当少,对不同的系统的版本以来也比较小(几乎可以说是没有),插件和主项目属于同一个运行环境下,共用一个Context。
  • 缺点:不能突破AndroidManifest.xml的限制,也就是说,所有的组件必须在清单文件里注册。

(二)   反射framework层,模拟APK的运行环境来跑未安装的APK。淘宝的atlas框架,新浪微博的fusion(目前好像看上去只用于了微博踢球)

  • 优点:插件主APP相对独立,绕过了manifest.xml文件的验证是一样的。
  • 缺点:重量级,因为要突破AndroidManifest.xml的限制,反射的类也多,不同系统版本需要兼容。

对于第二种方式,不是一下子就能搞出来的,就不同系统版本的兼容来讲就得看好多的源码。我的修炼还不到家,在这里我就只能说说比较简单的方法一了。

步骤及原理:

  1. 首先必须有application,在application中,先拿到加载程序本身的PathClassloader,,我们先称它为originalClassloader,因为在你的originalClassloader里,已经保存有对主程序中dex文件的链接,所以,能找到你的主程序的class。
  2. 编写自定义的PathClassloader,我们先称它为myClassloader。内部包含一个originalClassloader引用,在myClassloader构造方法中,要求接收originalClassloader作为参数。
  3. 将application中的originalClassloader的替换成你的myClassloader。在自定义的PathClassloader中,你需要重写findClass方法,以保证主APK中的类,可以从originalClassloader中链接的dex中加载,插件APK中的类,可以从插件的dex中加载。

代码:

           我这里只发一些代码块,其实很简单,大家都能看懂,随便建个工程,复制进去就OK。代码随手试着写的,比较粗糙。

自定义Application类,记得要在清单文件中配置<application>标签。

public class MyApplication extends Application {
	private Field field;
	@Override
	public void onCreate() {
		super.onCreate();
		Context context = getBaseContext();
		Field loadedApkField;
		try {
			loadedApkField= context.getClass().getDeclaredField("mPackageInfo");
			loadedApkField.setAccessible(true);
			Object mPackageInfo = loadedApkField.get(context);
			field = mPackageInfo.getClass().getDeclaredField("mClassLoader");
			field.setAccessible(true);
			//拿到originalclassloader
			Object mClassLoader = field.get(mPackageInfo);
			//创建自定义的classloader
			ClassLoader loader = new MyPathClassLoader(this,
					this.getApplicationInfo().sourceDir,
					(PathClassLoader) mClassLoader);
			//替换originalclassloader为自定义的classloader
			field.set(mPackageInfo, loader);
		} catch (Exception e) {
		}
	}
}

自定义的classloader

public class MyPathClassLoader extends PathClassLoader {
	private ClassLoader mClassLoader;
	private Context context;
	public MyPathClassLoader(Context context,String dexPath, PathClassLoader mClassLoader) {
		
		super(dexPath, mClassLoader);
		this.mClassLoader = mClassLoader;
		this.context = context;
	}
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		File file = new File("/data/data/com.example.test/lib/libtest.so");
		Class clazz = null;
		try {
			clazz = mClassLoader.loadClass(name);
		} catch (Exception e) {
		}
		if (clazz != null) {
			return clazz;
		}
		try {
			DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(), context
					.getDir("dex", 0).getAbsolutePath() + "/libtest.so", 0);
			return dexFile.loadClass(name, ClassLoader.getSystemClassLoader());
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		return super.findClass(name);
	}
	
}
说明:

  • 切记,插件中的所有组件,必须在主APK的AndroidManifest,xml文件中注册,在插件中的AndroidManifest,xml可以不写。
  • 代码中的libtest.so,就是一个APK,改了一下后缀名。大家都知道.so库在运行时候,会从APK中提取出来到/data/data/packagename/lib/目录下,这样就不用我写文件流手动copy了,是吧。

只要加入上面的代码,就具有动态加载的功能了,将你想要作为插件的工程打包成一个APK,在自定义的PathClassLoader中加载进来就行了。

资源ID怎么处理:

       仔细想想,我们只处理了class,安卓还拥有资源文件,怎么办?因为是一个context,所以,资源id也是共享的,插件可以直接访问主工程下的所有资源。如果你的插件下也有资源,在主工程也有资源,只要两份资源没有冲突的id,那你就可以创建AssetManager后,用反射调用2次addAssetPath方法,参数分别是插件apk的路径和主APK的路径(getApplicationInfo().sourceDir),用下面的代码创建了Resources对象后,用反射替换掉 mPackageInfo 的mResources字段(参考application的代码)。

       但是,如果你的资源id有冲突,就需要另当别论了,应该是后加载的会把之前的资源替换掉。我的想法是,在插件内的时候,将mPackageInfo 的mResources改成插件的Resources,退出插件后,再将mPackageInfo 的mResources改成主APK的Resources。这就需要自己写容器管理rs的切换了。

AssetManager am = (AssetManager) AssetManager.class.newInstance();
//
am.getClass().getMethod("addAssetPath", String.class)
			.invoke(am, dexFile.getAbsolutePath());
Resources rs = context.getResources();
Resources res = new Resources(am, rs.getDisplayMetrics(),rs.getConfiguration());
//再用反射替换掉 mPackageInfo 的mResources字段

我这里连个DEMO都不算,有问题可以进一步交流,还是希望大家有空去我独立域名的BLOG坐坐,说不定哪天就更新了。。。哈哈!我的另一个Blog

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值