Android 插件版本切换崩溃(Resource not found)

时间:2023-02-19 19:57:36

综述:

主工程加载插件工程(把插件做成皮肤、特殊的View等)。当插件工程更新代码、布局时,Resource ID因新增的id导致编译后id数值发生改变。而当主程序加载 更新前的主题(A主题),再加载更新后的(B主题),就会导致View inflate为null。

有个前提条件是 升级后,自定义View的全限定包名(如com.test.TestLayout)不变。LayoutInflater inflate view时,如果发现是已经加载过的View,就直接用之前的ClassLoader来加载(之后的Log可以看出来)。

解决方法就是在LayoutInflater采用默认方式加载缓存View的时,抢先用我们自己的ClassLoader进行加载。通过LayoutInflater setFactory实现,详细资料点击打开链接

之前Host代码:

主工程加载插件:

 String layoutName = getStyleLayout();
if (!TextUtils.isEmpty(layoutName)) {
Resources resources = mContext.getResources();
String packageName = mContext.getPackageName();
int layoutId = resources.getIdentifier(layoutName, "layout", packageName);
if (layoutId > 0) {
try {

if (mLayoutInflater == null) {
mLayoutInflater = LayoutInflater.from(mContext);
}
mLayoutInflater = mLayoutInflater.cloneInContext(mContext);
Log.e("test", "host 5: " + mLayoutInflater.getContext().getClassLoader().toString());
return (mStyleView = mLayoutInflater.inflate(layoutId, parent, false));

插件自定义View(com.mytest.view.TestView):

        mDateWidget = (IBaseWidget) findViewById(R.id.widget_date);

Log.e("test", "plug 1: " + getClass().getClassLoader().toString());
mDateWidget.setClickable(false);

打Log可以看到,新主题切换旧主题,用的仍旧是旧主题的ClassLoader


解决方案:

主工程加载View代码:

    public View createStyleView(ViewGroup parent) {
if (parent == null || mContext == null) {
return null;
}

String layoutName = getStyleLayout();
if (!TextUtils.isEmpty(layoutName)) {
Resources resources = mContext.getResources();
String packageName = mContext.getPackageName();
int layoutId = resources.getIdentifier(layoutName, "layout", packageName);
if (layoutId > 0) {
try {
updateLayoutInflater();
return (mStyleView = mLayoutInflater.inflate(layoutId, parent, false));
} catch (InflateException e) {
e.printStackTrace();
}
}
}

return null;
}


private void updateLayoutInflater() {
if (mLayoutInflater == null) {
mLayoutInflater = LayoutInflater.from(mContext);
}
mLayoutInflater = mLayoutInflater.cloneInContext(mContext);
mLayoutInflater.setFactory(new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View customView = null;
if (mLayoutInflater != null && !TextUtils.isEmpty(name) && name.startsWith("com.mytest.view")) {
Context plugContext = mLayoutInflater.getContext();
if (null != plugContext) {
try {
Class<?> customViewClass = plugContext.getClassLoader().loadClass(name);
Object customViewObject = customViewClass.getDeclaredConstructor(Context.class, AttributeSet.class).newInstance(plugContext, attrs);
if (customViewObject instanceof View) {
customView = (View) customViewObject;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return customView;
}
});
}