使用androidx.appcompat库的APP,在小米TV上闪退,原因是res/drawable目录下使用了矢量动画资源<animated-vector>,当View获得焦点会开始矢量动画。View去获取getDrawable() 转型为 AnimatedVectorDrawable时报错。
AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getDrawable()
自Android 5.0(API 21)开始,Vectordrawable(矢量图像)正式得到了支持,可以通过VectorDrawable和AnimatedVectorDrawable来实现矢量图像和动画。这个APP最低支持Android 5.0的,而小米TV是Android 6.0, 应该原生就支持android.graphics.drawable.AnimatedVectorDrawable, 所以代码里使用的就是AnimatedVectorDrawable类,没有用AnimatedVectorDrawableCompat,但却发生了AnimatedVectorDrawableCompat不能转型为AnimatedVectorDrawable的错误,在Sony(Android 9)上就没问题,所以怀疑还是Android版本问题。
如果是使用AnimatedVectorDrawableCompat,有两种方式:
1、xml布局文件中ImageView通过android:srcCompat引用
2、代码里setImageResource()引用
APP中我们是通过方式2设置的<animated-vector>资源,所以从这里开始排查。
布局文件中的ImageView在inflate的时候会被实例化为AppCompatImageView
public AppCompatImageView(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
ThemeUtils.checkAppCompatTheme(this, getContext());
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
mImageHelper = new AppCompatImageHelper(this);
mImageHelper.loadFromAttributes(attrs, defStyleAttr);
}
@Override
public void setImageResource(@DrawableRes int resId) {
if (mImageHelper != null) {
// Intercept this call and instead retrieve the Drawable via the image helper
mImageHelper.setImageResource(resId);
}
}
AppCompatImageView.setImageResource(resId)调用了mImageHelper.setImageResource(resId)
//androidx.appcompat.widget.AppCompatImageHelper
public void setImageResource(int resId) {
if (resId != 0) {
final Drawable d = AppCompatResources.getDrawable(mView.getContext(), resId);
if (d != null) {
DrawableUtils.fixDrawable(d);
}
mView.setImageDrawable(d);
} else {
mView.setImageDrawable(null);
}
applySupportImageTint();
}
继续看final Drawable d = AppCompatResources.getDrawable(mView.getContext(), resId);
//androidx.appcompat.content.res.AppCompatResources
public static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
return ResourceManagerInternal.get().getDrawable(context, resId);
}
//androidx.appcompat.widget.ResourceManagerInternal
public static synchronized ResourceManagerInternal get() {
if (INSTANCE == null) {
INSTANCE = new ResourceManagerInternal();
installDefaultInflateDelegates(INSTANCE);
}
return INSTANCE;
}
第一次创建ResourceManagerInternal时候会添加加载对应xml资源文件代理对象,这里就是关键,
private static void installDefaultInflateDelegates(@NonNull ResourceManagerInternal manager) {
// This sdk version check will affect src:appCompat code path.
// Although VectorDrawable exists in Android framework from Lollipop, AppCompat will use
// (Animated)VectorDrawableCompat before Nougat to utilize bug fixes & feature backports.
if (Build.VERSION.SDK_INT < 24) {
manager.addDelegate("vector", new VdcInflateDelegate());
manager.addDelegate("animated-vector", new AvdcInflateDelegate());
manager.addDelegate("animated-selector", new AsldcInflateDelegate());
manager.addDelegate("drawable", new DrawableDelegate());
}
}
根据上面的源码,可以知道,Android API < 24 (Android O)都会使用VectorDrawableCompat以及AnimatedVectorDrawableCompat等,也就是ImageView.setImageResource(R.drawable.xxxx)后,资源文件解析后生成的实际上是AnimatedVectorDrawableCompat对象,所以才会出现在小米电视上
(AnimatedVectorDrawable) getDrawable() 返回 AnimatedVectorDrawableCompat, 强制转型为AnimatedVectorDrawable的错误。
在Android API >= 21 (Android P),不管是使用(AnimatedVectorDrawableCompat) getDrawable()或是(AnimatedVectorDrawable) getDrawable() 都存在问题, 因为AppCompat库本身就可能会返回这两种类型的对象,我们只能做兼容处理。
比如
Drawable drawable = getDrawable();
if (drawable instanceof AnimatedVectorDrawableCompat) {
………
} else if (drawable instanceof AnimatedVectorDrawable) {
……..
}
又或者直接转为Animatable启动动画
Animatable animatable = (Animatable) getDrawable()
animatable.start()