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

Posted QIANDXX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 非45度倍数角度渐变引起的崩溃相关的知识,希望对你有一定的参考价值。

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就会主动提示值的范围,可能直接写习惯了,忽略了提示,加上测试用的手机正好没有崩溃,幸好发现的早。

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

以上是关于Android 非45度倍数角度渐变引起的崩溃的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义控件之可平移、缩放、旋转图片控件

既是3的倍数又是5的倍数都有哪些

为啥大多数值在 Android xml 中是 8 的倍数?

GO和KEGG富集倍数(Fold Enrichment)如何计算

android 图片双击放大倍数的算法

android用 MPAndroidChart空间生成曲线,怎么更改初始放大倍数?初始刻度值?