Android 插件化学习 加载apk并调用类的函数

项目地址
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);

猜你喜欢

转载自blog.csdn.net/z979451341/article/details/79766135
今日推荐