首页 > 代码库 > 为什么手机无法运行应用? Values之谜

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

欢迎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)的模拟机上, 应用运行正常.

技术分享

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

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中声明.

技术分享

原因

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

解决

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

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

技术分享

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

That’s all! Enjoy it!

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

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