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)就不能启动呢? 原因很简单, 就是因为开源项目的主题资源设置有误. 默认主题在AndroidManifest
的theme
属性中设置.
<application android:theme="@style/AppTheme">
点击IDE的AppTheme
跳转至声明, 发现只有一处, 即在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!
网友评论