The wave

Yesterday boao liver hair version, the next day just arrived at the company, buttocks did not sit hot, colleagues called me in the past, said yesterday the version of the cable collapse. I look at the crash information, isn’t that what I wrote? So quickly back to the station, first check the crash information corresponding to the content:

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$notifyRecyclerDataChangedThe $1.invoke (QuickPullLoadManager.java:412)
  at xxx.base.quickpullload.QuickPullLoadManager$notifyRecyclerDataChangedThe $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) Copy the code

In line 38, we create a Drawable with a tap in the background. On Android5.0, we implement a water ripple effect.

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;  } } Copy the code

This is a simple and generic method that returns RippleDrawable after 5.0 and StateListDrawable after 5.0. So why did it crash?

To solve the

First look at the VerifyError class, the official explanation is:

Thrown when the “verifier” detects that a class file, though well formed, contains some sort of internal inconsistency or security problem.

When the machine turns over, it reads:

Thrown when the Validator detects that the class file (although well-formed) contains some internal inconsistencies or security issues.

Is there some inconsistencies or security issues with my code methods? If so, is it because Kotlin called a Java method?

To verify this, I first tried calling the Java method from a Java class in an emulator on Android4.4. The result still crashed. Then convert the Java method to Kotlin method, WTF, and it doesn’t crash? !!!!! The Kotlin code looks like this:

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  )  )  } } Copy the code

But Kotlin doesn’t look any different from Java. !!!!! To see the difference further, I decomcompiled Kotlin into a Java check

public final class ViewUtils {
   public static final ViewUtils INSTANCE;

   @NotNull
Public final Drawable createAdaptiveRippleDrawable (@ ColorInt int color, @ FloatRange (from = 0.0 D, to = 1.0 D)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;  } } Copy the code

That makes the difference between the two is obvious: the Kotlin method will, in the method createAdaptiveRippleDrawable createRipple return values RippleDrawable will be equivalent to Drawable, then return;

And methods in Java class createAdaptiveRippleDrawable returns RippleDrawable (no RippleDrawable this class under the Android 5.0), so that led to the collapse

Now that THE crash was resolved using Kotlin’s method, I reported the cause, let the test go through the main process, and redistributed.

unsettles

The number of crashes with Kotlin has decreased significantly since the relaunch, but there are still a few crashes. The analysis is probably due to different phones, different verification file consistency and different security mechanisms. So some 4.x phones crash and some 4.X phones work.

But this is just a guess, hope there is a big man can answer.

Then I looked it up on StackOverflow and here’s stackOverflow’s answerAgain, you must change the return value of createRipple to Drawable

The perfect solution

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  )  )  } } Copy the code

When you change the return value of createRipple to Drawbale, it doesn’t crash anymore

conclusion

  1. When using classes added to a higher version of Android as a return value, you should return classes that exist in lower versions of Android or have their common parent.

    For example, this article throws VerifyError for the following specific reasons:
    • The RippleDrawable class is not available on Android5.0
    • The createRipple method will not be called under Android 5.0, but the RippleDrawable class will not be recognized under Android 5.0, and VerifyError will be raised
  2. Writing requirements requires careful self-testing to eliminate all possible scenarios
  3. There needs to be a more secure solution to fix the bug before it can go live

This article is formatted using MDNICE