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 及以下版本处理逻辑:
- 对于非45度角的值,系统会主动抛出异常
- 角度支持设置为: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 处理逻辑:
- 对于非45度角的值,系统仍会主动抛出异常
- 支持角度负值设置。如果角度设置为负值,如-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开始,改动点:
- 对于非法角度值,不在主动抛异常
- 如果角度设置为负值,如-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;
...
结论
"android:angle"
只支持上、下、左、右以及斜对角8个方向的渐变,即值必须为45度倍数,且范围为 [0-315]。- 即值应为:0、45、90、135、180、225、270、315
- 对于非规范值:
- 低版本系统(Android 11以下),默认会抛出异常;
- 高版本系统(Android 11及以上)不会抛出异常,
- 不排除国内Rom会对是否抛出异常进行修改,所以建议按都按第一条进行设置,即可。
最后,其实,在写xml时,鼠标指向 "android:angle"
时,IDE就会主动提示值的范围,可能直接写习惯了,忽略了提示,加上测试用的手机正好没有崩溃,幸好发现的早。
教训就是,写代码不论是否熟悉API,还是要多看IDE提示,不要想当然。
以上是关于Android 非45度倍数角度渐变引起的崩溃的主要内容,如果未能解决你的问题,请参考以下文章