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

欢迎Follow我的GitHub, 关注我的简书.

Why

本文的合集已经编著成书,高级Android开发强化实战,欢迎各位读友的建议和指导。在京东即可购买:https://item.jd.com/12385680.html

Android

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

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

E/AndroidRuntime: FATAL EXCEPTION: main
java.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
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = DataBindingUtil.setContentView(this, R.layout.activity_home);
    // ...
}

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

// DataBindingUtil.java
public 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
@Override
public 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!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,393评论 25 708
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,957评论 19 139
  • Android程序员都知道Activity调用setContentView的方法是将xml布局文件加载到Activ...
    AxeChen阅读 6,114评论 4 14
  • 你说,江篱之畔,故人未归,空有佳人独盼 你说,陌上少年,一笑倾城,空有余香绕城 你说,江南三月,满城烟雨,才子丽人...
    糖醋排骨要加油阅读 305评论 0 0
  • 简书,你好! 第一次看到你的名字,就莫名地喜欢了! 进来了!希望你能带给我更多的惊喜!! 我知道自己已经过了做梦的...
    云沐风阅读 198评论 1 1