背景

这两天上报一个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

粗略确定报错原因

  1. 根据第一行可以知道名为media_carousel 的layout文件在加载时候出错,报错行在46行
  2. 从倒数第二行可以确定类com.android.systemui.qs.PageIndicator报错,报错行在70行
  3. 从倒数第三四行可以确定在TypedArray中获取getColorStateList属性出错,报错行598
  4. 基于以上认知,再根据总体报错信息可以粗略得出: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"
    />

可以看到PageIndicatortint不是直接定义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验证下

  1. 首先自定义一个简单控件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()
    }
}
  1. 模仿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中都没有指定ttextColort或者textColorPrimary,那么最终就会报错。

Android中属性值的查找会在 xml中的属性定义,theme,控件构造函数的第三个参数defStyleAttr(AttributeSet),控件构造函数的第四个参数defStyleRes(按查找优先顺序排序)。如果在这些范围还未能找到就会报错。

综合上述分析,在读取textColor属性时候未能读取到其实际对应的值导致的异常。那么又有问题了,代码里面不是都是正常对应的吗?可能的原因是framework-res.apk在编译过程中丢失资源文件或者读取失败。这个要结合实际情况去定位,此为后话。

处理方式

  1. 治标:android:tint=“?android:attr/textColor”改属性调用为直接调用
  2. 治本: 结合实际环境追踪framework-res.apk具体原因并解决
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐