为什么手机无法运行应用? Values之谜

来源:互联网 发布:淘宝卖假货被公安抓了 编辑:程序博客网 时间:2024/05/22 09:02

欢迎Follow我的GitHub, 关注我的CSDN, 精彩不断!

CSDN: http://blog.csdn.net/caroline_wendy/article/details/68923156

在GitHub上Clone的某开源Android项目, 下载配置, 完成构建, 在手机上可以安装, 但是无法运行. 项目的编译版本(compileSdkVersion)是25(7.1), 最低的兼容版本(minSdkVersion)是19(4.4), 手机的系统版本是21(5.0), 已经满足应用的最低运行条件. 然而, 在相同系统版本(25, 7.1)的模拟机上, 应用运行正常.

Why

在我的手机运行应用时, 报错如下:

E/AndroidRuntime: FATAL EXCEPTION: mainjava.lang.RuntimeException: Unable to start activity ComponentInfo{com.saulmm.cui/com.saulmm.cui.HomeActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.    at android.support.v7.app.AppCompatDelegateImplV9.createSubDecor(AppCompatDelegateImplV9.java:359)    at android.support.v7.app.AppCompatDelegateImplV9.ensureSubDecor(AppCompatDelegateImplV9.java:328)    at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:289)    at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:140)    at android.databinding.DataBindingUtil.setContentView(DataBindingUtil.java:276)    at android.databinding.DataBindingUtil.setContentView(DataBindingUtil.java:261)    at com.saulmm.cui.HomeActivity.onCreate(HomeActivity.java:42)

定位

问题起源于DataBindingUtil#setContentView, DataBindingUtil绑定layout布局.

// HomeActivity.java@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    binding = DataBindingUtil.setContentView(this, R.layout.activity_home);    // ...}

调用AppCompatActivity#setContentView, Activity绑定layout布局.

// DataBindingUtil.javapublic static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,        DataBindingComponent bindingComponent) {    activity.setContentView(layoutId);    // ...}

最终是Activity代理实现类AppCompatDelegateImplV9实现setContentView的具体逻辑. 通过ensureSubDecor方法创建DecorView, 填充Activity的自定义布局resId, ensureSubDecor再调用createSubDecor方法创建DecorView.

// AppCompatDelegateImplV9.java@Overridepublic void setContentView(int resId) {    ensureSubDecor(); // 创建并初始化DecorView    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);    contentParent.removeAllViews();    LayoutInflater.from(mContext).inflate(resId, contentParent);    mOriginalWindowCallback.onContentChanged();}private void ensureSubDecor() {    if (!mSubDecorInstalled) {        mSubDecor = createSubDecor();        // ...    }}

createSubDecor方法, 根据应用的样式主题(Theme)设置根布局DecorView的样式, 并执行初始化. 当未含有AppCompatTheme_windowActionBar属性时, 则认为主题未设置, 并抛出异常IllegalStateException.

// AppCompatDelegateImplV9.java// 根据布局样式Style设置根布局DecorView的样式private ViewGroup createSubDecor() {    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);    // 没有布局属性    if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {        a.recycle();        // 问题所在!        throw new IllegalStateException(                "You need to use a Theme.AppCompat theme (or descendant) with this activity.");    }    // ...}

为什么API 25的模拟器可以启动, 我的手机(API 21)就不能启动呢? 原因很简单, 就是因为开源项目的主题资源设置有误. 默认主题在AndroidManifesttheme属性中设置.

<application android:theme="@style/AppTheme">

点击IDE的AppTheme跳转至声明, 发现只有一处, 即在values-v23中声明.

values-v23

原因

对于资源属性而言, 系统默认查找与匹配低于当前API等级的属性, 保证高版本属性不会在低版本中执行. 因为高版本会添加更多的新接口, 低版本无法找到, 强制使用可能导致异常甚至崩溃, 所以禁止访问高版本的属性.

解决

理解了问题的所在, 解决方案就非常简单. 为了支持最低API以上的全部系统, 在默认的values/themes.xml中, 添加AppTheme属性即可.

<style name="AppTheme" parent="Base.AppTheme"/>

values

问题虽小, 但不可忽视, 否则就只能在某些手机可用, 在某些手机崩溃, 摸不着头脑. 在开发中, 优先在默认values文件夹中添加属性, 如果需要额外支持, 在其他高版本values-vXX中再添加. Do you get it?

That’s all! Enjoy it!

2 1