一、问题来源
二、原因分析
1.getSupportActionBar()
2.setSupportActionBar()
3.完整过程图
三、解决方法
问题来源
给Activity在代码中设置主题theme,然后Activity被销毁重建了。先看主体代码:
/*=====NoActionBarActivity.java=====*/
public class NoActionBarActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.No_Actionbar);
Toolbar toolbar = (Toolbar) getView().findViewById(R.id.home_page_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(" ");
}
//省略代码...
}
/*=====Style.xml=====*/
<style name="No_Actionbar" parent="Theme.AppCompat.Light">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowBackground">@drawable/bg_actionmode</item>
</style>
正常运行都没问题,但是当Activity被销毁重建时(模拟 Activity销毁重建 方法)就出现crash了,报错 java.lang.IllegalStateException: This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead.
原因分析
当Activity设置ActionBar被销毁重建时,主要涉及AppCompatDelegateImplV9调用getSupportActionBar()
和setSupportActionBar()
两个方法的过程
1.getSupportActionBar()
/*=====AppCompatDelegateImplBase.java=====*/
@Override
public ActionBar getSupportActionBar() {
// The Action Bar should be lazily created as hasActionBar
// could change after onCreate
initWindowDecorActionBar(); //***(1)***
return mActionBar;
}
/*=====AppCompatDelegateImplV9.java=====*/
@Override
public void initWindowDecorActionBar() {
ensureSubDecor(); //***(2)***
if (!mHasActionBar || mActionBar != null) { //***(6)***
return;
}
if (mOriginalWindowCallback instanceof Activity) {
mActionBar = new WindowDecorActionBar((Activity) mOriginalWindowCallback,
mOverlayActionBar);
} else if (mOriginalWindowCallback instanceof Dialog) {
mActionBar = new WindowDecorActionBar((Dialog) mOriginalWindowCallback);
}
if (mActionBar != null) {
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
}
}
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor(); //***(3)***
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
onTitleChanged(title);
}
//省略代码...
}
}
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.");
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) { //***(4)***
requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) { //***(5)***
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
//省略代码..
}
首先,会从(1) initWindowDecorActionBar() —> (2) ensureSubDecor() —> (3) createSubDecor();
然后,根据主题theme中设置的值给requestWindowFeature()
传不同的参数,在theme中设置了<item name="windowActionBar">false</item>
时,正常情况下会走(4)requestWindowFeature(Window.FEATURE_NO_TITLE),但销毁重建时就会走(5)requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR)。为什么会走到(5)去呢?大致原因就是销毁重建getSupportActionBar()
时,还没有走到代码中设置theme的地方(就是上面NoActionBarActivity.java中的setTheme
方法),于是createSubDecor()
获取出来的theme就为默认的了。代码中No_Actionbar主题的父类是Theme.AppCompat.Light,默认带有Actionbar,所以走到了(5)传入参数FEATURE_SUPPORT_ACTION_BAR;
关键就在调用requestWindowFeature()
方法这里:
最后,回到initWindowDecorActionBar()
中的(6),当mHasActionBar为true时就不会return,而是继续往下走,于是就创建了mActionBar。
2.setSupportActionBar()
调用完getSupportActionBar()
后会进行setSupportActionBar()
,crash异常也就是在这个里面抛出的:
/*=====AppCompatDelegateImplV9.java=====*/
@Override
public void setSupportActionBar(Toolbar toolbar) {
if (!(mOriginalWindowCallback instanceof Activity)) {
// Only Activities support custom Action Bars
return;
}
final ActionBar ab = getSupportActionBar(); //***(1)***
if (ab instanceof WindowDecorActionBar) {
throw new IllegalStateException("This Activity already has an action bar supplied " + //***(2)***
"by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set " +
"windowActionBar to false in your theme to use a Toolbar instead.");
}
// If we reach here then we're setting a new action bar
// First clear out the MenuInflater to make sure that it is valid for the new Action Bar
mMenuInflater = null;
// If we have an action bar currently, destroy it
if (ab != null) {
ab.onDestroy();
}
if (toolbar != null) {
final ToolbarActionBar tbab = new ToolbarActionBar(toolbar,
((Activity) mOriginalWindowCallback).getTitle(), mAppCompatWindowCallback);
mActionBar = tbab;
mWindow.setCallback(tbab.getWrappedWindowCallback());
} else {
mActionBar = null;
// Re-set the original window callback since we may have already set a Toolbar wrapper
mWindow.setCallback(mAppCompatWindowCallback);
}
invalidateOptionsMenu();
}
由上面知道,在get销毁重建时在getSupportActionBar()
中会创建一个ActionBar出来,也就是在(1)出获取的ActionBar不为null了,于是就走到了(2)中,抛出了异常。
3.完整过程图
解决方法
1.在AndroidManifest.xml中为Activity设置theme,不在代码中动态设置
2.代码动态设置theme时,在super.onCreate()
前面设置theme
/*=====NoActionBarActivity.java=====*/
public class NoActionBarActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(R.style.No_Actionbar); //修改setTheme()在super.onCreate()之前
super.onCreate(savedInstanceState);
Toolbar toolbar = (Toolbar) getView().findViewById(R.id.home_page_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(" ");
}
//省略代码...
}