做android项目打包apk时,为了尽量减少apk的大小,加入了替换资源路径功能,类似于微信混淆压缩,把资源路径和资源的名称用较短的字符串替换,这样做的好处是apk确实变小了,坏处就是一些通过资源的名字,用反射的方式来获取资源失败了,因为名字变了,所以这时候就需要添加个资源白名单的功能,里面可以填写资源的名字。我再网上找了一些通过名称反射的方式来获取 drawable 资源的方法,先看看方法一,比较繁琐
private LoadedResource mLoadedResource;
public Drawable getDrawable(String packageName, String fieldName) {
Drawable drawable = null;
int resourceID = getResourceID(packageName, "drawable", fieldName);
LoadedResource installedResource = getInstalledResource(packageName);
if (installedResource != null) {
drawable = installedResource.resources.getDrawable(resourceID);
}
return drawable;
}
public int getResourceID(String packageName, String type, String fieldName) {
int resID = 0;
LoadedResource installedResource = getInstalledResource(packageName); // 获取已安装APK的资源
if (installedResource != null) {
String rClassName = packageName + ".R$" + type; // 根据匿名内部类的命名, 拼写出R文件的包名+类名
try {
Class cls = installedResource.classLoader.loadClass(rClassName); // 加载R文件
resID = (Integer) cls.getField(fieldName).get(null); // 反射获取R文件对应资源名的ID
} catch (Exception e) {
e.printStackTrace();
}
} else {
}
return resID;
}
public LoadedResource getInstalledResource(String packageName) {
LoadedResource resource = mLoadedResource; // 先从缓存中取, 没有就去加载
if (resource == null) {
try {
Context context = mContext.createPackageContext(packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
resource = new LoadedResource();
resource.packageName = packageName;
resource.resources = context.getResources();
resource.classLoader = context.getClassLoader();
mLoadedResource = resource; // 得到结果缓存起来
} catch (Exception e) {
e.printStackTrace();
}
}
return resource;
}
public static class LoadedResource {
public Resources resources;
public String packageName;
public ClassLoader classLoader;
}
说明,"com.test.cn" 是自己项目的包名,user_pic.png 是放在 drawable 文件里面的图片,调用下面的方法,给 ImageView 设置图片:
imageView.setImageDrawable(getDrawable("com.test.cn", "user_pic"));
方法二,比较简单粗暴,没那么多步骤:
private int getResidByReflect(String packageName, String imageName){
try {
Field field = Class.forName(packageName + ".R$drawable").getField(imageName);
int resid = field.getInt(field);
return resid;
} catch (Exception e) {
return -1;
}
}
imageView.setBackgroundResource(getResidByReflect("com.test.cn","user_pic"));
方法一获取的是 Drawable 类型,方法二获取的是资源id,传入的参数是一样的。
还有中获取图片的方式,借用阿里百川sdk中的代码,示例如下
public class ResourceUtils {
public ResourceUtils() {
}
public static String getString(Context var0, String var1) {
return var0.getResources().getString(getIdentifier(var0, "string", var1));
}
public static int getRLayout(Context var0, String var1) {
return getIdentifier(var0, "layout", var1);
}
public static int getRDrawable(Context var0, String var1) {
return getIdentifier(var0, "drawable", var1);
}
public static int getRId(Context var0, String var1) {
return getIdentifier(var0, "id", var1);
}
public static float getDimen(Context var0, String var1) {
return getIdentifier(var0, "dimen", var1);
}
public static int getIdentifier(Context var0, String var1, String var2) {
return var0.getResources().getIdentifier(var2, var1, var0.getPackageName());
}
}
为什么介绍这个呢?是因为以前项目中接入了阿里百川sdk,sdk中包含有淘宝登录等页面,它里面用到的都是反射,这样就造成了一些有趣的现象。项目继承sdk后,debug模式下使用的好好的,release模式也没问题;一旦切换到混淆路径release模式,刚跳转到淘宝页面就崩溃了,原因是资源图片找不到,原因就是资源路径被混淆,所以找不到了。阿里百川官方文档也没资源免混淆的说明,只能动手去翻sdk里面的代码,然后去添加相应的资源白名单,废了好一番力气。