解决SDK中getIdentifier的痛点
2020-08-08 本文已影响0人
Ray206
SDK做出来后,需要给其他公司使用。SDK中包含资源和jar包,其它公司使用SDK的时候会重新编译资源并生成ID(aar)除外。遇到第三方打包时(quick、快接)等都会生成新的资源ID。如果使用R.xx.xx的方式会报找不到资源的问题。
- 解决方式.
Resources.getIdentifier(String name, String defType, String defPackage)
这么做确实可以解决资源找不到的问题,但是对于编码来说太生硬。
- getIdentifier的痛点
- 删除其中一个layout,编译可以通过,使用时因为找不到资源而无法使用
- 使用名称去查找,无编译器支持,每次使用时查找资源名称都要硬敲
- getIdentifier的原理就是通过反射去查找当前包名下面,名称对应的资源id,反射影响性能,如果有反复查找的需求,性能上就会有影响
- 我的解决方法
jar包的资源id(A)->资源名称(B)->新生成的资源ID(C)
不管怎么编译资源名称是不会改变的,我们导出jar包时,也导出对应的资源的R.java到jar包中。
如果使用eclipse开发可以直接吧gen目录选中和导出,如果使用android studio开发,library是不生成R文件的。所以需要
//apply plugin: 'com.android.library'
改成
apply plugin: 'com.android.application'
这样配置后就可以生成对应的R文件了。
- 步骤
- 读取旧jar包中R文件中资源id名称,生成对应的索引
- 在外部调用R.xx.xx时通过对应的资源id找到名称,并查找到对面资源的名称
- 通过getIdentifier/或者反射的方式,找新包名下对应的新的资源id
- 把旧id和新的id做一个映射,然后保存
- 工具类对R.xx.xx做包装,当调用R.xx.xx时返回新包名下的新资源id
整个流程就是这样了,相当于我们自己生成了一个资源id,做绑定,只不过现在用了编译生成的id。我们对新生成的资源id用SparseArray做了缓存,提高查找效率(只调用一次getIdentifier)。如果有反复查找提高命中的需求,可以使用LinkedHashMap做缓存容器(Lru算法)
附工具类:
/**
* Time: 2020/8/5 0005
* Author: zoulong
*/
public class Res {
//资源类型
private String[] resTypes = new String[]{"color", "dimen", "drawable", "id", "string", "style", "layout"};
//生成R时的包名
private final String SDK_PACKAGE_NAME = "com.xiyou.sdk.p";
//全局上下文
private static Application mApplication;
//缓存表
private static SparseArray<ResId> ids = new SparseArray<>();
private static Res mRes = null;
public Res(){};
public static Res get(){
if(mRes == null){
synchronized(Res.class){
if(mRes == null){
mRes = new Res();
}
}
}
return mRes;
}
public void init(){
mApplication = getApplicationByReflect();
for(String resType : resTypes){
try {
Class resTypeClass = Class.forName(SDK_PACKAGE_NAME + ".R$" + resType);
for(Field resField : resTypeClass.getFields()){
int oldResId = resField.getInt(null);
ids.put(oldResId, new ResId(resType, resField.getName()));
}
} catch (Exception e) {
}
}
}
public static int R(int resourcesId){
if(resourcesId == -1) return -1;
ResId resId = ids.get(resourcesId);
if(resId.newId != -1) return resId.newId;
String packageName = mApplication.getPackageName();
int newId = mApplication.getResources().getIdentifier(resId.name, resId.Type, packageName);
resId.newId = newId;
return newId;
}
private Application getApplicationByReflect() {
try {
@SuppressLint("PrivateApi")
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Object thread = activityThread.getMethod("currentActivityThread").invoke(null);
Object app = activityThread.getMethod("getApplication").invoke(thread);
if (app == null) {
throw new NullPointerException("you should init first");
}
return (Application) app;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
throw new NullPointerException("you should init first");
}
private static class ResId{
private String Type;
private String name;
private int newId = -1;
public ResId(String type, String name) {
Type = type;
this.name = name;
}
}
}
使用:
引入:import static com.xiyou.sdk.p.sdk.utlis.Res.R;
初始化:Res.get().init();
layout调用:R(R.layout.xy_sdk_main_activity)
id调用:R(R.id.test)
到这里SDK中查找资源使用R.xx.xx的方式就介绍完了,希望你可以喜欢!