Android 非45度倍数角度渐变引起的崩溃

  • Post author:
  • Post category:其他




Android 非45度倍数角度渐变引起的崩溃



报错:java.lang.IllegalArgumentException: Linear gradient requires ‘angle’ attribute to be a multiple of 45

测试同学用华为 P20 鸿蒙系统测一个弹窗,发现一出弹窗就闪退,其它手机未发现问题,闪退日志如下:

--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: main
AndroidRuntime: Process: com.baidu.netdisk, PID: 29919
AndroidRuntime: java.lang.IllegalArgumentException: Linear gradient requires 'angle' attribute to be a multiple of 45
AndroidRuntime: 	at android.graphics.drawable.GradientDrawable$GradientState.updateGradientStateOrientation(GradientDrawable.java:2219)
AndroidRuntime: 	at android.graphics.drawable.GradientDrawable$GradientState.getOrientation(GradientDrawable.java:2207)
AndroidRuntime: 	at android.graphics.drawable.GradientDrawable.ensureValidRect(GradientDrawable.java:1284)
AndroidRuntime: 	at android.graphics.drawable.GradientDrawable.buildPathIfDirty(GradientDrawable.java:875)
AndroidRuntime: 	at android.graphics.drawable.GradientDrawable.getOutline(GradientDrawable.java:1875)
AndroidRuntime: 	at android.view.ViewOutlineProvider$1.getOutline(ViewOutlineProvider.java:40)
AndroidRuntime: 	at android.view.View.rebuildOutline(View.java:17449)
AndroidRuntime: 	at android.view.View.onAttachedToWindow(View.java:19561)
AndroidRuntime: 	at android.view.ViewGroup.onAttachedToWindow(ViewGroup.java:5283)
AndroidRuntime: 	at android.view.View.dispatchAttachedToWindow(View.java:20102)
AndroidRuntime: 	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3619)
AndroidRuntime: 	at android.view.ViewGroup.addViewInner(ViewGroup.java:5404)
AndroidRuntime: 	at android.view.ViewGroup.addView(ViewGroup.java:5190)
AndroidRuntime: 	at android.view.ViewGroup.addView(ViewGroup.java:5130)
AndroidRuntime: 	at androidx.recyclerview.widget.RecyclerView$5.addView(SourceFile:856)
AndroidRuntime: 	at androidx.recyclerview.widget.ChildHelper.addView(SourceFile:107)
AndroidRuntime: 	at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(SourceFile:8601)
AndroidRuntime: 	at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(SourceFile:8559)
AndroidRuntime: 	at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(SourceFile:8547)
AndroidRuntime: 	at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(SourceFile:1641)
AndroidRuntime: 	at androidx.recyclerview.widget.LinearLayoutManager.fill(SourceFile:1587)
AndroidRuntime: 	at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(SourceFile:665)
AndroidRuntime: 	at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(SourceFile:4134)
AndroidRuntime: 	at androidx.recyclerview.widget.RecyclerView.dispatchLayout(SourceFile:3851)
AndroidRuntime: 	at androidx.recyclerview.widget.RecyclerView.onLayout(SourceFile:4404)
AndroidRuntime: 	at android.view.View.layout(View.java:22491)
AndroidRuntime: 	at android.view.ViewGroup.layout(ViewGroup.java:6528)
AndroidRuntime: 	at androidx.constraintlayout.widget.ConstraintLayout.onLayout(SourceFile:1762)
AndroidRuntime: 	at android.view.View.layout(View.java:22491)
AndroidRuntime: 	at android.view.ViewGroup.layout(ViewGroup.java:6528)
AndroidRuntime: 	at android.widget.FrameLayout.layoutChildren(FrameLayout.java:334)
AndroidRuntime: 	at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
AndroidRuntime: 	at android.view.View.layout(View.java:22491)
AndroidRuntime: 	at android.view.ViewGroup.layout(ViewGroup.java:6528)
AndroidRuntime: 	at android.widget.FrameLayout.layoutChildren(FrameLayout.java:334)
AndroidRuntime: 	at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
AndroidRuntime: 	at android.view.View.layout(View.java:22491)
AndroidRuntime: 	at android.view.ViewGroup.layout(ViewGroup.java:6528)
AndroidRuntime: 	at android.widget.FrameLayout.layoutChildren(FrameLayout.java:334)
AndroidRuntime: 	at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
AndroidRuntime: 	at android.view.View.layout(View.java:22491)
AndroidRuntime: 	at android.view.ViewGroup.layout(ViewGroup.java:6528)
AndroidRuntime: 	at android.widget.FrameLayout.layoutChildren(FrameLayout.java:334)
AndroidRuntime: 	at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
AndroidRuntime: 	at com.android.internal.policy.DecorView.onLayout(DecorView.java:1144)
AndroidRuntime: 	at android.view.View.layout(View.java:22491)
AndroidRuntime: 	at android.view.ViewGroup.layout(ViewGroup.java:6528)
AndroidRuntime: 	at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3743)
AndroidRuntime: 	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3207)
AndroidRuntime: 	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2166)
AndroidRuntime: 	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8884)
AndroidRuntime: 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1280)
AndroidRuntime: 	at android.view.Choreographer.doCallbacks(Choreographer.java:1019)
AndroidRuntime: 	at android.view.Choreographer.doFrame(Choreographer.java:911)
AndroidRuntime: 	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1248)
AndroidRuntime: 	at android.os.Handler.handleCallback(Handler.java:900)
AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:103)
AndroidRuntime: 	at android.os.Looper.loop(Looper.java:219)
AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:8668)
AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109) 

上述堆栈中没有应用的具体报错代码,全是系统的报错日志,但是通过这个报错提示

java.lang.IllegalArgumentException: Linear gradient requires 'angle' attribute to be a multiple of 45

,可以知道大概原因,线性渐变的角度

angle

必须是45的倍数角度。也就是必须设置为0、45、90…这种。

然后排查可疑代码,全局搜索

android:angle

,找到如下代码

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:angle="-25"
        android:endColor="#FFF6E7"
        android:startColor="#FFFCF5" />
</shape> 

确实 angle 不是45度倍数



angle

,直接设置为-45度,左上角到右下角,为了避免部分API不支持负值,改为360+(-45)=315度

 <gradient
        android:angle="315"
        android:endColor="#FFF6E7"
        android:startColor="#FFFCF5" /> 

问题解决,但是,为什么呢?



Android 官方文档:GradientDrawable


developer.android.com/reference/a…

看下官方文档对渐变的描述,



XML attributes



android:angle

Angle of the gradient, used only with linear gradient. Must be a multiple of 45 in the range [0, 315].

May be a floating point value, such as “

1.2

”.

确实有提到,角度必须是45度的倍数,值的范围为0-315,且值类型为float型,那为什么有的机器崩溃,有的不崩溃呢?

直接看下系统源码对

GradientDrawable

的实现,查找里面读取

android:angle

配置相关代码



GradientDrawable源码:API 27、28


Android 9 及以下

版本处理逻辑:

  1. 对于非45度角的值,系统会主动抛出异常
  2. 角度支持设置为:0、45、90、135、180、225、270、315 这8个值
 private void updateGradientDrawableGradient(Resources r, TypedArray a)
            throws XmlPullParserException {
       
   			...
        if (st.mGradient == LINEAR_GRADIENT) {
            int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
            angle %= 360;

            if (angle % 45 != 0) {
                throw new XmlPullParserException(a.getPositionDescription()
                        + "<gradient> tag requires 'angle' attribute to "
                        + "be a multiple of 45");
            }

            st.mAngle = angle;

            switch (angle) {
                case 0:
                    st.mOrientation = Orientation.LEFT_RIGHT;
                    break;
                case 45:
                    st.mOrientation = Orientation.BL_TR;
                    break;
                case 90:
                    st.mOrientation = Orientation.BOTTOM_TOP;
                    break;
                case 135:
                    st.mOrientation = Orientation.BR_TL;
                    break;
                case 180:
                    st.mOrientation = Orientation.RIGHT_LEFT;
                    break;
                case 225:
                    st.mOrientation = Orientation.TR_BL;
                    break;
                case 270:
                    st.mOrientation = Orientation.TOP_BOTTOM;
                    break;
                case 315:
                    st.mOrientation = Orientation.TL_BR;
                    break;
            }
        } else {
            ...
        }
} 



GradientDrawable源码:API 29


Android 10

处理逻辑:

  1. 对于非45度角的值,系统仍会主动抛出异常
  2. 支持角度负值设置。如果角度设置为负值,如-45度,系统默认会重新包装为正确的值,315度
 private void updateGradientDrawableGradient(Resources r, TypedArray a) {
    final GradientState st = mGradientState;

    int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
    st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
    ...
  }

  @NonNull
  public Orientation getOrientation() {
    updateGradientStateOrientation();
    return mOrientation;
  }

  private void updateGradientStateOrientation() {
    if (mGradient == LINEAR_GRADIENT) {
      int angle = mAngle;
      if (angle % 45 != 0) {
        throw new IllegalArgumentException("Linear gradient requires 'angle' attribute "
                                           + "to be a multiple of 45");
      }

      Orientation orientation;
      switch (angle) {
        case 0:
          orientation = Orientation.LEFT_RIGHT;
          break;
        case 45:
          orientation = Orientation.BL_TR;
          break;
        case 90:
          orientation = Orientation.BOTTOM_TOP;
          break;
        case 135:
          orientation = Orientation.BR_TL;
          break;
        case 180:
          orientation = Orientation.RIGHT_LEFT;
          break;
        case 225:
          orientation = Orientation.TR_BL;
          break;
        case 270:
          orientation = Orientation.TOP_BOTTOM;
          break;
        case 315:
          orientation = Orientation.TL_BR;
          break;
        default:
          // Should not get here as exception is thrown above if angle is not multiple
          // of 45 degrees
          orientation = Orientation.LEFT_RIGHT;
          break;
      }
      mOrientation = orientation;
    }
  } 



GradientDrawable源码:API 30



Android 11

开始,改动点:

  1. 对于非法角度值,不在主动抛异常
  2. 如果角度设置为负值,如-45度,系统默认会重新包装为正确的值,315度
private void updateGradientDrawableGradient(Resources r, TypedArray a) {
        final GradientState st = mGradientState;
				...
        int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);

        // GradientDrawable historically has not parsed negative angle measurements and always
        // stays on the default orientation for API levels older than Q.
        // Only configure the orientation if the angle is greater than zero.
        // Otherwise fallback on Orientation.TOP_BOTTOM
        // In Android Q and later, actually wrap the negative angle measurement to the correct
        // value
        if (sWrapNegativeAngleMeasurements) {
            st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
        } else {
            st.mAngle = angle % 360;
        }

        if (st.mAngle >= 0) {
            switch (st.mAngle) {
                case 0:
                    st.mOrientation = Orientation.LEFT_RIGHT;
                    break;
                case 45:
                    st.mOrientation = Orientation.BL_TR;
                    break;
                case 90:
                    st.mOrientation = Orientation.BOTTOM_TOP;
                    break;
                case 135:
                    st.mOrientation = Orientation.BR_TL;
                    break;
                case 180:
                    st.mOrientation = Orientation.RIGHT_LEFT;
                    break;
                case 225:
                    st.mOrientation = Orientation.TR_BL;
                    break;
                case 270:
                    st.mOrientation = Orientation.TOP_BOTTOM;
                    break;
                case 315:
                    st.mOrientation = Orientation.TL_BR;
                    break;
            }
        } else {
            st.mOrientation = DEFAULT_ORIENTATION;
        }
				...
} 



结论


  1. "android:angle"

    只支持

    上、下、左、右以及斜对角8个方向

    的渐变,即值必须为

    45度倍数

    ,且范围为

    [0-315]

    • 即值应为:0、45、90、135、180、225、270、315
  2. 对于非规范值:

    • 低版本系统(Android 11以下),默认会抛出异常;
    • 高版本系统(Android 11及以上)不会抛出异常,
    • 不排除国内Rom会对是否抛出异常进行修改,所以建议按都按第一条进行设置,即可。

最后,其实,在写xml时,鼠标指向

"android:angle"

时,IDE就会主动提示值的范围,可能直接写习惯了,忽略了提示,加上测试用的手机正好没有崩溃,幸好发现的早。

image-20220513110152107.png
教训就是,写代码不论是否熟悉API,还是要多看IDE提示,不要想当然。



版权声明:本文为QIANDXX原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。