SystemUI闪退异常排查-InflateException 基于Android12
- 背景
- 报错代码跟踪栈
- 粗略确定报错原因
- 基于问题去追踪真正原因
- 问题1:PageIndicator中报错信息怎么来的
- 问题2:?android:attr/textColor为何会导致属性错误
- 问题3:报错信息{t=0x2/d=0x1010098 a=-1}究竟是啥意思
- 结论
- 处理方式
背景
这两天上报一个systemui闪退的异常,报错的地方很奇怪,我也没有动过那块代码,难不成鄙人代码还有隔山打牛之功效?
报错代码跟踪栈
android.view.InflateException: Binary XML file line #46 in com.android.systemui:layout/media_carousel: Binary XML file line #46 in com.android.systemui:layout/media_carousel: Error inflating class com.android.systemui.qs.PageIndicator
Caused by: android.view.InflateException: Binary XML file line #46 in com.android.systemui:layout/media_carousel: Error inflating class com.android.systemui.qs.PageIndicator
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at android.view.LayoutInflater.createView(LayoutInflater.java:858)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1010)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:965)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:1127)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1088)
at android.view.LayoutInflater.inflate(LayoutInflater.java:686)
at android.view.LayoutInflater.inflate(LayoutInflater.java:538)
at com.android.systemui.media.MediaCarouselController.inflateMediaCarousel(MediaCarouselController.kt:315)
at com.android.systemui.media.MediaCarouselController.<init>(MediaCarouselController.kt:175)
at com.android.systemui.media.MediaCarouselController_Factory.newInstance(MediaCarouselController_Factory.java:95)
at com.android.systemui.media.MediaCarouselController_Factory.get(MediaCarouselController_Factory.java:74)
at com.android.systemui.media.MediaCarouselController_Factory.get(MediaCarouselController_Factory.java:16)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at dagger.internal.DelegateFactory.get(DelegateFactory.java:36)
at com.android.systemui.media.MediaHierarchyManager_Factory.get(MediaHierarchyManager_Factory.java:64)
at com.android.systemui.media.MediaHierarchyManager_Factory.get(MediaHierarchyManager_Factory.java:15)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at com.android.systemui.media.dagger.MediaModule_ProvidesKeyguardMediaHostFactory.get(MediaModule_ProvidesKeyguardMediaHostFactory.java:42)
at com.android.systemui.media.dagger.MediaModule_ProvidesKeyguardMediaHostFactory.get(MediaModule_ProvidesKeyguardMediaHostFactory.java:12)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at com.android.systemui.media.KeyguardMediaController_Factory.get(KeyguardMediaController_Factory.java:53)
at com.android.systemui.media.KeyguardMediaController_Factory.get(KeyguardMediaController_Factory.java:13)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at com.android.systemui.statusbar.notification.stack.NotificationSectionsManager_Factory.get(NotificationSectionsManager_Factory.java:62)
at com.android.systemui.statusbar.notification.stack.NotificationSectionsManager_Factory.get(NotificationSectionsManager_Factory.java:12)
at com.android.systemui.statusbar.notification.stack.AmbientState_Factory.get(AmbientState_Factory.java:33)
at com.android.systemui.statusbar.notification.stack.AmbientState_Factory.get(AmbientState_Factory.java:8)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at com.android.systemui.statusbar.LockscreenShadeTransitionController_Factory.get(LockscreenShadeTransitionController_Factory.java:85)
at com.android.systemui.statusbar.LockscreenShadeTransitionController_Factory.get(LockscreenShadeTransitionController_Factory.java:17)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at com.android.systemui.statusbar.PulseExpansionHandler_Factory.get(PulseExpansionHandler_Factory.java:69)
at com.android.systemui.statusbar.PulseExpansionHandler_Factory.get(PulseExpansionHandler_Factory.java:16)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule_ProvideStatusBarFactory.get(StatusBarPhoneModule_ProvideStatusBarFactory.java:467)
at com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule_ProvideStatusBarFactory.get(StatusBarPhoneModule_ProvideStatusBarFactory.java:100)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at dagger.internal.DelegateFactory.get(DelegateFactory.java:36)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at com.android.systemui.navigationbar.NavigationBar.onViewAttachedToWindow(NavigationBar.java:621)
at android.view.View.dispatchAttachedToWindow(View.java:20766)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3490)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2613)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2126)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8649)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:780)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7842)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 0: TypedValue{t=0x2/d=0x1010098 a=-1}
at android.content.res.TypedArray.getColorStateList(TypedArray.java:598)
at com.android.systemui.qs.PageIndicator.<init>(PageIndicator.java:70)
... 59 more
粗略确定报错原因
- 根据第一行可以知道名为
media_carousel
的layout文件在加载时候出错,报错行在46行 - 从倒数第二行可以确定类
com.android.systemui.qs.PageIndicator
报错,报错行在70行 - 从倒数第三四行可以确定在
TypedArray
中获取getColorStateList
属性出错,报错行598 - 基于以上认知,再根据总体报错信息可以粗略得出:
LayoutInflater
在加载xml布局文件时候,布局文件中的com.android.systemui.qs.PageIndicator
初始化属性值时候报错导致的java.lang.reflect.InvocationTargetException
从而造成闪退
基于问题去追踪真正原因
个人经验之谈,遇到报错追究问题根源可以采取不断提问再解决自己提问的方式,从而拼凑造成异常的根源的完整拼图。不断的提问可以激发自己好奇心,也使得问题慢慢深入,不断的解决完疑问后可以在一个更深层次的维度去看待问题。
问题1:PageIndicator
中报错信息怎么来的
先找到media_carousel文件
<com.android.systemui.qs.PageIndicator
android:id="@+id/media_page_indicator"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="4dp"
android:tint="?android:attr/textColor"
android:forceHasOverlappingRendering="false"
/>
可以看到PageIndicator
中tint
不是直接定义color而是引用了其他属性?android:attr/textColor
at com.android.systemui.qs.PageIndicator.<init>(PageIndicator.java:70)
根据报错信息指示,我们找到具体报错行
public PageIndicator(Context context, AttributeSet attrs) {
...
TypedArray array = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.tint});
if (array.hasValue(0)) {
//报错行
mTint = array.getColorStateList(0);
} else {
mTint = Utils.getColorAccent(context);
}
...
}
再根据以下报错跟踪到598行
at android.content.res.TypedArray.getColorStateList(TypedArray.java:598)
public ColorStateList getColorStateList(@StyleableRes int index) {
...
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
return mResources.loadColorStateList(value, value.resourceId, mTheme);
}
...
}
可以看到报错信息来自这里,TypedValue
的type 为TypedValue.TYPE_ATTRIBUTE导致报错
Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 0: TypedValue{t=0x2/d=0x1010098 a=-1}
问题2:?android:attr/textColor
为何会导致属性错误
首先我们确定下?android:attr/textColor
指向是不是有问题
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:forceHasOverlappingRendering="false"
android:theme="@style/MediaPlayer">
</FrameLayout>
我们看到media_carousel
的布局文件中设置了主题,名为MediaPlayer
,接着跟踪该主题内容
<style name="MediaPlayer" parent="@*android:style/Theme.DeviceDefault.Light">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:backgroundTint">@android:color/system_accent2_50</item>
</style>
可以看到该textColor
引用的是另一个应用?android:attr/textColorPrimary
,而该主题继承自Theme.DeviceDefault.Light
android/frameworks/base/core/res/res/values/themes_device_defaults.xml
<style name="Theme.DeviceDefault.Light" parent="Theme.Material.Light" >
...
<item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
...
</style>
textColorPrimary
属性指向的是资源文件text_color_primary_device_default_light
android/frameworks/base/core/res/res/color/text_color_primary_device_default_light.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/system_neutral1_400"/>
<item android:color="@color/system_neutral1_900"/>
</selector>
该文件是一个color selector,定义了控件启用以及禁用的颜色,跟踪其引用的颜色定义如下
android/frameworks//base/core/res/remote_color_resources_res/values/colors.xml
...
<color name="system_neutral1_900">#1b1b1b</color>
<color name="system_neutral1_400">#909090</color>
...
追踪到这里,我们可以发现引用并没有问题,为了辅助验证我们的看法,写个demo验证下
- 首先自定义一个简单控件
SimpleTextView
class SimpleTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
private val TAG = SimpleTextView::class.simpleName
init {
val array = context.obtainStyledAttributes(attrs, intArrayOf(R.attr.tint))
if (array.hasValue(0)) {
val mTint = array.getColorStateList(0)
Log.e(TAG, mTint.toString())
}
array.recycle()
}
}
- 模仿
media_carousel
中使用?android:attr/textColor
属性
...
<com.example.globalsettings.SimpleTextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:text="simple TextView"
android:textSize="40dp"
android:tint="?android:attr/textColor"
app:layout_constraintBottom_toBottomOf="parent" />
...
3.应用MediaPlayer
主题
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:forceHasOverlappingRendering="false"
android:theme="@style/MediaPlayer">
</FrameLayout>
Demo运行起来属性读取正常,未出现报错。那么该报错到底是什么原因呢?为了继续追踪该问题我们换个角度继续追踪问题。
问题3:报错信息{t=0x2/d=0x1010098 a=-1}
究竟是啥意思
Failed to resolve attribute at index 0: TypedValue{t=0x2/d=0x1010098 a=-1}
上面我们知道该报错位于TypedValue
的598行,代码如下
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
return mResources.loadColorStateList(value, value.resourceId, mTheme);
}
value
类型为TypedValue
内容为{t=0x2/d=0x1010098 a=-1}
,该内容来自toString()方法
,如下
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("TypedValue{t=0x").append(Integer.toHexString(type));
sb.append("/d=0x").append(Integer.toHexString(data));
if (type == TYPE_STRING) {
sb.append(" \"").append(string != null ? string : "<null>").append("\"");
}
if (assetCookie != 0) {
sb.append(" a=").append(assetCookie);
}
if (resourceId != 0) {
sb.append(" r=0x").append(Integer.toHexString(resourceId));
}
sb.append("}");
return sb.toString();
}
上面可以知道t
实际上代表type
,其type
0x2对应TYPE_ATTRIBUTE
也就是属性类型。而根据我们demo来看实际正常应该是TYPE_STRING
public static final int TYPE_ATTRIBUTE = 0x02;
public static final int TYPE_STRING = 0x03;
其中d
对应的data
是资源ID,需要注意的是framework资源是在编译前已经指定,而app的资源ID是在编译后才确定。所以据此我们可以跟踪到该ID0x1010098
对应的资源
android/frameworks/base/core/res/res/values/public.xml
<public type="attr" name="textColor" id="0x01010098" />
该ID对应的属性就是我们之前看到的?android:attr/textColor
结论
可以看出textColor
的值就是attr类型,如果在theme、app的其他xml文件、framework的attr中都没有指定ttextColor
t或者textColorPrimary
,那么最终就会报错。
Android中属性值的查找会在 xml中的属性定义,theme,控件构造函数的第三个参数defStyleAttr(AttributeSet)
,控件构造函数的第四个参数defStyleRes(按查找优先顺序排序)。如果在这些范围还未能找到就会报错。
综合上述分析,在读取textColor
属性时候未能读取到其实际对应的值导致的异常。那么又有问题了,代码里面不是都是正常对应的吗?可能的原因是framework-res.apk在编译过程中丢失资源文件或者读取失败。这个要结合实际情况去定位,此为后话。
处理方式
- 治标:
android:tint=“?android:attr/textColor”
改属性调用为直接调用 - 治本: 结合实际环境追踪framework-res.apk具体原因并解决