转载

记一次血淋淋的VerifyError崩溃

风波

昨天熬肝发版,第二天刚到公司,屁股没坐热,同事就把我叫过去,说昨天发的版本有线上崩溃。我一看崩溃信息,这不就是我写的需求嘛?于是乎赶紧回到工位,先查看崩溃信息对应的内容:

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是这么回答的 记一次血淋淋的VerifyError崩溃 还是一定要将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 之后,就再也没有崩溃了

总结

  1. 在使用高版本才有的类的时候,应该返回低版本也有的父类。

  2. 写需求需要认真自测,尽可能排除所有情况

  3. 在修改bug时需要有个更加稳妥的解决办法,才能上线

本文使用 mdnice 排版

原文  https://juejin.im/post/5f100c435188252e644d0024
正文到此结束
Loading...