导 语
Android插件化一个重要问题就是插件资源访问,这也是面试官主要问及一个主要方面。在京东面试问到这个问题时回答的模模糊糊导致与我的人生转折点擦肩而过。哀哉!痛哉!
接下来我们就好好的深入研究一下这个话题吧!
问题列举
1.插件中资源是如何被加载的?
2.如何处理插件资源与宿主资源冲突?
原理分析
-
资源链分析
这里我们将一个重要的概念 "上下文环境",也就是我们最最最常用的 Context 这是一个神奇的变量无论我们启动Activity、启Service也好都离不开Context , 包括我们要讲的加载资源同样也无法逃脱的它的魔爪.
资源链递进分析
context只是一个抽象类,其真正的实现类是ContextImpl
ContextImpl中有一个Resources成员变量mResources,可通过getResources()获取.
Resources内部的重要成员AssetManager,它可以指定APK的资源路径,最终通过AssetManager获取资源.
插件中的资源是如何被加载的呢?
1.封装反射管理类 RefInvoker.java
@SuppressWarnings("unchecked")
public class RefInvoker {
private static HashMap<String, Class> clazzCache = new HashMap<String, Class>();
public static Class forName(String clazzName) throws ClassNotFoundException {
Class clazz = clazzCache.get(clazzName);
if (clazz == null) {
clazz = Class.forName(clazzName);
ClassLoader cl = clazz.getClassLoader();
if (cl == system || cl == application || cl == bootloader) {
clazzCache.put(clazzName, clazz);
}
}
return clazz;
}
public static Object invokeMethod(Object target, String className, String methodName, Class[] paramTypes,
Object[] paramValues) {
try {
Class clazz = forName(className);
return invokeMethod(target, clazz, methodName, paramTypes, paramValues);
}catch (ClassNotFoundException e) {
LogUtil.printException("ClassNotFoundException", e);
}
return null;
}
public static Object invokeMethod(Object target, Class clazz, String methodName, Class[] paramTypes,
Object[] paramValues) {
try {
Method method = clazz.getDeclaredMethod(methodName, paramTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method.invoke(target, paramValues);
} catch (SecurityException e) {
LogUtil.printException("SecurityException", e);
} catch (IllegalArgumentException e) {
LogUtil.printException("IllegalArgumentException", e);
} catch (IllegalAccessException e) {
LogUtil.printException("IllegalAccessException", e);
} catch (NoSuchMethodException e) {
//这个日志...
LogUtil.e("NoSuchMethodException", methodName);
} catch (InvocationTargetException e) {
LogUtil.printException("InvocationTargetException", e);
}
return null;
}
}
2.我们需要对AssetManager进行Hook,创建出一个代理类HookAssetManager.java
public class HookAssetManager {
private static final String ClassName = "android.content.res.AssetManager";
private static final String Method_addAssetPath = "addAssetPath";
private static final String Method_ensureStringBlocks = "ensureStringBlocks";
private Object instance;
public HookAssetManager(Object instance) {
this.instance = instance;
}
/**
* 加载apk对应的资源
* @param path Apk路径 此处为插件APK的路径
*/
public void addAssetPath(String path) {
RefInvoker.invokeMethod(instance, ClassName, Method_addAssetPath, new Class[]{String.class}, new Object[]{path});
}
/**
* 初始化其内部参数
* @return
*/
public Object[] ensureStringBlocks() {
return (Object[])RefInvoker.invokeMethod(instance,
ClassName, Method_ensureStringBlocks, null, null);
}
}
3.插件资源创建
AssetManager assetMgr = AssetManager.class.newInstance();
HookAssetManager hookAssetManager = new hookAssetManager(assetMgr);
hookAssetManager.addAssetPath(path);
hookAssetManager.ensureStringBlocks();
如何处理插件资源与宿主资源冲突?
我看了一些插件化框架发现它们的处理方式都是对资源ID的PP段进行修改. 即在aapt工具编辑前后修改PP段.
定制aapt在编译阶段进行修改PP段.
编译后期重新对插件APK资源ID进行重新组织编排.
两者之间的区别在于前者的侵入编译流程,维护起来很不方便 ; 后者只是对资源ID和对应的索引表resorce.arsc进行了修改,不入侵编译流程. 两者之间高下立判,我个人比较赞同第二种方式
网友评论