前言:
上篇说了下java的动态加载机制,最终还是为android的动态加载准备的。android中的动态加载有所不同,android中Dalvik虚拟机所支持的是.dex文件,我们项目中中的代码就是在dex文件中。而ClassLoader运用的也是"双亲代理模式",Android中运用的classloader有2中,分别是--DexClassLoader以及PathClassLoader。
两者的区别:
DexClassLoader:用于加载jar/apk/dex,可以加载SD卡中的apk;
PathClassLoader:用于加载被安装过的apk。
不过项目中基本使用的都是DexClassLoader。来看下DexClassLoader的构造函数。
new DexClassLoader(dexPath, optimizedDirectory, libraryPath, parent)
第一个参数:dex/apk/jar压缩文件的路径;
第二个参数:dex解压后存放的路径;
第三个参数:C/C++依赖的本地库文件,可以为null;
第四个参数:上一级的classloader。
DexClassLoader有2中实现方式,一种是通过接口来实现,另一种是通过反射机制来实现。
其他的不多说,先来个实现结果展示,通过DexClassLoader来动态加载我们的class的实现。
分别点击两个按钮,一种是通过反射,另一种是通过接口来实现。
以下是我的工程目录:
所要动态加载的类是我们的ToastImpl.java,而ToastInterface则是ToastImpl实现的接口:
ToastInterface.java:
public interface ToastInterface {
public String toastString();
}
ToastInterface.java:
public class ToastImpl implements ToastInterface {
@Override
public String toastString() {
// TODO Auto-generated method stub
return "classLoader successed";
}
}
然后把我们的ToastImpl.java导出生成.jar文件,再把我们的.jar文件用dx.bat转成我们的Dalvik所识别的.dex文件
准备工作已经做好,接下来就是我们的MainActivity的实现了:
public class MainActivity extends Activity {
private Button flect, inter;
// 通过dx.bat生成的dex文件存放的目录,这里我直接放到了工程目录的cache下
private final static String DEX_PATH = "/data/data/com.example.classloaderforandroid02/cache/target.dex";
// dex解压之后存放的路径,如果是一个固定的路径运行程序的时候会报错:optimizedDirectory not readable/writable
private File dexOutputDir;
//用于动态加载类的DexClassLoader
private DexClassLoader classLoader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Context context = getApplicationContext();
dexOutputDir = context.getDir("dex", 0);
/**
* 第一个参数:dex压缩文件的路径
* 第二个参数: dex解压之后存放的路径
* 第三个参数: C/C++依赖的本地库文件目录,可以为null
* 第四个参数:上一级的classloader
*/
classLoader = new DexClassLoader(DEX_PATH,
dexOutputDir.getAbsolutePath(), null, getClassLoader());
flect = (Button) findViewById(R.id.flect);
inter = (Button) findViewById(R.id.inter);
//反射方式实现
flect.setOnClickListener(reflectListener);
//接口方式实现
inter.setOnClickListener(interfaceListener);
}
/** 通过反射来调用加载到的类*/
private View.OnClickListener reflectListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
try {
Class<?> clazz = classLoader.loadClass("com.ljx.classloader.ToastImpl");
Method method = clazz.getDeclaredMethod("toastString");
method.setAccessible(true);
String result = (String) method.invoke(clazz.newInstance());
Toast.makeText(getApplicationContext(), result, 0).show();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
/** 通过接口的方式来调用加载到的类*/
private View.OnClickListener interfaceListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
try {
Class<?> clazz = classLoader.loadClass("com.ljx.classloader.ToastImpl");
ToastInterface toastIn = (ToastInterface) clazz.newInstance();
String result = toastIn.toastString();
Toast.makeText(getApplicationContext(), result, 0).show();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}
代码中都有注释,就不详细解释了,不过值得注意的是android中的ClassLoader是通过loadClass来加载类的,内部实现和java中的有所不同,JVM中的classloader是通过defineClass来加载类的,而Dalvik则把这个方法注释掉了。
不过android中动态加载一个类和java中动态加载一个类还有一个更麻烦的地方,比如无法使用被动态记载apk中的resource,以及在创建Activity的时候没有注册到AndroidManifest.xml中,所以在PackageManagerService扫描我们项目apk的时候没有对我们动态加载的Activity进行初始化,导致动态加载Activity无法运行。当然也有解决方法,那就是用一个代理Activity来执行我们动态加载的Activity,或者也可以通过动态创建Activity来执行我们动态加载的Activity,这里仅仅只是提供一个入门级别的动态加载,如果有兴趣的话,可以更深入的了解如何动态创建一个Activity或者创建代理Activity供我们使用。