项目地址
https://github.com/979451341/PlugStudy/tree/master/LoadSimpleClass
我们首先需要生成一个apk,并创建一个asset文件,至于生成apk的工程,就是我贴出的项目地址的parasitic文件,其实里面就是两个很简单的类,我这里就不演示了。
2.然后获取刚才保存在手机存储里的apk文件,并获取dex列表文件,在通过dex文件获取相应的DexClassLoader
4.获取了class后,通过反射调用函数。
https://github.com/979451341/PlugStudy/tree/master/LoadSimpleClass
知识点在于通过加载apk,复制成一个个FIle,然后通过使用DexClassLoade加载File(也可以说是Dex文件)获取DexClassLoade实例,后面在通过加载这个DexClassLoader实例获取class文件,再通过反射调用函数。
我们首先需要生成一个apk,并创建一个asset文件,至于生成apk的工程,就是我贴出的项目地址的parasitic文件,其实里面就是两个很简单的类,我这里就不演示了。
1.加载asset文件里的apk,并保存在手机存储里
AssetManager assetManager = context.getAssets();
long startTime = System.currentTimeMillis();
try {
File dex = context.getDir(APK_DIR, Context.MODE_PRIVATE);
if(dex.exists()){
return ;
}
dex.mkdir();
String []fileNames = assetManager.list("");
for(String fileName:fileNames){
if(!fileName.endsWith(FILE_FILTER)){
return;
}
InputStream in = null;
OutputStream out = null;
in = assetManager.open(fileName);
File f = new File(dex, fileName);
if (f.exists() && f.length() == in.available()) {
Log.i(TAG, fileName+"no change");
return;
}
Log.i(TAG, fileName+" chaneged");
out = new FileOutputStream(f);
byte[] buffer = new byte[2048];
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, fileName+" copy over");
}
Log.i(TAG,"###copyAssets time = "+(System.currentTimeMillis() - startTime));
} catch (Exception e) {
e.printStackTrace();
}
2.然后获取刚才保存在手机存储里的apk文件,并获取dex列表文件,在通过dex文件获取相应的DexClassLoader
File dexDir = context.getDir(AssetsManager.APK_DIR,
Context.MODE_PRIVATE);
File[] szFiles = dexDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return filename.endsWith(AssetsManager.FILE_FILTER);
}
});
for (File f : szFiles) {
Log.v("ClassLoaderManager","debug:load file:" + f.getName());
BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader(
f.getAbsolutePath(), dexDir.getAbsolutePath(), null,
context.getClassLoader());
bundleDexClassLoaderList.add(bundleDexClassLoader);
}
3.ClassLoader
我就简单的说一下,双亲委托模式,就是首先搜索判断当前的Class是否加载了,如果没有就看看父类是否可以加载这个Class,如果没有就只能通过当前类加载Class
这里因为我们是要通过自定义ClassLoader加载未安装的apk里的dex文件里的class,所以需要我们了解一下ClassLoader的运行模式。首先通过系统自带的ClassLoader加载一下Class,看是否能够成功,至于这个运行过程,上面说了,双亲委托模式。
因为是外部的apk,所以肯定不行,只是客气一下,还是需要靠自己通过dex文件获取的DexClassLoader才能加载Class。这个过程走的也是双亲委托模式。
public static Class<?> loadClass(Context context,String className) throws ClassNotFoundException {
try {
Class<?> clazz = context.getClassLoader().loadClass(className);
if (clazz != null) {
System.out.println("debug: class find in main classLoader");
return clazz;
}
} catch (Exception e) {
e.printStackTrace();
}
for (BundleDexClassLoader bundleDexClassLoader : bundleDexClassLoaderList) {
try {
Class<?> clazz = bundleDexClassLoader.loadClass(className);
if (clazz != null) {
System.out.println("debug: class find in bundle classLoader");
return clazz;
}
} catch (Exception e) {
e.printStackTrace();
}
}
throw new ClassCastException(className + " not found exception");
}
4.获取了class后,通过反射调用函数。
Constructor<?> constructor = clazz.getConstructor();
Object bundleUtils = constructor.newInstance();
Method printSumMethod = clazz.getMethod("printSum", Context.class,
int.class, int.class, String.class);
printSumMethod.setAccessible(true);
Integer sum = (Integer) printSumMethod.invoke(bundleUtils,
getApplicationContext(), 10, 20, "计算结果");
System.out.println("debug:sum = " + sum);