做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裏面的代碼,然後去添加相應的資源白名單,廢了好一番力氣。