Android学习之DexClassLoader类装载器使用

参考 柯元旦:《Android内核剖析》第2.1.1/2.1.2

转载注明出处:http://blog.csdn.net/u012527802/article/details/52620316

下载demo


    在Java环境中,有个概念叫做“类装载器”(ClassLoader),其作用是动态装载Class文件。标准的Java SDK中有一个ClassLoader类,借助它可以装载想要的CLass文件,每个ClassLoader对象在初始化时必须要指定Class文件路径。

在过去的程序开发中,当我们需要某个类是,只需要使用import关键字引入该类就可以。但是import所引用的类文件有两个特点:

  • 必须存在于本地,当程序运行需要该类时,内部类装载器会自动装载。
  • 编译时必须在场,否则编译过程会因为找不到引用文件而不能正常编译。
但是有写情况下,所需要的类不能满足上面两个条件。比如该类是从远程下载到本地执行的,或者所引用的类不方便直接参与编译,只能通过动态装载实现调用。


    在Android应用程序中,虽然使用Java编译Class文件,但是最终的APK文件中包含的确是dex类型的文件。dex文件是将所需要的所有Class文件重新打包,打包的规则不是单纯的压缩。由于dex文件是一种经过优化的Class文件,因此要加载这样特殊的Class文件就要使用Android提供的DexClassLoader。

使用方法及参数说明:

	public void useDexClassLoader() {
		Intent intent = new Intent("com.lzy.plugin.greet");
		PackageManager pm = getPackageManager();
		final List<ResolveInfo> plugins = pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
		if (plugins == null || plugins.size() == 0) {
			Toast.makeText(this, "未找到匹配的Plugin!", Toast.LENGTH_LONG).show(); return;
		}
		ResolveInfo info = plugins.get(0);
		ActivityInfo activityInfo = info.activityInfo;
		
		String div = System.getProperty("path.separator"); // 特定分隔符
		String packageName = activityInfo.packageName; // plugin包名
		String dexPath = activityInfo.applicationInfo.sourceDir; // plugin apk或jar路径
		String dexOutputDir = getApplicationInfo().dataDir; // 目标路径
		String libPath = activityInfo.applicationInfo.nativeLibraryDir; // 库路径
		
		DexClassLoader classLoader = new DexClassLoader(dexPath, dexOutputDir, libPath, this.getClass().getClassLoader());
		try {
			Class<?> clazz = classLoader.loadClass(packageName + ".GreetPlugin");
			
			// 方式一
			 Object instance = clazz.newInstance();
			 Class[] params = new Class[1];
			 params[0] = String.class;
			 Method action = clazz.getMethod("greet", params);
			 String result = (String) action.invoke(instance, "linzhiyong");

			Resources resources = pm.getResourcesForApplication(packageName);
			int id = resources.getIdentifier("version", "string", packageName);
			Toast.makeText(this, "返回结果:" + result + "\n" + resources.getString(id), Toast.LENGTH_LONG).show();
		} catch (Exception e) {
			Toast.makeText(this, "" + e.getMessage(), Toast.LENGTH_LONG).show();
		}
	}

  • dexPath,指目标类所在的APK或者Jar文件的路径,类装载器将从该路径中寻找指定的目标类。必须是全路径。
  • dexOutputDir,解压出的目标dex文件存放的路径,一般使用本程序的数据路径。
  • libPath,指目标类中所使用的C/C++库所存放的路径。
  • classLoader,指该装载器的父类装载器,一般为当前执行类的装载器。
    上述实例中,虽然使用loadClass返回了一个Class对象,虽然Class对象是一个GreetPlugin对象,但是调用者PluginHost中却没有定义 GreetPlugin,因此不能直接调用 GreetPlugin对象的方法,只能通过反射机制去调用。

    反射机制调用时主要使用了Method类,CLass对象的getMethod()方法可以返回该类的任何方法,比如本示例中的greet()方法。getMethod()方法有两个参数,第一个是需要访问的函数名称,第二个是函数参数类型。

    得到目标类的函数对象的Method后,就可以调用带Method的invoke()方法去执行目标函数了,invoke()方法的第一个参数是指执行目标函数的对象,该对象就是真正的GreetPlugin对象,就是Class类调用newInstance()创建的。


总结:通过类装载器装载的类,调用其内部函数还是有些繁琐,所以可以通过定义一个interface接口,定义一些函数。该interface同时存在于GreetPlugin项目中,也存在于PluginHost中。在PluginHost去实现该接口,然后在上面示例中使用loadClass直接返回到interface对象。

修改代码如下:

1、在PluginHost中定义interface接口,并且把该类Export为greet.jar包。

package com.lzy.plugin.service;

/**
 * Plugin接口
 * 
 * @author lzy
 * @time 2016-09-22 10:36:30
 * @email wflinzhiyong@163.com
 */
public interface GreetService {

	String greet(String greet);
	
}

2、在GreetPlugin项目中通过Add Library的方式引用greet.jar包(不能使用外部Jar方式原因是:外部Jar会作为程序一部分被打包到apk中,从而使得GreetPlugin和PluginHost项目中存在报名相同但验证码不同的类文件,会在运行时报错“Class ref in pre-verifired class resolved to unexpected implementation”),并且实现interface接口:

package com.lzy.plugin.greet;

import com.lzy.plugin.service.GreetService;

/**
 * Plugin接口实现
 * 
 * @author lzy
 * @time 2016-09-22 10:36:30
 * @email wflinzhiyong@163.com
 */
public class GreetPlugin implements GreetService{

	@Override
	public String greet(String greet) {
		return "GreetPlugin: " + greet;
	}

}

3、在 PluginHost修改:

	public void useDexClassLoader() {
		Intent intent = new Intent("com.lzy.plugin.greet");
		PackageManager pm = getPackageManager();
		final List<ResolveInfo> plugins = pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
		if (plugins == null || plugins.size() == 0) {
			Toast.makeText(this, "未找到匹配的Plugin!", Toast.LENGTH_LONG).show(); return;
		}
		ResolveInfo info = plugins.get(0);
		ActivityInfo activityInfo = info.activityInfo;
		
		String div = System.getProperty("path.separator"); // 特定分隔符
		String packageName = activityInfo.packageName; // plugin包名
		String dexPath = activityInfo.applicationInfo.sourceDir; // plugin apk或jar路径
		String dexOutputDir = getApplicationInfo().dataDir; // 目标路径
		String libPath = activityInfo.applicationInfo.nativeLibraryDir; // 库路径
		
		DexClassLoader classLoader = new DexClassLoader(dexPath, dexOutputDir, libPath, this.getClass().getClassLoader());
		try {
			Class<?> clazz = classLoader.loadClass(packageName + ".GreetPlugin");
			
			// 方式一
			// Object instance = clazz.newInstance();
			// Class[] params = new Class[1];
			// params[0] = String.class;
			// Method action = clazz.getMethod("greet", params);
			// String result = (String) action.invoke(instance, "linzhiyong");
			
			// 方式二
			GreetService service = (GreetService) clazz.newInstance();
			String result = service.greet("linzhiyong");

			Resources resources = pm.getResourcesForApplication(packageName);
			int id = resources.getIdentifier("version", "string", packageName);
			Toast.makeText(this, "返回结果:" + result + "\n" + resources.getString(id), Toast.LENGTH_LONG).show();
		} catch (Exception e) {
			Toast.makeText(this, "" + e.getMessage(), Toast.LENGTH_LONG).show();
		}
	}

下载demo

转载注明出处:http://blog.csdn.net/u012527802/article/details/52620316

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值