在android中,类的加载主要是通过PathClassLoader 和DexClassLoader来实现的,主要有一下特点:
1、他们都支持双亲委派机制,都没有重写loadClass方法,通过findClass来加载类
2、加载类之前都会检查自身以及父类是否加载过该类,如果没有就加载返回,加载了就不需要重复加载了。
3、本质都是通过DexFile来实现类加载的,也就是说dalvik只能识别dex文件,所以android中类加载的文件只能是dex文件,或者是包含dex的.apk和.jar文件。
4、PathClassLoader和DexClassLoader的区别在于,前者只能直接加载dex文件或者已经安装的apk文件,而后者可以加载dex文件,.zip,.apk和.jar文件。DexClassLoader 是通过loadDex(path,outPath,0)来得到DexFile对象,outPath路径是用来释放.apk和.jar文件中的dex文件,然后存储到outPath下。
自定义ClassLoader
首先创建插件工程PluginDemo,里面创建一个测试类DynamicTest。其代码为:
package com.plugin.app;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
public class DynamicTest {
public void showPluginWindow(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("我是插件的对话框");
builder.setTitle(R.string.app_name);
builder.setNegativeButton("取消", new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
Dialog dialog = builder.create();
dialog.show();
}
}
该类的主要功能就是显示出一个对话框。
第二步,创建我们的主工程DynamicHostDemo,在该工程中,我们动态加载PluginDemo的apk文件,然后点击按钮弹出对话框。
1、将PluginDemo的apk文件PluginDemo.apk放到主工程DynamicHostDemo的Asset目录下。
2、主工程DynamicHostDemo的MainActivity代码如下,主要实现按钮的点击事件:
package com.dynamic.app;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import java.io.File;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity";
//apk文件存放的路径
private String dexFilePath;
//解压出dex存储路径
private File dexFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e(TAG, "当前的classloader:" + getClassLoader());
Log.e(TAG, "系统的classloader:" + ClassLoader.getSystemClassLoader());
//设置dexFilePath
dexFilePath = getExternalFilesDir(null).getPath() + File.separator + "plugin.apk";
System.out.println("dexFilePath:" + dexFilePath);
dexFile = getDir("dex", 0);
boolean success = Utils.prepareDex(this, "PluginDemo.apk", new File(dexFilePath));
if (!success)
return;
//创建DexClassLoader
final DexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, dexFile.getAbsolutePath(), null, getClassLoader());
findViewById(R.id.open_plug_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Class mClass = dexClassLoader.loadClass("com.plugin.app.DynamicTest");
Log.e(TAG,"mClass的默认classLoader:"+mClass.getClassLoader());
Object dynamicTest = mClass.newInstance();
Method mMethod = mClass.getDeclaredMethod("showPluginWindow", new Class<?>[]{Context.class});
mMethod.setAccessible(true);
mMethod.invoke(dynamicTest, MainActivity.this);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
其中Utils.prepareDex()方法主要是将Asset目录下的apk文件写入到sdcard下,代码为:
package com.dynamic.app;
import android.content.Context;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Utils {
private static final int BUF_SIZE = 2048;
/**
* 将插件资源从assets写入到sdcard上
* @param context 上下文
* @param assetFile asset目录下的文件名称
* @param sdCardPath 要写入到sdcard的文件
*/
public static boolean prepareDex(Context context, String assetFile, File sdCardPath) {
BufferedInputStream bis = null;
OutputStream dexWriter = null;
try {
bis = new BufferedInputStream(context.getAssets().open(assetFile));
dexWriter = new BufferedOutputStream(new FileOutputStream(sdCardPath));
byte[] buf = new byte[BUF_SIZE];
int len;
while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
dexWriter.write(buf, 0, len);
}
dexWriter.close();
bis.close();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
通过以上代码,可以看到android中动态加载机制的核心思想就是通过创建DexClassLoader来动态加载apk文件,然后利用反射机制来获取apk中的类,进而来调用其中的方法。