問(wèn)題定義:
Android 使用自定義ClassLoader加載插件的時(shí)候,當(dāng)插件資源中使用自定義View后酸休,會(huì)出現(xiàn)異常
java.lang.ClassCastException: android.support.v4.widget.SwipeRefreshLayout cannot be cast to android.support.v4.widget.SwipeRefreshLayout
</br>
問(wèn)題分析:
同一個(gè)類(lèi)的強(qiáng)制轉(zhuǎn)換異常,肯定是由于不同ClassLoader加載出現(xiàn)的問(wèn)題,打印一下兩個(gè)ClassLoader柿究,發(fā)現(xiàn)由布局文件解析出來(lái)的SwipeRefreshLayout是由PathClassLoader來(lái)加載,而不是由我們自定的的ClassLoader加載黄选,應(yīng)該是解析XML的時(shí)候出現(xiàn)的問(wèn)題蝇摸,查看源碼中xml加載到View過(guò)程進(jìn)行分析婶肩。
從Activity的setContentView()方法進(jìn)入
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow的mWindow對(duì)象是 com.android.internal.policy.PhoneWindow,查看PhoneWindow的setContentView()方法
public void setContentView(int layoutResID) {
......
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
......
}
XML的解析是使用mLayoutInflater的inflate方法來(lái)創(chuàng)建View貌夕,一直往下找律歼,發(fā)現(xiàn)自定義的View是由createView()方法創(chuàng)建,查看該方法
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs); //補(bǔ)全 android.view. 啡专,最終還是指向createView
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
new HashMap<String, Constructor<? extends View>>();
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
try {
if (constructor == null) {
//使用ClassLoader進(jìn)行加載
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//添加到靜態(tài)緩存
sConstructorMap.put(name, constructor);
} else {
}
final View view = constructor.newInstance(args);
return view;
}
}
發(fā)現(xiàn)每個(gè)View在創(chuàng)建之前险毁,都會(huì)從靜態(tài)sConstructorMap中獲取以前創(chuàng)建添加的緩存。
那么如果插件中使用到了宿主里面已經(jīng)創(chuàng)建過(guò)的自定義View们童,那么進(jìn)行類(lèi)型轉(zhuǎn)化的時(shí)候就會(huì)出現(xiàn) cannot be cast to 的異常畔况。
</br>
解決方案:
這個(gè)問(wèn)題可以看成 在同一個(gè)進(jìn)程中,同一個(gè)自定義View對(duì)象慧库,是無(wú)法用不同ClassLoader加載的跷跪;
那么,不同的進(jìn)程對(duì)靜態(tài)變量是不共享的齐板,所以可以對(duì)插件的活動(dòng)指定不同的進(jìn)程域庇。通過(guò)對(duì)中間Activity和Service指定process來(lái)解決該問(wèn)題。
android:process=":PluginProcess"
</br>