昨天熬肝发版,第二天刚到公司,屁股没坐热,同事就把我叫过去,说昨天发的版本有线上崩溃。我一看崩溃信息,这不就是我写的需求嘛?于是乎赶紧回到工位,先查看崩溃信息对应的内容:
java.lang.VerifyError:
at xxx.module.goods.detail.v5.extensions.GoodsStoreDecoratorKt.decoratorStore (GoodsStoreDecoratorKt.java:38)
at xxx.module.goods.detail.v5.GoodsDetailV5Model.convertRecyclerItemData (GoodsDetailV5Model.java:269)
at xxx.base.quickpullload.QuickPullLoadManager$notifyRecyclerDataChanged$1.invoke (QuickPullLoadManager.java:412)
at xxx.base.quickpullload.QuickPullLoadManager$notifyRecyclerDataChanged$1.invoke (QuickPullLoadManager.java:38)
at xxx.base.adapter.BaseAdapter.onBindViewHolder (BaseAdapter.java:30)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder (RecyclerView.java:7065)
xxxx
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:600)
at dalvik.system.NativeStart.main (NativeStart.java)
复制代码
第38行对应的代码就是写了一个创建带点击效果的背景Drawable,并且在Android5.0上实现的是水波纹效果,代码如下:
public class ViewUtils {
private ViewUtils() {
}
public static Drawable createAdaptiveRippleDrawable(
@ColorInt int color,
@FloatRange(from = 0f, to = 1f) float alpha,
@Px float radius,
@Nullable Drawable content
) {
int pressedColor = ColorUtils.INSTANCE.modifyAlpha(color, alpha);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return createRipple(pressedColor, radius, content);
} else {
return crateStateListDrawable(radius, content, pressedColor);
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static RippleDrawable createRipple(int rippleColor,
float radius,
@Nullable Drawable content) {
return new RippleDrawable(
ColorStateList.valueOf(rippleColor),
content,
getRippleMaskDrawable(Color.WHITE, radius)
);
}
private static StateListDrawable crateStateListDrawable(
float radius,
Drawable content,
int pressedColor
) {
StateListDrawable states = new StateListDrawable();
states.addState(new int[]{android.R.attr.state_pressed},
getRippleMaskDrawable(pressedColor, radius));
states.addState(new int[]{android.R.attr.state_focused},
getRippleMaskDrawable(pressedColor, radius));
states.addState(new int[]{android.R.attr.state_activated},
getRippleMaskDrawable(pressedColor, radius));
states.addState(new int[]{},
content);
return states;
}
private static Drawable getRippleMaskDrawable(@ColorInt int color, float radius) {
float[] outerRadii = new float[8];
Arrays.fill(outerRadii, radius);
RoundRectShape r = new RoundRectShape(outerRadii, null, null);
ShapeDrawable shapeDrawable = new ShapeDrawable(r);
shapeDrawable.getPaint().setColor(color);
return shapeDrawable;
}
}
复制代码
这是很简单且通用的方法,在5.0及以上返回RippleDrawable,在5.0以下返回StateListDrawable。所以为什么会导致崩溃呢?
首先查了一下VerifyError这个类,官方是这么解释的:
Thrown when the "verifier" detects that a class file, though well formed, contains some sort of internal inconsistency or security problem.
机翻过来就是:
当“验证者”检测到类文件(尽管格式正确)包含某种内部不一致或安全性问题时抛出。
难道我写的代码方法有什么不一致性或安全性问题吗?如果是的话,是否因为Kotlin调用Java方法导致的呢?
为了验证,我首先在Android4.4的模拟器下,试了一下在Java类中调用那个Java方法,结果依然崩溃;然后再将Java方法转成了Kotlin方法,WTF,居然就没有崩溃了??!! Kotlin代码如下:
object ViewUtils {
fun createAdaptiveRippleDrawable(
@ColorInt color: Int,
@FloatRange(from = 0.0, to = 1.0) alpha: Float,
radius: Float,
content: Drawable?
): Drawable {
val pressedColor = modifyAlpha(color, alpha)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
createRipple(pressedColor, radius, content)
} else {
crateStateListDrawable(
radius,
content,
pressedColor
)
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
fun createRipple(
rippleColor: Int,
radius: Float,
content: Drawable?
): RippleDrawable {
return RippleDrawable(
ColorStateList.valueOf(rippleColor),
content,
getRippleMaskDrawable(
Color.WHITE,
radius
)
)
}
}
复制代码
然而Kotlin看着跟Java也没什么区别啊??!!为了进一步看这两者的区别,我又把Kotlin再decompiled成Java检查
public final class ViewUtils {
public static final ViewUtils INSTANCE;
@NotNull
public final Drawable createAdaptiveRippleDrawable(@ColorInt int color, @FloatRange(from = 0.0D,to = 1.0D) float alpha, float radius, @Nullable Drawable content) {
int pressedColor = ColorUtils.INSTANCE.modifyAlpha(color, alpha);
return VERSION.SDK_INT >= 21 ? (Drawable)this.createRipple(pressedColor, radius, content) : (Drawable)this.crateStateListDrawable(radius, content, pressedColor);
}
@RequiresApi(
api = 21
)
@NotNull
public final RippleDrawable createRipple(int rippleColor, float radius, @Nullable Drawable content) {
return new RippleDrawable(ColorStateList.valueOf(rippleColor), content, this.getRippleMaskDrawable(-1, radius));
}
private ViewUtils() {
}
static {
ViewUtils var0 = new ViewUtils();
INSTANCE = var0;
}
}
复制代码
这样两者的区别就很明显了:Kotlin方法会在方法createAdaptiveRippleDrawable中,createRipple的返回值RippleDrawable会先强转成Drawable,然后再返回;
而Java类中方法createAdaptiveRippleDrawable返回的是RippleDrawable(在Android 5.0以下没有RippleDrawable这个类),所以导致了崩溃
既然用Kotlin方法解决了崩溃,我便将原因上报,让测试走一遍主流程,重新发布了。
重新上线后,使用Kotlin的写法崩溃量明显减少了,但还是有少许崩溃,分析可能就是因为不同的手机,验证文件一致性和安全性机制不同。所以在某些4.x手机崩溃,某些4.x手机正常。
不过这只是猜测,希望有大佬可以解答。
后来我又查了一下stackoverflow,stackoverflow是这么回答的 还是一定要将createRipple的返回值改为Drawable才行
object ViewUtils {
fun createAdaptiveRippleDrawable(
@ColorInt color: Int,
@FloatRange(from = 0.0, to = 1.0) alpha: Float,
radius: Float,
content: Drawable?
): Drawable {
val pressedColor = modifyAlpha(color, alpha)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
createRipple(pressedColor, radius, content)
} else {
crateStateListDrawable(
radius,
content,
pressedColor
)
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
fun createRipple(
rippleColor: Int,
radius: Float,
content: Drawable?
): Drawable {
return RippleDrawable(
ColorStateList.valueOf(rippleColor),
content,
getRippleMaskDrawable(
Color.WHITE,
radius
)
)
}
}
复制代码
将createRipple的返回值改为Drawbale 之后,就再也没有崩溃了
在使用高版本才有的类的时候,应该返回低版本也有的父类。
写需求需要认真自测,尽可能排除所有情况
在修改bug时需要有个更加稳妥的解决办法,才能上线
本文使用 mdnice 排版